Skip to content

Commit

Permalink
added init, update and emit token metadata actions
Browse files Browse the repository at this point in the history
  • Loading branch information
mistersimon committed Nov 18, 2023
1 parent c8b42ed commit 5d22bf8
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 4 deletions.
11 changes: 7 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions token/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"dependencies": {
"@solana/buffer-layout": "^4.0.0",
"@solana/buffer-layout-utils": "^0.2.0",
"@solana/spl-token-metadata": "^0.1.1",
"buffer": "^6.0.3"
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions token/js/src/extensions/extensionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum ExtensionType {
// ConfidentialTransferFee, // Not implemented yet
// ConfidentialTransferFeeAmount, // Not implemented yet
MetadataPointer = 18, // Remove number once above extensions implemented
TokenMetadata = 19, // Remove number once above extensions implemented
}

export const TYPE_SIZE = 2;
Expand All @@ -46,6 +47,7 @@ export const LENGTH_SIZE = 2;
export function getTypeLen(e: ExtensionType): number {
switch (e) {
case ExtensionType.Uninitialized:
case ExtensionType.TokenMetadata:
return 0;
case ExtensionType.TransferFeeConfig:
return TRANSFER_FEE_CONFIG_SIZE;
Expand Down Expand Up @@ -95,6 +97,7 @@ export function isMintExtension(e: ExtensionType): boolean {
case ExtensionType.PermanentDelegate:
case ExtensionType.TransferHook:
case ExtensionType.MetadataPointer:
case ExtensionType.TokenMetadata:
return true;
case ExtensionType.Uninitialized:
case ExtensionType.TransferFeeAmount:
Expand Down Expand Up @@ -130,6 +133,7 @@ export function isAccountExtension(e: ExtensionType): boolean {
case ExtensionType.PermanentDelegate:
case ExtensionType.TransferHook:
case ExtensionType.MetadataPointer:
case ExtensionType.TokenMetadata:
return false;
default:
throw Error(`Unknown extension type: ${e}`);
Expand All @@ -154,6 +158,7 @@ export function getAccountTypeOfMintType(e: ExtensionType): ExtensionType {
case ExtensionType.MemoTransfer:
case ExtensionType.MintCloseAuthority:
case ExtensionType.MetadataPointer:
case ExtensionType.TokenMetadata:
case ExtensionType.Uninitialized:
case ExtensionType.InterestBearingConfig:
case ExtensionType.PermanentDelegate:
Expand Down
1 change: 1 addition & 0 deletions token/js/src/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './immutableOwner.js';
export * from './interestBearingMint/index.js';
export * from './memoTransfer/index.js';
export * from './metadataPointer/index.js';
export * from './tokenMetadata/index.js';
export * from './mintCloseAuthority.js';
export * from './nonTransferable.js';
export * from './transferFee/index.js';
Expand Down
133 changes: 133 additions & 0 deletions token/js/src/extensions/tokenMetadata/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type { ConfirmOptions, Connection, PublicKey, Signer, TransactionSignature } from '@solana/web3.js';
import type { Field } from '@solana/spl-token-metadata';
import {
createInitializeInstruction,
createEmitInstruction,
createUpdateFieldInstruction,
} from '@solana/spl-token-metadata';
import { sendAndConfirmTransaction, Transaction } from '@solana/web3.js';

import { TOKEN_2022_PROGRAM_ID } from '../../constants.js';
import { getSigners } from '../../actions/internal.js';

/**
* Initializes a TLV entry with the basic token-metadata fields.
*
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param updateAuthority Update Authority
* @param mint Mint Account
* @param mintAuthority Mint Authority
* @param name Longer name of token
* @param symbol Shortened symbol of token
* @param uri URI pointing to more metadata (image, video, etc)
* @param multiSigners Signing accounts if `authority` is a multisig
* @param confirmOptions Options for confirming the transaction
* @param programId SPL Token program account
*
* @return Signature of the confirmed transaction
*/
export async function tokenMetadataInitialize(
connection: Connection,
payer: Signer,
updateAuthority: PublicKey,
mint: PublicKey,
mintAuthority: PublicKey | Signer,
name: string,
symbol: string,
uri: string,
multiSigners: Signer[] = [],
confirmOptions?: ConfirmOptions,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TransactionSignature> {
const [mintAuthorityPublicKey, signers] = getSigners(mintAuthority, multiSigners);

const transaction = new Transaction().add(
createInitializeInstruction({
programId,
metadata: mint,
updateAuthority,
mint,
mintAuthority: mintAuthorityPublicKey,
name,
symbol,
uri,
})
);

return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
}

/**
* Updates a field in a token-metadata account.
* If the field does not exist on the account, it will be created.
* If the field does exist, it will be overwritten.
*
* The field can be one of the required fields (name, symbol, URI), or a
* totally new field denoted by a "key" string.
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param updateAuthority Update Authority
* @param mint Mint Account
* @param field Longer name of token
* @param value Shortened symbol of token
* @param multiSigners Signing accounts if `authority` is a multisig
* @param confirmOptions Options for confirming the transaction
* @param programId SPL Token program account
*
* @return Signature of the confirmed transaction
*/
export async function tokenMetadataUpdateField(
connection: Connection,
payer: Signer,
updateAuthority: PublicKey | Signer,
mint: PublicKey,
field: string | Field,
value: string,
multiSigners: Signer[] = [],
confirmOptions?: ConfirmOptions,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TransactionSignature> {
const [updateAuthorityPublicKey, signers] = getSigners(updateAuthority, multiSigners);

const transaction = new Transaction().add(
createUpdateFieldInstruction({
programId,
metadata: mint,
updateAuthority: updateAuthorityPublicKey,
field,
value,
})
);

return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
}

/**
* Emits the token-metadata as return data
*
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param mint Mint Account
* @param multiSigners Signing accounts if `authority` is a multisig
* @param confirmOptions Options for confirming the transaction
* @param programId SPL Token program account
*
* @return Signature of the confirmed transaction
*/
export async function tokenMetadataEmit(
connection: Connection,
payer: Signer,
mint: PublicKey,
confirmOptions?: ConfirmOptions,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TransactionSignature> {
const transaction = new Transaction().add(
createEmitInstruction({
programId,
metadata: mint,
})
);

return await sendAndConfirmTransaction(connection, transaction, [payer], confirmOptions);
}
2 changes: 2 additions & 0 deletions token/js/src/extensions/tokenMetadata/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './actions.js';
export * from './state.js';
62 changes: 62 additions & 0 deletions token/js/src/extensions/tokenMetadata/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Commitment, Connection, Finality, PublicKey } from '@solana/web3.js';
import type { TokenMetadata } from '@solana/spl-token-metadata';
import { unpack } from '@solana/spl-token-metadata';

import { TOKEN_2022_PROGRAM_ID } from '../../constants.js';
import { ExtensionType, getExtensionData } from '../extensionType.js';
import { getMint } from '../../state/mint.js';

/**
* Retrieve Token Metadata Information
*
* @param connection Connection to use
* @param address Mint account
* @param commitment Desired level of commitment for querying the state
* @param programId SPL Token program account
*
* @return Token Metadata information
*/
export async function getTokenMetadata(
connection: Connection,
address: PublicKey,
commitment?: Commitment,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TokenMetadata | null> {
const mintInfo = await getMint(connection, address, commitment, programId);
const data = getExtensionData(ExtensionType.TokenMetadata, mintInfo.tlvData);

if (data === null) {
return null;
}

return unpack(data);
}

/**
* Retrieve Token Metadata Information emitted in transaction
*
* @param connection Connection to use
* @param signature Transaction signature
* @param commitment Desired level of commitment for querying the state
*
* @return Token Metadata information
*/
export async function getEmittedTokenMetadata(
connection: Connection,
signature: string,
commitment?: Finality,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TokenMetadata | null> {
const tx: any = await connection.getTransaction(signature, {
commitment: commitment,
maxSupportedTransactionVersion: 2,
});

const data = Buffer.from(tx?.meta?.returnData?.data?.[0], 'base64');

if (data === null) {
return null;
}

return unpack(data);
}
8 changes: 8 additions & 0 deletions token/js/src/instructions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
export {
createInitializeInstruction,
createUpdateFieldInstruction,
createRemoveKeyInstruction,
createUpdateAuthorityInstruction,
createEmitInstruction,
} from '@solana/spl-token-metadata';

export * from './associatedTokenAccount.js';
export * from './decode.js';
export * from './types.js';
Expand Down
Loading

0 comments on commit 5d22bf8

Please sign in to comment.