From 3691b14a0163c6a212e4ed1963f5030660c6cd37 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 29 Aug 2020 01:04:37 +0800 Subject: [PATCH] Update token js bindings --- docs/src/token.md | 2 +- token/js/cli/main.js | 20 +- token/js/cli/token-test.js | 331 +++++++++++-------------- token/js/client/token.js | 484 +++++++++++++++++++++++-------------- token/js/module.d.ts | 72 ++++-- token/js/module.flow.js | 74 ++++-- 6 files changed, 552 insertions(+), 431 deletions(-) diff --git a/docs/src/token.md b/docs/src/token.md index 1b3735a2..1cd71815 100644 --- a/docs/src/token.md +++ b/docs/src/token.md @@ -188,7 +188,7 @@ The owner of the source Account must be present as a signer in the `Transfer` instruction. An Account's owner may transfer ownership of an account to another using the -`SetOwner` instruction. +`SetAuthority` instruction. It's important to note that the `InitializeAccount` instruction does not require the Solana account being initialized also be a signer. The `InitializeAccount` diff --git a/token/js/cli/main.js b/token/js/cli/main.js index ee36e4f5..5067c6a2 100644 --- a/token/js/cli/main.js +++ b/token/js/cli/main.js @@ -12,11 +12,11 @@ import { approveRevoke, invalidApprove, failOnApproveOverspend, - setOwner, + setAuthority, mintTo, multisig, burn, - failOnCloseAccount, + closeAccount, nativeToken, } from './token-test'; @@ -27,6 +27,8 @@ async function main() { await createMint(); console.log('Run test: createAccount'); await createAccount(); + console.log('Run test: mintTo'); + await mintTo(); console.log('Run test: transfer'); await transfer(); console.log('Run test: approveRevoke'); @@ -35,16 +37,14 @@ async function main() { await invalidApprove(); console.log('Run test: failOnApproveOverspend'); await failOnApproveOverspend(); - console.log('Run test: setOwner'); - await setOwner(); - console.log('Run test: mintTo'); - await mintTo(); - console.log('Run test: multisig'); - await multisig(); + console.log('Run test: setAuthority'); + await setAuthority(); console.log('Run test: burn'); await burn(); - console.log('Run test: failOnCloseAccount'); - await failOnCloseAccount(); + console.log('Run test: closeAccount'); + await closeAccount(); + console.log('Run test: multisig'); + await multisig(); console.log('Run test: nativeToken'); await nativeToken(); console.log('Success\n'); diff --git a/token/js/cli/token-test.js b/token/js/cli/token-test.js index c78adeba..94498703 100644 --- a/token/js/cli/token-test.js +++ b/token/js/cli/token-test.js @@ -4,7 +4,7 @@ import fs from 'mz/fs'; import {Account, Connection, BpfLoader, PublicKey} from '@solana/web3.js'; import semver from 'semver'; -import {Token, u64} from '../client/token'; +import {Token} from '../client/token'; import {url} from '../url'; import {newAccountWithLamports} from '../client/util/new-account-with-lamports'; import {sleep} from '../client/util/sleep'; @@ -13,20 +13,14 @@ import {Store} from '../client/util/store'; // Loaded token program's program id let programId: PublicKey; -// A token created by the next test and used by all subsequent tests -let mintOwner: Account; +// Accounts setup in createMint and used by all subsequent tests +let testMintAuthority: Account; let testToken: Token; -// Initial token account + +// Accounts setup in createAccount and used by all subsequent tests let testAccountOwner: Account; let testAccount: PublicKey; -// A mintable token used by multiple tests -let mintableOwner: Account; -let testMintableToken: Token; -// Initial token account -let testMintableAccountOwner: Account; -let testMintableAccount: PublicKey; - function assert(condition, message) { if (!condition) { console.log(Error().stack + ':token-test.js'); @@ -116,72 +110,88 @@ export async function createMint(): Promise { connection, 100000000000 /* wag */, ); - mintOwner = new Account(); - testAccountOwner = new Account(); - [testToken, testAccount] = await Token.createMint( + testMintAuthority = new Account(); + testToken = await Token.createMint( connection, payer, - mintOwner.publicKey, - testAccountOwner.publicKey, - new u64(10000), + testMintAuthority.publicKey, + null, 2, programId, - false, ); const mintInfo = await testToken.getMintInfo(); - assert(mintInfo.decimals == 2); - assert(mintInfo.owner == null); - - const accountInfo = await testToken.getAccountInfo(testAccount); - assert(accountInfo.mint.equals(testToken.publicKey)); - assert(accountInfo.owner.equals(testAccountOwner.publicKey)); - assert(accountInfo.amount.toNumber() == 10000); - assert(accountInfo.delegate == null); - assert(accountInfo.delegatedAmount.toNumber() == 0); + if (mintInfo.mintAuthority !== null) { + assert(mintInfo.mintAuthority.equals(testMintAuthority.publicKey)); + } else { + assert(mintInfo.mintAuthority !== null); + } + assert(mintInfo.supply.toNumber() === 0); + assert(mintInfo.decimals === 2); + assert(mintInfo.isInitialized === true); + assert(mintInfo.freezeAuthority === null); } export async function createAccount(): Promise { - const destOwner = new Account(); - const account = await testToken.createAccount(destOwner.publicKey); - const accountInfo = await testToken.getAccountInfo(account); + testAccountOwner = new Account(); + testAccount = await testToken.createAccount(testAccountOwner.publicKey); + const accountInfo = await testToken.getAccountInfo(testAccount); assert(accountInfo.mint.equals(testToken.publicKey)); - assert(accountInfo.owner.equals(destOwner.publicKey)); - assert(accountInfo.amount.toNumber() == 0); - assert(accountInfo.delegate == null); + assert(accountInfo.owner.equals(testAccountOwner.publicKey)); + assert(accountInfo.amount.toNumber() === 0); + assert(accountInfo.delegate === null); + assert(accountInfo.delegatedAmount.toNumber() === 0); + assert(accountInfo.isInitialized === true); + assert(accountInfo.isFrozen === false); + assert(accountInfo.isNative === false); + assert(accountInfo.rentExemptReserve === null); + assert(accountInfo.closeAuthority === null); +} + +export async function mintTo(): Promise { + await testToken.mintTo(testAccount, testMintAuthority, [], 1000); + + const mintInfo = await testToken.getMintInfo(); + assert(mintInfo.supply.toNumber() === 1000); + + const accountInfo = await testToken.getAccountInfo(testAccount); + assert(accountInfo.amount.toNumber() === 1000); } export async function transfer(): Promise { const destOwner = new Account(); const dest = await testToken.createAccount(destOwner.publicKey); - await testToken.transfer(testAccount, dest, testAccountOwner, [], 123); - await sleep(500); + await testToken.transfer(testAccount, dest, testAccountOwner, [], 100); + + const mintInfo = await testToken.getMintInfo(); + assert(mintInfo.supply.toNumber() === 1000); let destAccountInfo = await testToken.getAccountInfo(dest); - assert(destAccountInfo.amount.toNumber() == 123); + assert(destAccountInfo.amount.toNumber() === 100); + + let testAccountInfo = await testToken.getAccountInfo(testAccount); + assert(testAccountInfo.amount.toNumber() === 900); } export async function approveRevoke(): Promise { - if (programId == null) { - console.log('test skipped, requires "load token program" to succeed'); - return; - } + const delegate = new Account().publicKey; + + await testToken.approve(testAccount, delegate, testAccountOwner, [], 42); - const delegate = new PublicKey(); - await testToken.approve(testAccount, delegate, testAccountOwner, [], 456); let testAccountInfo = await testToken.getAccountInfo(testAccount); - assert(testAccountInfo.delegatedAmount.toNumber() == 456); + assert(testAccountInfo.delegatedAmount.toNumber() === 42); if (testAccountInfo.delegate === null) { - throw new Error('deleage should not be null'); + throw new Error('delegate should not be null'); } else { assert(testAccountInfo.delegate.equals(delegate)); } await testToken.revoke(testAccount, testAccountOwner, []); + testAccountInfo = await testToken.getAccountInfo(testAccount); - assert(testAccountInfo.delegatedAmount.toNumber() == 0); - if (testAccountInfo.delegate != null) { + assert(testAccountInfo.delegatedAmount.toNumber() === 0); + if (testAccountInfo.delegate !== null) { throw new Error('delegate should be null'); } } @@ -233,75 +243,31 @@ export async function failOnApproveOverspend(): Promise { assert(didThrow(testToken.transfer, [account1, account2, delegate, [], 1])); } -export async function setOwner(): Promise { - const owner = new Account(); +export async function setAuthority(): Promise { const newOwner = new Account(); - const owned = await testToken.createAccount(owner.publicKey); - - await testToken.setOwner(owned, newOwner.publicKey, owner, []); - assert(didThrow(testToken.setOwner, [owned, newOwner.publicKey, owner, []])); - await testToken.setOwner(owned, owner.publicKey, newOwner, []); -} - -export async function mintTo(): Promise { - const connection = await getConnection(); - const payer = await newAccountWithLamports( - connection, - 100000000000 /* wag */, + await testToken.setAuthority( + testAccount, + newOwner.publicKey, + 'AccountOwner', + testAccountOwner, + [], ); - mintableOwner = new Account(); - testMintableAccountOwner = new Account(); - [testMintableToken, testMintableAccount] = await Token.createMint( - connection, - payer, - mintableOwner.publicKey, - testMintableAccountOwner.publicKey, - new u64(10000), - 2, - programId, - true, + assert( + didThrow(testToken.setAuthority, [ + testAccountOwner, + newOwner.publicKey, + 'AccountOwner', + testAccountOwner, + [], + ]), ); - - { - const mintInfo = await testMintableToken.getMintInfo(); - assert(mintInfo.decimals == 2); - if (mintInfo.owner === null) { - throw new Error('owner should not be null'); - } else { - assert(mintInfo.owner.equals(mintableOwner.publicKey)); - } - - const accountInfo = await testMintableToken.getAccountInfo( - testMintableAccount, - ); - assert(accountInfo.mint.equals(testMintableToken.publicKey)); - assert(accountInfo.owner.equals(testMintableAccountOwner.publicKey)); - assert(accountInfo.amount.toNumber() == 10000); - assert(accountInfo.delegate == null); - assert(accountInfo.delegatedAmount.toNumber() == 0); - } - - const dest = await testMintableToken.createAccount( - testMintableAccountOwner.publicKey, + await testToken.setAuthority( + testAccount, + testAccountOwner.publicKey, + 'AccountOwner', + newOwner, + [], ); - await testMintableToken.mintTo(dest, mintableOwner, [], 42); - - { - const mintInfo = await testMintableToken.getMintInfo(); - assert(mintInfo.decimals == 2); - if (mintInfo.owner === null) { - throw new Error('owner should not be null'); - } else { - assert(mintInfo.owner.equals(mintableOwner.publicKey)); - } - - const accountInfo = await testMintableToken.getAccountInfo(dest); - assert(accountInfo.mint.equals(testMintableToken.publicKey)); - assert(accountInfo.owner.equals(testMintableAccountOwner.publicKey)); - assert(accountInfo.amount.toNumber() == 42); - assert(accountInfo.delegate == null); - assert(accountInfo.delegatedAmount.toNumber() == 0); - } } export async function burn(): Promise { @@ -309,12 +275,65 @@ export async function burn(): Promise { const amount = accountInfo.amount.toNumber(); await testToken.burn(testAccount, testAccountOwner, [], 1); - await sleep(500); accountInfo = await testToken.getAccountInfo(testAccount); assert(accountInfo.amount.toNumber() == amount - 1); } +export async function closeAccount(): Promise { + const closeAuthority = new Account(); + + await testToken.setAuthority( + testAccount, + closeAuthority.publicKey, + 'CloseAccount', + testAccountOwner, + [], + ); + let accountInfo = await testToken.getAccountInfo(testAccount); + if (accountInfo.closeAuthority === null) { + assert(accountInfo.closeAuthority !== null); + } else { + assert(accountInfo.closeAuthority.equals(closeAuthority.publicKey)); + } + + const dest = await testToken.createAccount(new Account().publicKey); + const remaining = accountInfo.amount.toNumber(); + + // Check that accounts with non-zero token balance cannot be closed + assert( + didThrow(testToken.closeAccount, [testAccount, dest, closeAuthority, []]), + ); + + const connection = await getConnection(); + let tokenRentExemptAmount; + let info = await connection.getAccountInfo(testAccount); + if (info != null) { + tokenRentExemptAmount = info.lamports; + } else { + throw new Error('Account not found'); + } + + // Transfer away all tokens + await testToken.transfer(testAccount, dest, testAccountOwner, [], remaining); + + // Close for real + await testToken.closeAccount(testAccount, dest, closeAuthority, []); + + info = await connection.getAccountInfo(testAccount); + assert(info === null); + + let destInfo = await connection.getAccountInfo(dest); + if (destInfo !== null) { + assert(destInfo.lamports === 2 * tokenRentExemptAmount); + } else { + throw new Error('Account not found'); + } + + let destAccountInfo = await testToken.getAccountInfo(dest); + assert(destAccountInfo.amount.toNumber() === remaining); +} + export async function multisig(): Promise { const m = 2; const n = 5; @@ -326,7 +345,6 @@ export async function multisig(): Promise { let signerPublicKeys = []; signerAccounts.forEach(account => signerPublicKeys.push(account.publicKey)); const multisig = await testToken.createMultisig(m, signerPublicKeys); - const multisigInfo = await testToken.getMultisigInfo(multisig); assert(multisigInfo.m === m); assert(multisigInfo.n === n); @@ -338,13 +356,8 @@ export async function multisig(): Promise { const multisigOwnedAccount = await testToken.createAccount(multisig); const finalDest = await testToken.createAccount(multisig); - await testToken.transfer( - testAccount, - multisigOwnedAccount, - testAccountOwner, - [], - 2, - ); + + await testToken.mintTo(multisigOwnedAccount, testMintAuthority, [], 1000); // Transfer via multisig await testToken.transfer( @@ -376,49 +389,13 @@ export async function multisig(): Promise { } } - // MintTo via multisig - { - let accountInfo = await testMintableToken.getAccountInfo( - testMintableAccount, - ); - const initialAmount = accountInfo.amount.toNumber(); - await testMintableToken.setOwner( - testMintableToken.publicKey, - multisig, - mintableOwner, - [], - ); - await testMintableToken.mintTo( - testMintableAccount, - multisig, - signerAccounts, - 42, - ); - accountInfo = await testMintableToken.getAccountInfo(testMintableAccount); - assert(accountInfo.amount.toNumber() == initialAmount + 42); - } - - // SetOwner of mint via multisig - { - await testMintableToken.setOwner( - testMintableToken.publicKey, - mintableOwner.publicKey, - multisig, - signerAccounts, - ); - const mintInfo = await testMintableToken.getMintInfo(); - assert(mintInfo.owner != null); - if (mintInfo.owner != null) { - assert(mintInfo.owner.equals(mintableOwner.publicKey)); - } - } - - // SetOwner of account via multisig + // SetAuthority of account via multisig { const newOwner = new PublicKey(); - await testToken.setOwner( + await testToken.setAuthority( multisigOwnedAccount, newOwner, + 'AccountOwner', multisig, signerAccounts, ); @@ -427,45 +404,11 @@ export async function multisig(): Promise { } } -export async function failOnCloseAccount(): Promise { - const connection = await getConnection(); - const owner = new Account(); - const close = await testToken.createAccount(owner.publicKey); - - let close_balance; - let info = await connection.getAccountInfo(close); - if (info != null) { - close_balance = info.lamports; - } else { - throw new Error('Account not found'); - } - - // Initialize destination account to isolate source of failure - const balanceNeeded = await connection.getMinimumBalanceForRentExemption(0); - const dest = await newAccountWithLamports(connection, balanceNeeded); - - info = await connection.getAccountInfo(dest.publicKey); - if (info != null) { - assert(info.lamports == balanceNeeded); - } else { - throw new Error('Account not found'); - } - - assert(didThrow(testToken.closeAccount, [close, dest.publicKey, owner, []])); - - info = await connection.getAccountInfo(close); - if (info != null) { - assert(info.lamports == close_balance); - } else { - throw new Error('Account not found'); - } -} - export async function nativeToken(): Promise { const connection = await getConnection(); const mintPublicKey = new PublicKey( - 'So11111111111111111111111111111111111111111', + 'So11111111111111111111111111111111111111112', ); const payer = await newAccountWithLamports( connection, diff --git a/token/js/client/token.js b/token/js/client/token.js index b4d544d4..ebd20d26 100644 --- a/token/js/client/token.js +++ b/token/js/client/token.js @@ -11,6 +11,7 @@ import { SystemProgram, Transaction, TransactionInstruction, + SYSVAR_RENT_PUBKEY, } from '@solana/web3.js'; import type {Connection, TransactionSignature} from '@solana/web3.js'; @@ -56,14 +57,34 @@ function isAccount(accountOrPublicKey: any): boolean { return 'publicKey' in accountOrPublicKey; } +type AuthorityType = + | 'MintTokens' + | 'FreezeAccount' + | 'AccountOwner' + | 'CloseAccount'; + +const AuthorityTypeCodes = { + MintTokens: 0, + FreezeAccount: 1, + AccountOwner: 2, + CloseAccount: 3, +}; + /** * Information about the mint */ type MintInfo = {| /** - * Owner of the mint, given authority to mint new tokens + * Optional authority used to mint new tokens. The mint authority may only be provided during + * mint creation. If no mint authority is present then the mint has a fixed supply and no + * further tokens may be minted. */ - owner: null | PublicKey, + mintAuthority: null | PublicKey, + + /** + * Total supply of tokens + */ + supply: u64, /** * Number of base 10 digits to the right of the decimal place @@ -73,14 +94,22 @@ type MintInfo = {| /** * Is this mint initialized */ - initialized: boolean, + isInitialized: boolean, + + /** + * Optional authority to freeze token accounts + */ + freezeAuthority: null | PublicKey, |}; const MintLayout = BufferLayout.struct([ - BufferLayout.u32('option'), - Layout.publicKey('owner'), + BufferLayout.u32('mintAuthorityOption'), + Layout.publicKey('mintAuthority'), + Layout.uint64('supply'), BufferLayout.u8('decimals'), - BufferLayout.u8('is_initialized'), + BufferLayout.u8('isInitialized'), + BufferLayout.u32('freezeAuthorityOption'), + Layout.publicKey('freezeAuthority'), BufferLayout.u16('padding'), ]); @@ -118,10 +147,27 @@ type AccountInfo = {| */ isInitialized: boolean, + /** + * Is this account frozen + */ + isFrozen: boolean, + /** * Is this a native token account */ isNative: boolean, + + /** + * If this account is a native token, it must be rent-exempt. This + * value logs the rent-exempt reserve which must remain in the balance + * until the account is closed. + */ + rentExemptReserve: null | u64, + + /** + * Optional authority to close the account + */ + closeAuthority: null | PublicKey, |}; /** @@ -131,12 +177,15 @@ const AccountLayout = BufferLayout.struct([ Layout.publicKey('mint'), Layout.publicKey('owner'), Layout.uint64('amount'), - BufferLayout.u32('option'), + BufferLayout.u32('delegateOption'), Layout.publicKey('delegate'), - BufferLayout.u8('is_initialized'), - BufferLayout.u8('is_native'), - BufferLayout.u16('padding'), + BufferLayout.u8('state'), + BufferLayout.u32('isNativeOption'), + Layout.uint64('isNative'), Layout.uint64('delegatedAmount'), + BufferLayout.u32('closeAuthorityOption'), + Layout.publicKey('closeAuthority'), + BufferLayout.blob(11, 'padding'), ]); /** @@ -195,8 +244,6 @@ const MultisigLayout = BufferLayout.struct([ Layout.publicKey('signer11'), ]); -type TokenAndPublicKey = [Token, PublicKey]; // This type exists to workaround an esdoc parse error - /** * An ERC20-like Token */ @@ -289,14 +336,11 @@ export class Token { static async createMint( connection: Connection, payer: Account, - mintOwner: PublicKey, - accountOwner: PublicKey, - supply: u64, + mintAuthority: PublicKey, + freezeAuthority: PublicKey | null, decimals: number, programId: PublicKey, - is_owned: boolean = false, - ): Promise { - let transaction; + ): Promise { const mintAccount = new Account(); const token = new Token( connection, @@ -304,13 +348,13 @@ export class Token { programId, payer, ); - const initialAccountPublicKey = await token.createAccount(accountOwner); // Allocate memory for the account const balanceNeeded = await Token.getMinBalanceRentForExemptMint( connection, ); - transaction = SystemProgram.createAccount({ + + const transaction = SystemProgram.createAccount({ fromPubkey: payer.publicKey, newAccountPubkey: mintAccount.publicKey, lamports: balanceNeeded, @@ -318,42 +362,15 @@ export class Token { programId, }); - // Create the mint - let keys = [ - {pubkey: mintAccount.publicKey, isSigner: false, isWritable: true}, - ]; - if (supply.cmp(0) != 0) { - keys.push({ - pubkey: initialAccountPublicKey, - isSigner: false, - isWritable: true, - }); - } - if (is_owned) { - keys.push({pubkey: mintOwner, isSigner: false, isWritable: false}); - } - const commandDataLayout = BufferLayout.struct([ - BufferLayout.u8('instruction'), - Layout.uint64('supply'), - BufferLayout.u8('decimals'), - ]); - let data = Buffer.alloc(1024); - { - const encodeLength = commandDataLayout.encode( - { - instruction: 0, // InitializeMint instruction - supply: supply.toBuffer(), - decimals, - }, - data, - ); - data = data.slice(0, encodeLength); - } - transaction.add({ - keys, - programId, - data, - }); + transaction.add( + Token.createInitMintInstruction( + programId, + mintAccount.publicKey, + decimals, + mintAuthority, + freezeAuthority, + ), + ); // Send the two instructions await sendAndConfirmTransaction( @@ -364,7 +381,7 @@ export class Token { mintAccount, ); - return [token, initialAccountPublicKey]; + return token; } /** @@ -376,40 +393,29 @@ export class Token { * @return Public key of the new empty account */ async createAccount(owner: PublicKey): Promise { - const mintAccount = new Account(); - let transaction; - // Allocate memory for the account const balanceNeeded = await Token.getMinBalanceRentForExemptAccount( this.connection, ); - transaction = SystemProgram.createAccount({ + + const newAccount = new Account(); + const transaction = SystemProgram.createAccount({ fromPubkey: this.payer.publicKey, - newAccountPubkey: mintAccount.publicKey, + newAccountPubkey: newAccount.publicKey, lamports: balanceNeeded, space: AccountLayout.span, programId: this.programId, }); - // create the new account - const keys = [ - {pubkey: mintAccount.publicKey, isSigner: false, isWritable: true}, - {pubkey: this.publicKey, isSigner: false, isWritable: false}, - {pubkey: owner, isSigner: false, isWritable: false}, - ]; - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: 1, // InitializeAccount instruction - }, - data, + const mintPublicKey = this.publicKey; + transaction.add( + Token.createInitAccountInstruction( + this.programId, + mintPublicKey, + newAccount.publicKey, + owner, + ), ); - transaction.add({ - keys, - programId: this.programId, - data, - }); // Send the two instructions await sendAndConfirmTransaction( @@ -417,10 +423,10 @@ export class Token { this.connection, transaction, this.payer, - mintAccount, + newAccount, ); - return mintAccount.publicKey; + return newAccount.publicKey; } /** @@ -428,7 +434,6 @@ export class Token { * * This account may then be used for multisignature verification * - * @param owner User account that will own the multsig account * @return Public key of the new multisig account */ async createMultisig( @@ -453,6 +458,7 @@ export class Token { // create the new account let keys = [ {pubkey: multisigAccount.publicKey, isSigner: false, isWritable: true}, + {pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false}, ]; signers.forEach(signer => keys.push({pubkey: signer, isSigner: false, isWritable: false}), @@ -464,7 +470,7 @@ export class Token { const data = Buffer.alloc(dataLayout.span); dataLayout.encode( { - instruction: 2, // InitializeM, ): Promise { - let ownerPublicKey; + let currentAuthorityPublicKey: PublicKey; let signers; - if (isAccount(owner)) { - ownerPublicKey = owner.publicKey; - signers = [owner]; + if (isAccount(currentAuthority)) { + currentAuthorityPublicKey = currentAuthority.publicKey; + signers = [currentAuthority]; } else { - ownerPublicKey = owner; + currentAuthorityPublicKey = currentAuthority; signers = multiSigners; } await sendAndConfirmTransaction( - 'SetOwner', + 'SetAuthority', this.connection, new Transaction().add( - Token.createSetOwnerInstruction( + Token.createSetAuthorityInstruction( this.programId, - owned, - newOwner, - ownerPublicKey, + account, + newAuthority, + authorityType, + currentAuthorityPublicKey, multiSigners, ), ), @@ -754,7 +789,6 @@ export class Token { /** * Mint new tokens * - * @param mint Public key of the mint * @param dest Public key of the account to mint to * @param authority Owner of the mint * @param multiSigners Signing accounts if `authority` is a multiSig @@ -797,23 +831,23 @@ export class Token { * Burn tokens * * @param account Account to burn tokens from - * @param authority Public key account owner + * @param owner Public key account owner * @param multiSigners Signing accounts if `authority` is a multiSig - * @param amount ammount to burn + * @param amount amount to burn */ async burn( account: PublicKey, - authority: any, + owner: any, multiSigners: Array, amount: number, ): Promise { let ownerPublicKey; let signers; - if (isAccount(authority)) { - ownerPublicKey = authority.publicKey; - signers = [authority]; + if (isAccount(owner)) { + ownerPublicKey = owner.publicKey; + signers = [owner]; } else { - ownerPublicKey = authority; + ownerPublicKey = owner; signers = multiSigners; } await sendAndConfirmTransaction( @@ -822,6 +856,7 @@ export class Token { new Transaction().add( Token.createBurnInstruction( this.programId, + this.publicKey, account, ownerPublicKey, multiSigners, @@ -834,25 +869,26 @@ export class Token { } /** - * Burn account + * Close account * - * @param account Account to burn - * @param authority account owner + * @param account Account to close + * @param dest Account to receive the remaining balance of the closed account + * @param authority Account which is allowed to close the account * @param multiSigners Signing accounts if `owner` is a multiSig */ async closeAccount( account: PublicKey, dest: PublicKey, - owner: any, + authority: any, multiSigners: Array, ): Promise { - let ownerPublicKey; + let authorityPublicKey; let signers; - if (isAccount(owner)) { - ownerPublicKey = owner.publicKey; - signers = [owner]; + if (isAccount(authority)) { + authorityPublicKey = authority.publicKey; + signers = [authority]; } else { - ownerPublicKey = owner; + authorityPublicKey = authority; signers = multiSigners; } await sendAndConfirmTransaction( @@ -863,7 +899,7 @@ export class Token { this.programId, account, dest, - ownerPublicKey, + authorityPublicKey, multiSigners, ), ), @@ -872,12 +908,96 @@ export class Token { ); } + /** + * Construct an init mint instruction + * + * @param programId SPL Token program account + * @param mint Token mint account + * @param token New token account + * @param owner Owner of the new token account + */ + static createInitMintInstruction( + programId: PublicKey, + mint: PublicKey, + decimals: number, + mintAuthority: PublicKey, + freezeAuthority: PublicKey | null, + ): TransactionInstruction { + let keys = [ + {pubkey: mint, isSigner: false, isWritable: true}, + {pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false}, + ]; + const commandDataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + BufferLayout.u8('decimals'), + Layout.publicKey('mintAuthority'), + BufferLayout.u8('option'), + Layout.publicKey('freezeAuthority'), + ]); + let data = Buffer.alloc(1024); + { + const encodeLength = commandDataLayout.encode( + { + instruction: 0, // InitializeMint instruction + decimals, + mintAuthority: mintAuthority.toBuffer(), + option: freezeAuthority === null ? 0 : 1, + freezeAuthority: (freezeAuthority || new PublicKey()).toBuffer(), + }, + data, + ); + data = data.slice(0, encodeLength); + } + + return new TransactionInstruction({ + keys, + programId, + data, + }); + } + + /** + * Construct an init account instruction + * + * @param programId SPL Token program account + * @param mint Token mint account + * @param account New account + * @param owner Owner of the new account + */ + static createInitAccountInstruction( + programId: PublicKey, + mint: PublicKey, + account: PublicKey, + owner: PublicKey, + ): TransactionInstruction { + const keys = [ + {pubkey: account, isSigner: false, isWritable: true}, + {pubkey: mint, isSigner: false, isWritable: false}, + {pubkey: owner, isSigner: false, isWritable: false}, + {pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false}, + ]; + const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: 1, // InitializeAccount instruction + }, + data, + ); + + return new TransactionInstruction({ + keys, + programId, + data, + }); + } + /** * Construct a Transfer instruction * * @param source Source account * @param destination Destination account - * @param authority Owner of the source account + * @param owner Owner of the source account * @param multiSigners Signing accounts if `authority` is a multiSig * @param amount Number of tokens to transfer */ @@ -885,7 +1005,7 @@ export class Token { programId: PublicKey, source: PublicKey, destination: PublicKey, - authority: any, + owner: PublicKey, multiSigners: Array, amount: number | u64, ): TransactionInstruction { @@ -907,14 +1027,14 @@ export class Token { {pubkey: source, isSigner: false, isWritable: true}, {pubkey: destination, isSigner: false, isWritable: true}, ]; - if (isAccount(authority)) { + if (multiSigners.length === 0) { keys.push({ - pubkey: authority.publicKey, + pubkey: owner, isSigner: true, isWritable: false, }); } else { - keys.push({pubkey: authority, isSigner: false, isWritable: false}); + keys.push({pubkey: owner, isSigner: false, isWritable: false}); multiSigners.forEach(signer => keys.push({ pubkey: signer.publicKey, @@ -943,7 +1063,7 @@ export class Token { programId: PublicKey, account: PublicKey, delegate: PublicKey, - owner: any, + owner: PublicKey, multiSigners: Array, amount: number | u64, ): TransactionInstruction { @@ -965,8 +1085,8 @@ export class Token { {pubkey: account, isSigner: false, isWritable: true}, {pubkey: delegate, isSigner: false, isWritable: false}, ]; - if (isAccount(owner)) { - keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false}); + if (multiSigners.length === 0) { + keys.push({pubkey: owner, isSigner: true, isWritable: false}); } else { keys.push({pubkey: owner, isSigner: false, isWritable: false}); multiSigners.forEach(signer => @@ -997,7 +1117,7 @@ export class Token { static createRevokeInstruction( programId: PublicKey, account: PublicKey, - owner: any, + owner: PublicKey, multiSigners: Array, ): TransactionInstruction { const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); @@ -1011,8 +1131,8 @@ export class Token { ); let keys = [{pubkey: account, isSigner: false, isWritable: true}]; - if (isAccount(owner)) { - keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false}); + if (multiSigners.length === 0) { + keys.push({pubkey: owner, isSigner: true, isWritable: false}); } else { keys.push({pubkey: owner, isSigner: false, isWritable: false}); multiSigners.forEach(signer => @@ -1032,38 +1152,48 @@ export class Token { } /** - * Construct a SetOwner instruction + * Construct a SetAuthority instruction * * @param account Public key of the account - * @param newOwner New owner of the account - * @param owner Owner of the account - * @param multiSigners Signing accounts if `owner` is a multiSig + * @param newAuthority New authority of the account + * @param authorityType Type of authority to set + * @param currentAuthority Current authority of the specified type + * @param multiSigners Signing accounts if `currentAuthority` is a multiSig */ - static createSetOwnerInstruction( + static createSetAuthorityInstruction( programId: PublicKey, - owned: PublicKey, - newOwner: PublicKey, - owner: any, + account: PublicKey, + newAuthority: PublicKey | null, + authorityType: AuthorityType, + currentAuthority: PublicKey, multiSigners: Array, ): TransactionInstruction { - const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); + const commandDataLayout = BufferLayout.struct([ + BufferLayout.u8('instruction'), + BufferLayout.u8('authorityType'), + BufferLayout.u8('option'), + Layout.publicKey('newAuthority'), + ]); - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: 6, // SetOwner instruction - }, - data, - ); + let data = Buffer.alloc(1024); + { + const encodeLength = commandDataLayout.encode( + { + instruction: 6, // SetAuthority instruction + authorityType: AuthorityTypeCodes[authorityType], + option: newAuthority === null ? 0 : 1, + newAuthority: (newAuthority || new PublicKey()).toBuffer(), + }, + data, + ); + data = data.slice(0, encodeLength); + } - let keys = [ - {pubkey: owned, isSigner: false, isWritable: true}, - {pubkey: newOwner, isSigner: false, isWritable: false}, - ]; - if (isAccount(owner)) { - keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false}); + let keys = [{pubkey: account, isSigner: false, isWritable: true}]; + if (multiSigners.length === 0) { + keys.push({pubkey: currentAuthority, isSigner: true, isWritable: false}); } else { - keys.push({pubkey: owner, isSigner: false, isWritable: false}); + keys.push({pubkey: currentAuthority, isSigner: false, isWritable: false}); multiSigners.forEach(signer => keys.push({ pubkey: signer.publicKey, @@ -1084,16 +1214,15 @@ export class Token { * Construct a MintTo instruction * * @param dest Public key of the account to mint to - * @param authority Owner of the mint + * @param authority The mint authority * @param multiSigners Signing accounts if `authority` is a multiSig - * @param amount amount to mint */ static createMintToInstruction( programId: PublicKey, mint: PublicKey, dest: PublicKey, - authority: any, + authority: PublicKey, multiSigners: Array, amount: number, ): TransactionInstruction { @@ -1115,9 +1244,9 @@ export class Token { {pubkey: mint, isSigner: false, isWritable: true}, {pubkey: dest, isSigner: false, isWritable: true}, ]; - if (isAccount(authority)) { + if (multiSigners.length === 0) { keys.push({ - pubkey: authority.publicKey, + pubkey: authority, isSigner: true, isWritable: false, }); @@ -1142,15 +1271,17 @@ export class Token { /** * Construct a Burn instruction * + * @param mint Mint for the account * @param account Account to burn tokens from - * @param authority Public key account owner + * @param owner Owner of the account * @param multiSigners Signing accounts if `authority` is a multiSig - * @param amount ammount to burn + * @param amount amount to burn */ static createBurnInstruction( programId: PublicKey, + mint: PublicKey, account: PublicKey, - authority: any, + owner: PublicKey, multiSigners: Array, amount: number, ): TransactionInstruction { @@ -1168,15 +1299,18 @@ export class Token { data, ); - let keys = [{pubkey: account, isSigner: false, isWritable: true}]; - if (isAccount(authority)) { + let keys = [ + {pubkey: account, isSigner: false, isWritable: true}, + {pubkey: mint, isSigner: false, isWritable: true}, + ]; + if (multiSigners.length === 0) { keys.push({ - pubkey: authority.publicKey, + pubkey: owner, isSigner: true, isWritable: false, }); } else { - keys.push({pubkey: authority, isSigner: false, isWritable: false}); + keys.push({pubkey: owner, isSigner: false, isWritable: false}); multiSigners.forEach(signer => keys.push({ pubkey: signer.publicKey, @@ -1204,7 +1338,7 @@ export class Token { programId: PublicKey, account: PublicKey, dest: PublicKey, - owner: any, + owner: PublicKey, multiSigners: Array, ): TransactionInstruction { const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]); @@ -1220,8 +1354,8 @@ export class Token { {pubkey: account, isSigner: false, isWritable: true}, {pubkey: dest, isSigner: false, isWritable: true}, ]; - if (isAccount(owner)) { - keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false}); + if (multiSigners.length === 0) { + keys.push({pubkey: owner, isSigner: true, isWritable: false}); } else { keys.push({pubkey: owner, isSigner: false, isWritable: false}); multiSigners.forEach(signer => diff --git a/token/js/module.d.ts b/token/js/module.d.ts index 4295bad8..1bc8d7f6 100644 --- a/token/js/module.d.ts +++ b/token/js/module.d.ts @@ -8,10 +8,17 @@ declare module '@solana/spl-token' { toBuffer(): Buffer; static fromBuffer(buffer: Buffer): u64; } + export type AuthorityType = + | 'MintTokens' + | 'FreezeAccount' + | 'AccountOwner' + | 'CloseAccount'; export type MintInfo = { - owner: null | PublicKey, + mintAuthority: null | PublicKey, + supply: u64, decimals: number, - initialized: boolean, + isInitialized: boolean, + freezeAuthority: null | PublicKey, }; export type AccountInfo = { mint: PublicKey, @@ -20,7 +27,10 @@ declare module '@solana/spl-token' { delegate: null | PublicKey, delegatedAmount: u64, isInitialized: boolean, + isFrozen: boolean, isNative: boolean, + rentExemptReserve: null | u64, + closeAuthority: null | PublicKey, }; export type MultisigInfo = { m: number, @@ -38,7 +48,6 @@ declare module '@solana/spl-token' { signer10: PublicKey, signer11: PublicKey, }; - export type TokenAndPublicKey = [Token, PublicKey]; export class Token { constructor( connection: Connection, @@ -49,13 +58,11 @@ declare module '@solana/spl-token' { static createMint( connection: Connection, payer: Account, - mintOwner: PublicKey, - accountOwner: PublicKey, - supply: u64, + mintAuthority: PublicKey, + freezeAuthority: PublicKey | null, decimals: number, programId: PublicKey, - is_owned: boolean, - ): Promise; + ): Promise; static getAccount(connection: Connection): Promise; createAccount(owner: PublicKey): Promise; createMultisig(m: number, signers: Array): Promise; @@ -65,7 +72,7 @@ declare module '@solana/spl-token' { transfer( source: PublicKey, destination: PublicKey, - authority: Account | PublicKey, + owner: Account | PublicKey, multiSigners: Array, amount: number | u64, ): Promise; @@ -81,10 +88,11 @@ declare module '@solana/spl-token' { owner: Account | PublicKey, multiSigners: Array, ): Promise; - setOwner( - owned: PublicKey, - newOwner: PublicKey, - owner: Account | PublicKey, + setAuthority( + account: PublicKey, + newAuthority: PublicKey | null, + authorityType: AuthorityType, + currentAuthority: Account | PublicKey, multiSigners: Array, ): Promise; mintTo( @@ -95,21 +103,34 @@ declare module '@solana/spl-token' { ): Promise; burn( account: PublicKey, - authority: Account | PublicKey, + owner: Account | PublicKey, multiSigners: Array, amount: number, ): Promise; closeAccount( account: PublicKey, dest: PublicKey, - owner: Account | PublicKey, + authority: Account | PublicKey, multiSigners: Array, ): Promise; + static createInitMintInstruction( + programId: PublicKey, + mint: PublicKey, + decimals: number, + mintAuthority: PublicKey, + freezeAuthority: PublicKey | null, + ): TransactionInstruction; + static createInitAccountInstruction( + programId: PublicKey, + mint: PublicKey, + account: PublicKey, + owner: PublicKey, + ): TransactionInstruction; static createTransferInstruction( programId: PublicKey, source: PublicKey, destination: PublicKey, - authority: Account | PublicKey, + owner: PublicKey, multiSigners: Array, amount: number | u64, ): TransactionInstruction; @@ -117,35 +138,36 @@ declare module '@solana/spl-token' { programId: PublicKey, account: PublicKey, delegate: PublicKey, - owner: Account | PublicKey, + owner: PublicKey, multiSigners: Array, amount: number | u64, ): TransactionInstruction; static createRevokeInstruction( programId: PublicKey, account: PublicKey, - owner: Account | PublicKey, + owner: PublicKey, multiSigners: Array, ): TransactionInstruction; - static createSetOwnerInstruction( + static createSetAuthorityInstruction( programId: PublicKey, - owned: PublicKey, - newOwner: PublicKey, - owner: Account | PublicKey, + account: PublicKey, + newAuthority: PublicKey | null, + authority: PublicKey, multiSigners: Array, ): TransactionInstruction; static createMintToInstruction( programId: PublicKey, mint: PublicKey, dest: PublicKey, - authority: Account | PublicKey, + authority: PublicKey, multiSigners: Array, amount: number, ): TransactionInstruction; static createBurnInstruction( programId: PublicKey, + mint: PublicKey, account: PublicKey, - authority: Account | PublicKey, + owner: PublicKey, multiSigners: Array, amount: number, ): TransactionInstruction; @@ -153,7 +175,7 @@ declare module '@solana/spl-token' { programId: PublicKey, account: PublicKey, dest: PublicKey, - owner: Account | PublicKey, + authority: PublicKey, multiSigners: Array, ): TransactionInstruction; } diff --git a/token/js/module.flow.js b/token/js/module.flow.js index 1d81229d..fd6bba3b 100644 --- a/token/js/module.flow.js +++ b/token/js/module.flow.js @@ -11,10 +11,17 @@ declare module '@solana/spl-token' { toBuffer(): Buffer; static fromBuffer(buffer: Buffer): u64; } + declare export type AuthorityType = + | 'MintTokens' + | 'FreezeAccount' + | 'AccountOwner' + | 'CloseAccount'; declare export type MintInfo = {| - owner: null | PublicKey, + mintAuthority: null | PublicKey, + supply: u64, decimals: number, - initialized: boolean, + isInitialized: boolean, + freezeAuthority: null | PublicKey, |}; declare export type AccountInfo = {| mint: PublicKey, @@ -23,7 +30,10 @@ declare module '@solana/spl-token' { delegate: null | PublicKey, delegatedAmount: u64, isInitialized: boolean, + isFrozen: boolean, isNative: boolean, + rentExemptReserve: null | u64, + closeAuthority: null | PublicKey, |}; declare export type MultisigInfo = {| m: number, @@ -41,7 +51,6 @@ declare module '@solana/spl-token' { signer10: PublicKey, signer11: PublicKey, |}; - declare export type TokenAndPublicKey = [Token, PublicKey]; declare export class Token { constructor( connection: Connection, @@ -52,13 +61,11 @@ declare module '@solana/spl-token' { static createMint( connection: Connection, payer: Account, - mintOwner: PublicKey, - accountOwner: PublicKey, - supply: u64, + mintAuthority: PublicKey, + freezeAuthority: PublicKey | null, decimals: number, programId: PublicKey, - is_owned: boolean, - ): Promise; + ): Promise; static getAccount(connection: Connection): Promise; createAccount(owner: PublicKey): Promise; createMultisig(m: number, signers: Array): Promise; @@ -68,7 +75,7 @@ declare module '@solana/spl-token' { transfer( source: PublicKey, destination: PublicKey, - authority: Account | PublicKey, + owner: Account | PublicKey, multiSigners: Array, amount: number | u64, ): Promise; @@ -84,10 +91,11 @@ declare module '@solana/spl-token' { owner: Account | PublicKey, multiSigners: Array, ): Promise; - setOwner( - owned: PublicKey, - newOwner: PublicKey, - owner: Account | PublicKey, + setAuthority( + account: PublicKey, + newAuthority: PublicKey | null, + authorityType: AuthorityType, + currentAuthority: Account | PublicKey, multiSigners: Array, ): Promise; mintTo( @@ -98,21 +106,34 @@ declare module '@solana/spl-token' { ): Promise; burn( account: PublicKey, - authority: Account | PublicKey, + owner: Account | PublicKey, multiSigners: Array, amount: number, ): Promise; closeAccount( account: PublicKey, dest: PublicKey, - owner: Account | PublicKey, + authority: Account | PublicKey, multiSigners: Array, ): Promise; + static createInitMintInstruction( + programId: PublicKey, + mint: PublicKey, + decimals: number, + mintAuthority: PublicKey, + freezeAuthority: PublicKey | null, + ): TransactionInstruction; + static createInitAccountInstruction( + programId: PublicKey, + mint: PublicKey, + account: PublicKey, + owner: PublicKey, + ): TransactionInstruction; static createTransferInstruction( programId: PublicKey, source: PublicKey, destination: PublicKey, - authority: Account | PublicKey, + owner: PublicKey, multiSigners: Array, amount: number | u64, ): TransactionInstruction; @@ -120,35 +141,36 @@ declare module '@solana/spl-token' { programId: PublicKey, account: PublicKey, delegate: PublicKey, - owner: Account | PublicKey, + owner: PublicKey, multiSigners: Array, amount: number | u64, ): TransactionInstruction; static createRevokeInstruction( programId: PublicKey, account: PublicKey, - owner: Account | PublicKey, + owner: PublicKey, multiSigners: Array, ): TransactionInstruction; - static createSetOwnerInstruction( + static createSetAuthorityInstruction( programId: PublicKey, - owned: PublicKey, - newOwner: PublicKey, - owner: Account | PublicKey, + account: PublicKey, + newAuthority: PublicKey | null, + authority: PublicKey, multiSigners: Array, ): TransactionInstruction; static createMintToInstruction( programId: PublicKey, mint: PublicKey, dest: PublicKey, - authority: Account | PublicKey, + authority: PublicKey, multiSigners: Array, amount: number, ): TransactionInstruction; static createBurnInstruction( programId: PublicKey, + mint: PublicKey, account: PublicKey, - authority: Account | PublicKey, + owner: PublicKey, multiSigners: Array, amount: number, ): TransactionInstruction; @@ -156,8 +178,8 @@ declare module '@solana/spl-token' { programId: PublicKey, account: PublicKey, dest: PublicKey, - owner: Account | PublicKey, + authority: PublicKey, multiSigners: Array, - ): TransactionInstructio; + ): TransactionInstruction; } }