Intermediate

 

Intermediate : How to create Non-Fungible token

To continue with this workshop you need to first complete workshop

Create non-fungible token

Create the token type with zero decimal place,

const mint = await createMint(
connection,
wallet,
wallet.publicKey,
wallet.publicKey,
0);

 

then create an account to hold tokens of this new type:

const associatedTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
wallet,
mint,
wallet.publicKey
);

 

Now mint only one token into the account,

await mintTo(
connection,
wallet,
mint,
associatedTokenAccount.address,
wallet,
1
);

 

 

and disable future minting:

let transaction = new Transaction()
.add(createSetAuthorityInstruction(
mint,
wallet.publicKey,
AuthorityType.MintTokens,
null
));

await web3.sendAndConfirmTransaction(connection, transaction, [wallet]);

 

 

Now the 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM account holds the one and only 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z token:

const accountInfo = await getAccount(connection, associatedTokenAccount.address);

console.log(accountInfo.amount);
// 1
const mintInfo = await getMint(
connection,
mint
);

console.log(mintInfo);

 

you will see the log like this

{
address: "7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM",
mintAuthority: "559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z",
supply: 1,
decimals: 0,
isInitialized: true,
freezeAuthority: "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg"
}

 

 

Multisig usage

The main difference in using multisign is specifying the owner as the multisig key, and giving the list of signers when contructing a transaction. Normally you would provide the signer that has authority to run the transaction as the owner, but in the multisig case the owner would be the multisig key.

Multisig accounts can be used for any authority on an SPL Token mint or token account.

Mint account mint authority:createMint(/* ... */, mintAuthority: multisigKey, /* ... */)

Mint account freeze authority:createMint(/* ... */, freezeAuthority: multisigKey, /* ... */)

Token account owner authority:getOrCreateAssociatedTokenAccount(/* ... */, mintAuthority: multisigKey, /* ... */)

Token account close authority:closeAccount(/* ... */, authority: multisigKey, /* ... */)

 

Example: Mint with multisig authority

First create keypairs to act as the multisig signer-set. In reality, these can be any supported signer, like: a Ledger hardware wallet, a keypair file, or a paper wallet. For convenience, generated keypairs will be used in this example.

const signer1 = Keypair.generate();

const signer2 = Keypair.generate();

const signer3 = Keypair.generate();

In order to create the multisig account, the public keys of the signer-set must be collected.

 
console.log(signer1.publicKey.toBase58());

console.log(signer2.publicKey.toBase58());

console.log(signer3.publicKey.toBase58());

here you can see logs:

1BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ

2DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY

3D7ssXHrZJjfpZXsmDf8RwfPxe1BMMMmP1CtmX3WojPmG

 

Now the multisig account can be created with the spl-token create-multisig subcommand. Its first positional argument is the minimum number of signers (M) that must sign a transaction affecting a token/mint account that is controlled by this multisig account. The remaining positional arguments are the public keys of all keypairs allowed (N) to sign for the multisig account. This example will use a “2 of 3” multisig account. That is, two of the three allowed keypairs must sign all transactions.

NOTE: SPL Token Multisig accounts are limited to a signer-set of eleven signers (1 <= N <= 11) and minimum signers must be no more than N (1 <= M <= N)

const multisigKey = await createMultisig(
connection,
payer,
[
signer1.publicKey,
signer2.publicKey,
signer3.publicKey
],
2
);

console.log(`Created 2/3 multisig ${multisigKey.toBase58()}`);
// Created 2/3 multisig 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re

 

 

Next create the token mint and receiving accounts as previously described and set the mint account’s minting authority to the multisig account

const mint = await createMint(
connection,
payer,
multisigKey,
multisigKey,
9
);

const associatedTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
payer,
mint,
signer1.publicKey
);

 

To demonstrate that the mint account is now under control of the multisig account, attempting to mint with one multisig signer fails

try {
await mintTo(
connection,
payer,
mint,
associatedTokenAccount.address,
multisigKey,
1
)
} catch (error) {
console.log(error);
}
// Error: Signature verification failed

 

But repeating with a second multisig signer, succeeds

await mintTo(
connection,
payer,
mint,
associatedTokenAccount.address,
multisigKey,
1,
[
signer1,
signer2
]
)

const mintInfo = await getMint(
connection,
mint
)

console.log(`Minted ${mintInfo.supply} token`);
// Minted 1 token

 

 

Example: Offline signing with multisig

Sometimes online signing is not possible or desireable. Such is the case for example when signers are not in the same geographic location or when they use air-gapped devices not connected to the network. In this case, we use offline signing which combines the previous examples of multisig with offline signing and a nonce account.

This example will use the same mint account, token account, multisig account, and multisig signer-set keypair filenames as the online example, as well as a nonce account that we create here:

const connection = new Connection(
clusterApiUrl('devnet'),
'confirmed',
);

const onlineAccount = Keypair.generate();
const nonceAccount = Keypair.generate();

const minimumAmount = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
);

// Form CreateNonceAccount transaction
const transaction = new Transaction()
.add(
SystemProgram.createNonceAccount({
fromPubkey: onlineAccount.publicKey,
noncePubkey: nonceAccount.publicKey,
authorizedPubkey: onlineAccount.publicKey,
lamports: minimumAmount,
}),
);

await web3.sendAndConfirmTransaction(connection, transaction, [onlineAccount, nonceAccount])

const nonceAccountData = await connection.getNonce(
nonceAccount.publicKey,
'confirmed',
);

console.log(nonceAccountData);
/*
NonceAccount {
authorizedPubkey: '5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE'
nonce: '6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E',
feeCalculator: { lamportsPerSignature: 5000 }
}
*/

 

For the fee-payer and nonce-authority roles, a local hot wallet at 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE will be used.

First a raw transaction is built using the nonceAccountInformation and tokenAccount key. All signers of the transaction are noted as part of the raw transaction. This transaction will be handed to the signers later for signing.

const nonceAccountInfo = await connection.getAccountInfo(
nonceAccount.publicKey,
'confirmed'
);

const nonceAccountFromInfo = web3.NonceAccount.fromAccountData(nonceAccountInfo.data);

console.log(nonceAccountFromInfo);

const nonceInstruction = web3.SystemProgram.nonceAdvance({
authorizedPubkey: onlineAccount.publicKey,
noncePubkey: nonceAccount.publicKey
});

const nonce = nonceAccountFromInfo.nonce;

const mintToTransaction = new web3.Transaction({
feePayer: onlineAccount.publicKey,
nonceInfo: {nonce, nonceInstruction}
})
.add(
createMintToInstruction(
mint,
associatedTokenAccount.address,
multisigkey,
1,
[
signer1,
onlineAccount
],
TOKEN_PROGRAM_ID
)
);

 

Next each offline signer will take the transaction buffer and sign it with their corresponding key.

let mintToTransactionBuffer = mintToTransaction.serializeMessage();

let onlineSIgnature = nacl.sign.detached(mintToTransactionBuffer, onlineAccount.secretKey);
mintToTransaction.addSignature(onlineAccount.publicKey, onlineSIgnature);

// Handed to offline signer for signature
let offlineSignature = nacl.sign.detached(mintToTransactionBuffer, signer1.secretKey);
mintToTransaction.addSignature(signer1.publicKey, offlineSignature);

let rawMintToTransaction = mintToTransaction.serialize();

Finally, the hot wallet will take the transaction, serialize it, and broadcast it to the network.

// Send to online signer for broadcast to network
await web3.sendAndConfirmRawTransaction(connection, rawMintToTransaction);