Welcome to the Shogun SDK! This powerful and user-friendly SDK is designed to simplify decentralized authentication and wallet management in Web3 applications. Whether you're building a new dApp or enhancing an existing one, Shogun SDK provides the tools you need for secure and efficient crypto operations.
In today's Web3 landscape, developers face significant challenges in managing user authentication and crypto operations while maintaining security and usability. Traditional solutions often require compromises between security, usability, and decentralization.
Shogun combines GunDB's decentralization, modern authentication standards, and the Ethereum ecosystem into a comprehensive SDK that prioritizes both security and user experience.
npm install shogun-core
# or
yarn add shogun-core
import {
ShogunCore,
CorePlugins,
PluginCategory,
initShogunBrowser,
} from "shogun-core";
// Inizializzazione per ambiente Node.js
const shogun = new ShogunCore({
gundb: {
peers: ["https://your-gun-peer.com/gun"],
localStorage: true,
},
providerUrl: "https://ethereum-rpc-url.com",
// Abilitazione plugin integrati
webauthn: { enabled: true },
metamask: { enabled: true },
stealth: { enabled: true },
did: { enabled: true },
walletManager: { enabled: true },
});
// Oppure per browser (consigliato per applicazioni web)
const shogunBrowser = initShogunBrowser({
gundb: {
peers: ["https://your-gun-peer.com/gun"],
websocket: true,
},
providerUrl: "https://ethereum-rpc-url.com",
webauthn: {
enabled: true,
rpName: "Your App",
rpId: "yourdomain.com",
},
});
// Authentication examples
// Autenticazione standard
const passwordLogin = await shogun.login("username", "password");
// Accesso ai plugin
const webauthnPlugin = shogun.getPlugin(CorePlugins.WebAuthn);
const metamaskPlugin = shogun.getPlugin(CorePlugins.MetaMask);
const walletPlugin = shogun.getPlugin(CorePlugins.Wallet);
if (webauthnPlugin) {
const credentials = await webauthnPlugin.generateCredentials(
"username",
null,
true
); // true = login
if (credentials.success) {
// Usa le credenziali per autenticare...
}
}
if (metamaskPlugin) {
// Login con MetaMask
try {
// Connessione e autenticazione con MetaMask
const connectResult = await metamaskPlugin.connectMetaMask();
if (connectResult.success) {
const address = connectResult.address;
// Login usando l'indirizzo
const loginResult = await metamaskPlugin.loginWithMetaMask(address);
console.log("MetaMask login result:", loginResult);
}
} catch (error) {
console.error("Error during MetaMask login:", error);
}
}
// Wallet operations
if (walletPlugin) {
const wallet = await walletPlugin.createWallet();
const mainWallet = walletPlugin.getMainWallet();
}
// Utilizzo dello Stealth Plugin
if (shogun.hasPlugin(CorePlugins.Stealth)) {
const stealthPlugin = shogun.getPlugin(CorePlugins.Stealth);
// Genera un indirizzo stealth per ricevere pagamenti in modo privato
const stealthAddress = await stealthPlugin.generateStealthAddress();
console.log("Indirizzo stealth generato:", stealthAddress);
// Crea una transazione privata verso un indirizzo stealth
const receiverStealthAddress = "0xEccetera..."; // Indirizzo stealth del destinatario
const amount = "0.01"; // Quantità in ETH
const txResult = await stealthPlugin.createStealthTransaction(
receiverStealthAddress,
amount
);
console.log("Transazione stealth inviata:", txResult);
// Scansiona la blockchain per trovare pagamenti stealth indirizzati a te
const payments = await stealthPlugin.scanStealthPayments();
console.log("Pagamenti stealth trovati:", payments);
}
// Ottenere i plugin per categoria
const walletPlugins = shogun.getPluginsByCategory(PluginCategory.Wallet);
const authPlugins = shogun.getPluginsByCategory(PluginCategory.Authentication);
console.log(
`Found ${walletPlugins.length} wallet plugins and ${authPlugins.length} auth plugins`
);
Shogun provides a modern approach to access authentication methods via the getAuthenticationMethod
API. This simplifies working with different authentication types by providing a consistent interface.
// Recommended modern approach to access authentication methods
// Approach 1: Get the standard password authentication
const passwordAuth = shogun.getAuthenticationMethod("password");
const loginResult = await passwordAuth.login("username", "password123");
const signupResult = await passwordAuth.signUp("newuser", "securepassword");
// Approach 2: Get the WebAuthn authentication (if enabled)
const webauthnAuth = shogun.getAuthenticationMethod("webauthn");
if (webauthnAuth) {
// WebAuthn is available
const isSupported = await webauthnAuth.isSupported();
if (isSupported) {
const credentials = await webauthnAuth.generateCredentials(
"username",
null,
true
);
// Use the credentials...
}
}
// Approach 3: Get the MetaMask authentication (if enabled)
const metamaskAuth = shogun.getAuthenticationMethod("metamask");
if (metamaskAuth) {
// MetaMask is available
const isAvailable = await metamaskAuth.isAvailable();
if (isAvailable) {
const connectResult = await metamaskAuth.connectMetaMask();
if (connectResult.success) {
const loginResult = await metamaskAuth.login(connectResult.address);
console.log("Logged in with MetaMask:", loginResult);
}
}
}
// Example: Dynamic authentication based on user choice
function authenticateUser(
method: "password" | "webauthn" | "metamask",
username: string,
password?: string
) {
const auth = shogun.getAuthenticationMethod(method);
if (!auth) {
console.error(`Authentication method ${method} not available`);
return Promise.resolve({ success: false, error: "Method not available" });
}
switch (method) {
case "password":
return auth.login(username, password);
case "webauthn":
return auth.generateCredentials(username).then((creds) => {
if (creds.success) {
return auth.login(username);
}
return { success: false, error: "WebAuthn failed" };
});
case "metamask":
return auth.connectMetaMask().then((result) => {
if (result.success) {
return auth.login(result.address);
}
return { success: false, error: "MetaMask connection failed" };
});
}
}
// Usage
authenticateUser("password", "username", "password123").then((result) => {
console.log("Authentication result:", result);
});
Shogun SDK provides first-class RxJS integration, enabling reactive programming patterns with GunDB:
import { map, filter } from "rxjs/operators";
// Observe a user profile in real-time
shogun.observe<{ name: string; status: string }>("users/profile/123").subscribe(
(profile) => console.log("Profile updated:", profile),
(error) => console.error("Error observing profile:", error)
);
// Work with reactive collections
const todos$ = shogun.match<{ id: string; task: string; completed: boolean }>(
"todos"
);
todos$.subscribe((todos) => console.log("All todos:", todos));
// Filter collections with RxJS operators
todos$
.pipe(map((todos) => todos.filter((todo) => !todo.completed)))
.subscribe((pendingTodos) => console.log("Pending todos:", pendingTodos));
// Update data reactively
shogun
.rxPut<{ name: string; status: string }>("users/profile/123", {
name: "John Doe",
status: "online",
})
.subscribe(() => console.log("Profile updated"));
// Create computed values from multiple data sources
shogun
.compute<
any,
{ username: string; pendingCount: number; completedCount: number }
>(["todos", "users/profile/123"], (todos, profile) => ({
username: profile.name,
pendingCount: todos.filter((t) => !t.completed).length,
completedCount: todos.filter((t) => t.completed).length,
}))
.subscribe((stats) => console.log("Dashboard stats:", stats));
// Work with user data
if (shogun.isLoggedIn()) {
shogun
.observeUser<{ theme: string; notifications: boolean }>("preferences")
.subscribe((prefs) => console.log("User preferences:", prefs));
}
Shogun Core includes advanced GunDB features that allow you to build complex and robust decentralized applications.
The Repository Pattern provides an elegant abstraction for data access, with support for typing, transformation, and encryption.
import { ShogunCore, GunRepository } from "shogun-core";
// Define an interface for your data type
interface User {
id?: string;
name: string;
email: string;
role: string;
lastLogin?: number;
}
// Create a typed repository class
class UserRepository extends GunRepository<User> {
constructor(shogun: ShogunCore) {
// Initialize with options: userScope=true, encryption=true
super(shogun.gun, "users", {
userScope: true,
useEncryption: true,
encryptionKey: shogun.gun.getCurrentUser()?.user._?.sea,
});
}
// Implement transformation from raw data to entity
protected mapToEntity(data: any): User {
return {
id: data.id,
name: data.name || "",
email: data.email || "",
role: data.role || "user",
lastLogin: data.lastLogin || Date.now(),
};
}
// Implement transformation from entity to data to save
protected mapToData(entity: User): any {
// Remove computed or unnecessary fields
const { id, ...data } = entity;
return data;
}
// Add entity-specific methods
async findByRole(role: string): Promise<User[]> {
const all = await this.findAll();
return all.filter((user) => user.role === role);
}
}
// Usage:
const shogun = new ShogunCore({
/* config */
});
const userRepo = new UserRepository(shogun);
// Create a new user
const userId = await userRepo.save({
name: "John Smith",
email: "john@example.com",
role: "admin",
});
// Find a specific user
const user = await userRepo.findById(userId);
// Update a user
await userRepo.update(userId, { role: "superadmin" });
// Delete a user
await userRepo.remove(userId);
The Consensus module allows you to implement decentralized decision-making mechanisms based on voting.
import { ShogunCore } from "shogun-core";
const shogun = new ShogunCore({
/* config */
});
// Consensus configuration (optional)
const consensusConfig = {
threshold: 0.66, // Require 66% of votes in favor
timeWindow: 3600000, // 1 hour voting window
minVotes: 5, // Require at least 5 votes
};
// Access the consensus system
const consensus = shogun.gun.consensus(consensusConfig);
// 1. Propose a change
async function proposeChange() {
const proposalId = await consensus.proposeChange(
"config-update",
{ maxUsers: 1000, featureFlag: true },
{ importance: "high" }
);
console.log(`Proposal created: ${proposalId}`);
return proposalId;
}
// 2. Vote on a proposal
async function vote(proposalId, approve) {
await consensus.vote(proposalId, approve, "Reason for vote");
console.log(`Vote recorded: ${approve ? "Approved" : "Rejected"}`);
}
// 3. Check proposal status
async function checkProposal(proposalId) {
const proposal = await consensus.getProposal(proposalId);
console.log(`Proposal status: ${proposal.status}`);
// Count votes
const voteCount = await consensus.countVotes(proposalId);
console.log(
`Votes: ${voteCount.approvalCount} approvals, ${voteCount.rejectionCount} rejections`
);
console.log(
`The proposal is ${voteCount.approved ? "approved" : "rejected or pending"}`
);
}
The Frozen Space provides a mechanism for immutable data, ideal for logs, audit trails, and content archiving.
import { ShogunCore } from "shogun-core";
const shogun = new ShogunCore({
/* config */
});
// Save immutable data
async function savePermanentData() {
const data = {
content: "This content cannot be modified",
timestamp: Date.now(),
author: "John Smith",
};
// Add to Frozen Space
await shogun.gun.addToFrozenSpace("documents", "doc1", data);
console.log("Document saved immutably");
// Alternative: use content hash as key
const contentHash = await shogun.gun.addHashedToFrozenSpace(
"documents",
data
);
console.log(`Document saved with hash: ${contentHash}`);
return contentHash;
}
// Retrieve immutable data
async function getPermanentData(hash) {
// Retrieve data using hash, with integrity verification
const data = await shogun.gun.getHashedFrozenData("documents", hash, true);
console.log("Document retrieved:", data);
return data;
}
The certificate system enables decentralized and delegated authorizations.
import { ShogunCore } from "shogun-core";
const shogun = new ShogunCore({
/* config */
});
// Generate authorization certificates
async function generateCertificates() {
// Get the key pair from the current user
const pair = shogun.gun.getCurrentUser()?.user._?.sea;
// Generate a single certificate
const cert = await shogun.gun.issueCert({
pair,
tag: "write", // Authorization type
dot: "tasks", // Authorized path
users: "*", // For all users ('*') or specific ('~pubKey')
});
// Generate multiple certificates at once
const certs = await shogun.gun.generateCerts({
pair,
list: [
{ tag: "read", dot: "profiles", users: "*" },
{ tag: "write", dot: "profiles", users: "~ABCDEF" }, // Specific user
{ tag: "admin", dot: "settings", personal: true }, // For personal use only
],
});
return { cert, certs };
}
// Verify a certificate
async function verifyCertificate(cert, pubKey) {
const isValid = await shogun.gun.verifyCert(cert, pubKey);
console.log(`Certificate is ${isValid ? "valid" : "invalid"}`);
// Extract policy from certificate
const policy = await shogun.gun.extractCertPolicy(cert);
console.log("Certificate policy:", policy);
return isValid;
}
The Collections API simplifies managing collections of data with common operations.
import { ShogunCore } from "shogun-core";
const shogun = new ShogunCore({
/* config */
});
// Access the Collections module
const collections = shogun.gun.collections();
// Add an item to a collection
async function addToCollection() {
const itemId = await collections.add("products", {
name: "Smartphone XYZ",
price: 499.99,
category: "electronics",
inStock: true,
});
console.log(`Product added with ID: ${itemId}`);
return itemId;
}
// Update an item
async function updateCollectionItem(id) {
await collections.update("products", id, {
price: 449.99,
inStock: false,
});
console.log("Product updated");
}
// Retrieve all items
async function getAllItems() {
const items = await collections.findAll("products");
console.log(`Found ${items.length} products`);
return items;
}
// Retrieve a specific item
async function getItem(id) {
const item = await collections.findById("products", id);
console.log("Product found:", item);
return item;
}
// Remove an item
async function removeItem(id) {
await collections.remove("products", id);
console.log("Product removed");
}
Shogun Core provides advanced cryptographic utilities for encryption, signing, and hash management.
import { ShogunCore } from "shogun-core";
const shogun = new ShogunCore({
/* config */
});
// Cryptographic operations examples
async function cryptoExamples() {
// Generate key pair
const keyPair = await shogun.gun.generateKeyPair();
// Encryption
const encrypted = await shogun.gun.encrypt("Secret message", keyPair.epriv);
const decrypted = await shogun.gun.decrypt(encrypted, keyPair.epriv);
// Signing and verification
const signed = await shogun.gun.sign({ data: "Authenticated data" }, keyPair);
const verified = await shogun.gun.verify(signed, keyPair.pub);
// Hashing
const textHash = await shogun.gun.hashText("Text to hash");
const objHash = await shogun.gun.hashObj({
complex: "object",
with: ["arrays"],
});
// Short hashes (useful for identifiers)
const shortHash = await shogun.gun.getShortHash(
"user@example.com",
"salt123"
);
// URL-safe hashes
const safeHash = shogun.gun.safeHash(textHash);
const originalHash = shogun.gun.unsafeHash(safeHash);
return {
encrypted,
decrypted,
verified,
textHash,
objHash,
shortHash,
safeHash,
};
}
// End-to-end encryption between users
async function encryptForUser(receiverPub) {
const senderPair = shogun.gun.getCurrentUser()?.user._?.sea;
const receiverKey = { epub: receiverPub };
// Encrypt data for a specific recipient
const message = "This message is only readable by the recipient";
const encrypted = await shogun.gun.encFor(message, senderPair, receiverKey);
return encrypted;
}
// Decrypt data from a specific sender
async function decryptFromUser(senderPub, encryptedData) {
const receiverPair = shogun.gun.getCurrentUser()?.user._?.sea;
const senderKey = { epub: senderPub };
// Decrypt data coming from a specific sender
const decrypted = await shogun.gun.decFrom(
encryptedData,
senderKey,
receiverPair
);
return decrypted;
}
Shogun is particularly suitable for:
Shogun SDK includes a complete technical documentation generated with TSDoc:
Read the documentation here
./docs/index.html
./docs/classes/
for details on the main classes./docs/interfaces/
for details on the interfacesShogun SDK supports extending its functionality through custom plugins. This allows you to add new features while maintaining system modularity.
BasePlugin
or directly implements the ShogunPlugin
interface:import { BasePlugin } from "shogun-core/src/plugins/base";
import { ShogunCore } from "shogun-core";
import { PluginCategory } from "shogun-core/src/types/shogun";
// Define an interface for the plugin's public functionality
export interface MyPluginInterface {
exampleFunction(): void;
// Other public functions
}
// Implement the plugin by extending BasePlugin
export class MyPlugin extends BasePlugin implements MyPluginInterface {
// Required properties
name = "my-plugin";
version = "1.0.0";
description = "My custom plugin for shogun-core";
// Initialization
initialize(core: ShogunCore): void {
super.initialize(core);
// Specific initialization logic
console.log("MyPlugin initialized");
}
// Implement interface functions
exampleFunction(): void {
const core = this.assertInitialized();
// Implementation...
}
// Optional: implement destroy method
destroy(): void {
// Cleanup resources
super.destroy();
}
}
There are two ways to register a plugin with Shogun Core:
import { ShogunCore, PluginCategory } from "shogun-core";
import { MyPlugin } from "./my-plugin";
// Create ShogunCore instance
const shogun = new ShogunCore({
// Configuration...
});
// Create and register the plugin
const myPlugin = new MyPlugin();
myPlugin._category = PluginCategory.Utility; // Optional: assign a category
shogun.register(myPlugin);
import { ShogunCore } from "shogun-core";
import { MyPlugin } from "./my-plugin";
const shogun = new ShogunCore({
// Configuration...
plugins: {
autoRegister: [new MyPlugin()],
},
});
Once registered, you can access the plugin in two ways:
const plugin = shogun.getPlugin<MyPluginInterface>("my-plugin");
if (plugin) {
plugin.exampleFunction();
}
import { PluginCategory } from "shogun-core";
const utilityPlugins = shogun.getPluginsByCategory(PluginCategory.Utility);
console.log(`Found ${utilityPlugins.length} utility plugins`);
// Use the found plugins
utilityPlugins.forEach((plugin) => {
console.log(`Plugin: ${plugin.name}, version: ${plugin.version}`);
});
Available categories include:
PluginCategory.Authentication
: plugins for authenticationPluginCategory.Wallet
: plugins for wallet managementPluginCategory.Privacy
: plugins for privacy and anonymizationPluginCategory.Identity
: plugins for decentralized identityPluginCategory.Utility
: plugins for other functionalitiesShogun SDK supporta transazioni stealth per maggiore privacy nelle operazioni blockchain. Il plugin Stealth permette di creare indirizzi stealth che nascondono il vero destinatario di una transazione.
import { ShogunCore, CorePlugins } from "shogun-core";
// Inizializza Shogun con il plugin Stealth abilitato
const shogun = new ShogunCore({
// Configurazione...
stealth: { enabled: true },
});
// Accesso al plugin Stealth
const stealthPlugin = shogun.getPlugin(CorePlugins.Stealth);
// Esempio di funzionalità Stealth
// 1. Generazione di un nuovo indirizzo stealth
async function generateStealthAddress() {
const stealthResult = await stealthPlugin.generateStealthAddress();
console.log("Nuovo indirizzo stealth:", stealthResult.stealthAddress);
console.log("Chiave di scansione:", stealthResult.scanKey);
return stealthResult;
}
// 2. Invio di una transazione stealth
async function sendStealthPayment(recipientStealthAddress, amountInEth) {
try {
// Ottieni il wallet principale
const walletPlugin = shogun.getPlugin(CorePlugins.Wallet);
const senderWallet = walletPlugin.getMainWallet();
// Crea e invia la transazione stealth
const txResult = await stealthPlugin.createStealthTransaction(
recipientStealthAddress,
amountInEth,
{
gasLimit: 150000,
maxFeePerGas: "50", // in gwei
}
);
console.log("Transazione stealth inviata:", txResult);
console.log("Hash della transazione:", txResult.hash);
return txResult;
} catch (error) {
console.error("Errore nell'invio della transazione stealth:", error);
throw error;
}
}
// 3. Scansione per transazioni stealth in entrata
async function scanForPayments() {
try {
// Scansiona la blockchain per pagamenti stealth ricevuti
const payments = await stealthPlugin.scanStealthPayments();
console.log(`Trovati ${payments.length} pagamenti stealth`);
payments.forEach((payment, index) => {
console.log(`Pagamento ${index + 1}:`);
console.log(`- Indirizzo: ${payment.address}`);
console.log(`- Valore: ${payment.amount} ETH`);
console.log(`- Blocco: ${payment.blockNumber}`);
});
return payments;
} catch (error) {
console.error("Errore durante la scansione dei pagamenti stealth:", error);
throw error;
}
}
// 4. Reclama una transazione stealth ricevuta
async function claimStealthPayment(paymentInfo) {
try {
// Reclama il pagamento stealth spostando i fondi al wallet principale
const claimResult = await stealthPlugin.claimStealthPayment(
paymentInfo.address,
paymentInfo.privateKey
);
console.log("Pagamento stealth reclamato:", claimResult);
console.log("Hash della transazione di claim:", claimResult.hash);
return claimResult;
} catch (error) {
console.error("Errore nel reclamo del pagamento stealth:", error);
throw error;
}
}
Questa implementazione utilizza il protocollo Stealth Address per migliorare la privacy delle transazioni sulla blockchain Ethereum.
Shogun Core can be used directly in modern web browsers. This makes it possible to create decentralized applications that run entirely from the client's browser.
You can include Shogun Core in two ways:
<script src="path/to/shogun-core.js"></script>
npm install shogun-core
# or
yarn add shogun-core
And then import it in your applications:
// ESM
import {
ShogunCore,
initShogunBrowser,
CorePlugins,
PluginCategory,
} from "shogun-core";
// CommonJS
const {
ShogunCore,
initShogunBrowser,
CorePlugins,
PluginCategory,
} = require("shogun-core");
// Initialize Shogun with browser-optimized configuration
const shogun = initShogunBrowser({
gundb: {
peers: ["https://your-gun-relay.com/gun"],
websocket: true, // Use WebSocket for communication
},
providerUrl: "https://ethereum-rpc-url.com",
// WebAuthn configuration for biometric/device authentication
webauthn: {
enabled: true,
rpName: "Your App",
rpId: window.location.hostname,
},
// Optional: attiva stealth e wallet manager
stealth: {
enabled: true,
},
walletManager: {
enabled: true,
},
did: {
enabled: true,
},
});
// Registration
async function signup() {
try {
const result = await shogun.signUp("username", "password");
console.log("Registration completed:", result);
} catch (error) {
console.error("Error during registration:", error);
}
}
// Login
async function login() {
try {
const result = await shogun.login("username", "password");
console.log("Login completed:", result);
} catch (error) {
console.error("Error during login:", error);
}
}
// WebAuthn Login using the getAuthenticationMethod (modern way)
async function webAuthnLogin() {
try {
const username = document.getElementById("webauthnUsername").value;
const authMethod = shogun.getAuthenticationMethod("webauthn");
if (authMethod && (await authMethod.isSupported())) {
const credentials = await authMethod.generateCredentials(
username,
null,
true
);
if (credentials.success) {
// Utilizza le credenziali per autenticare l'utente
const loginResult = await authMethod.login(username);
console.log("WebAuthn login result:", loginResult);
}
} else {
console.error("WebAuthn not supported by this browser");
}
} catch (error) {
console.error("Error during WebAuthn login:", error);
}
}
// MetaMask Login using the getAuthenticationMethod (modern way)
async function metamaskLogin() {
try {
const authMethod = shogun.getAuthenticationMethod("metamask");
if (authMethod && (await authMethod.isAvailable())) {
// Get connection
const connectResult = await authMethod.connectMetaMask();
if (connectResult.success) {
// Login with the address
const loginResult = await authMethod.login(connectResult.address);
console.log("MetaMask login result:", loginResult);
}
} else {
console.error("MetaMask not available");
}
} catch (error) {
console.error("Error during MetaMask login:", error);
}
}
// Creating a wallet
async function createWallet() {
if (!shogun.isLoggedIn()) {
console.error("You must log in first!");
return;
}
try {
const walletPlugin = shogun.getPlugin(CorePlugins.Wallet);
if (walletPlugin) {
const wallet = await walletPlugin.createWallet();
console.log("Wallet created:", wallet);
} else {
console.error("Wallet plugin not available");
}
} catch (error) {
console.error("Error while creating wallet:", error);
}
}
// Using reactive data
function setupReactiveUI() {
// Subscribe to user profile changes
shogun.observe("users/current").subscribe((user) => {
document.getElementById("username").textContent = user.name;
document.getElementById("status").className = user.online
? "online"
: "offline";
});
// Handle real-time messages
shogun.match("messages").subscribe((messages) => {
const chatBox = document.getElementById("chat");
chatBox.innerHTML = "";
messages.forEach((msg) => {
const msgEl = document.createElement("div");
msgEl.className = "message";
msgEl.textContent = `${msg.sender}: ${msg.text}`;
chatBox.appendChild(msgEl);
});
});
}
For a complete example, check the examples/browser-example.html file.
The browser version of Shogun Core:
Contributions are welcome! If you would like to contribute to the project, please:
git checkout -b feature/amazing-feature
)git commit -m 'Added amazing feature'
)git push origin feature/amazing-feature
)MIT