forked from solana-labs/solana-program-library
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
token 2022: add metadata pointer extension to js @solana/spl-token cl…
…ient (solana-labs#5805) * added metadata pointer extension to js client * changed to default public key * addressed pr comments * comment out added, but unused instructions * remove number from extensionType enum * removed trailing unimplemented extensions
- Loading branch information
1 parent
6fe3c15
commit 20f27e2
Showing
8 changed files
with
388 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './instructions.js'; | ||
export * from './state.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { struct, u8 } from '@solana/buffer-layout'; | ||
import { publicKey } from '@solana/buffer-layout-utils'; | ||
import type { Signer } from '@solana/web3.js'; | ||
import { PublicKey, TransactionInstruction } from '@solana/web3.js'; | ||
import { TOKEN_2022_PROGRAM_ID, programSupportsExtensions } from '../../constants.js'; | ||
import { TokenUnsupportedInstructionError } from '../../errors.js'; | ||
import { TokenInstruction } from '../../instructions/types.js'; | ||
import { addSigners } from '../../instructions/internal.js'; | ||
|
||
export enum MetadataPointerInstruction { | ||
Initialize = 0, | ||
Update = 1, | ||
} | ||
|
||
export const initializeMetadataPointerData = struct<{ | ||
instruction: TokenInstruction.MetadataPointerExtension; | ||
metadataPointerInstruction: number; | ||
authority: PublicKey; | ||
metadataAddress: PublicKey; | ||
}>([ | ||
// prettier-ignore | ||
u8('instruction'), | ||
u8('metadataPointerInstruction'), | ||
publicKey('authority'), | ||
publicKey('metadataAddress'), | ||
]); | ||
|
||
/** | ||
* Construct an Initialize MetadataPointer instruction | ||
* | ||
* @param mint Token mint account | ||
* @param authority Optional Authority that can set the metadata address | ||
* @param metadataAddress Optional Account address that holds the metadata | ||
* @param programId SPL Token program account | ||
* | ||
* @return Instruction to add to a transaction | ||
*/ | ||
export function createInitializeMetadataPointerInstruction( | ||
mint: PublicKey, | ||
authority: PublicKey | null, | ||
metadataAddress: PublicKey | null, | ||
programId: PublicKey | ||
): TransactionInstruction { | ||
if (!programSupportsExtensions(programId)) { | ||
throw new TokenUnsupportedInstructionError(); | ||
} | ||
const keys = [{ pubkey: mint, isSigner: false, isWritable: true }]; | ||
|
||
const data = Buffer.alloc(initializeMetadataPointerData.span); | ||
initializeMetadataPointerData.encode( | ||
{ | ||
instruction: TokenInstruction.MetadataPointerExtension, | ||
metadataPointerInstruction: MetadataPointerInstruction.Initialize, | ||
authority: authority ?? PublicKey.default, | ||
metadataAddress: metadataAddress ?? PublicKey.default, | ||
}, | ||
data | ||
); | ||
|
||
return new TransactionInstruction({ keys, programId, data: data }); | ||
} | ||
|
||
export const updateMetadataPointerData = struct<{ | ||
instruction: TokenInstruction.MetadataPointerExtension; | ||
metadataPointerInstruction: number; | ||
metadataAddress: PublicKey; | ||
}>([ | ||
// prettier-ignore | ||
u8('instruction'), | ||
u8('metadataPointerInstruction'), | ||
publicKey('metadataAddress'), | ||
]); | ||
|
||
export function createUpdateMetadataPointerInstruction( | ||
mint: PublicKey, | ||
authority: PublicKey, | ||
metadataAddress: PublicKey | null, | ||
multiSigners: (Signer | PublicKey)[] = [], | ||
programId: PublicKey = TOKEN_2022_PROGRAM_ID | ||
): TransactionInstruction { | ||
if (!programSupportsExtensions(programId)) { | ||
throw new TokenUnsupportedInstructionError(); | ||
} | ||
|
||
const keys = addSigners([{ pubkey: mint, isSigner: false, isWritable: true }], authority, multiSigners); | ||
|
||
const data = Buffer.alloc(updateMetadataPointerData.span); | ||
updateMetadataPointerData.encode( | ||
{ | ||
instruction: TokenInstruction.MetadataPointerExtension, | ||
metadataPointerInstruction: MetadataPointerInstruction.Update, | ||
metadataAddress: metadataAddress ?? PublicKey.default, | ||
}, | ||
data | ||
); | ||
|
||
return new TransactionInstruction({ keys, programId, data: data }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { struct } from '@solana/buffer-layout'; | ||
import { publicKey } from '@solana/buffer-layout-utils'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import type { Mint } from '../../state/mint.js'; | ||
import { ExtensionType, getExtensionData } from '../extensionType.js'; | ||
|
||
/** MetadataPointer as stored by the program */ | ||
export interface MetadataPointer { | ||
/** Optional authority that can set the metadata address */ | ||
authority: PublicKey | null; | ||
/** Optional Account Address that holds the metadata */ | ||
metadataAddress: PublicKey | null; | ||
} | ||
|
||
/** Buffer layout for de/serializing a Metadata Pointer extension */ | ||
export const MetadataPointerLayout = struct<{ authority: PublicKey; metadataAddress: PublicKey }>([ | ||
publicKey('authority'), | ||
publicKey('metadataAddress'), | ||
]); | ||
|
||
export const METADATA_POINTER_SIZE = MetadataPointerLayout.span; | ||
|
||
export function getMetadataPointerState(mint: Mint): Partial<MetadataPointer> | null { | ||
const extensionData = getExtensionData(ExtensionType.MetadataPointer, mint.tlvData); | ||
if (extensionData !== null) { | ||
const { authority, metadataAddress } = MetadataPointerLayout.decode(extensionData); | ||
|
||
// Explicity set None/Zero keys to null | ||
return { | ||
authority: authority.equals(PublicKey.default) ? null : authority, | ||
metadataAddress: metadataAddress.equals(PublicKey.default) ? null : metadataAddress, | ||
}; | ||
} else { | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { expect } from 'chai'; | ||
import type { Connection, Signer } from '@solana/web3.js'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import { sendAndConfirmTransaction, Keypair, SystemProgram, Transaction } from '@solana/web3.js'; | ||
|
||
import { | ||
ExtensionType, | ||
createInitializeMetadataPointerInstruction, | ||
createInitializeMintInstruction, | ||
createUpdateMetadataPointerInstruction, | ||
getMetadataPointerState, | ||
getMint, | ||
getMintLen, | ||
} from '../../src'; | ||
import { TEST_PROGRAM_ID, newAccountWithLamports, getConnection } from '../common'; | ||
|
||
const TEST_TOKEN_DECIMALS = 2; | ||
const EXTENSIONS = [ExtensionType.MetadataPointer]; | ||
|
||
describe('Metadata pointer', () => { | ||
let connection: Connection; | ||
let payer: Signer; | ||
let mint: Keypair; | ||
let mintAuthority: Keypair; | ||
let metadataAddress: PublicKey; | ||
|
||
before(async () => { | ||
connection = await getConnection(); | ||
payer = await newAccountWithLamports(connection, 1000000000); | ||
mintAuthority = Keypair.generate(); | ||
}); | ||
|
||
beforeEach(async () => { | ||
mint = Keypair.generate(); | ||
metadataAddress = PublicKey.unique(); | ||
|
||
const mintLen = getMintLen(EXTENSIONS); | ||
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen); | ||
|
||
const transaction = new Transaction().add( | ||
SystemProgram.createAccount({ | ||
fromPubkey: payer.publicKey, | ||
newAccountPubkey: mint.publicKey, | ||
space: mintLen, | ||
lamports, | ||
programId: TEST_PROGRAM_ID, | ||
}), | ||
createInitializeMetadataPointerInstruction( | ||
mint.publicKey, | ||
mintAuthority.publicKey, | ||
metadataAddress, | ||
TEST_PROGRAM_ID | ||
), | ||
createInitializeMintInstruction( | ||
mint.publicKey, | ||
TEST_TOKEN_DECIMALS, | ||
mintAuthority.publicKey, | ||
null, | ||
TEST_PROGRAM_ID | ||
) | ||
); | ||
|
||
await sendAndConfirmTransaction(connection, transaction, [payer, mint], undefined); | ||
}); | ||
|
||
it('can successfully initialize', async () => { | ||
const mintInfo = await getMint(connection, mint.publicKey, undefined, TEST_PROGRAM_ID); | ||
const metadataPointer = getMetadataPointerState(mintInfo); | ||
|
||
expect(metadataPointer).to.deep.equal({ | ||
authority: mintAuthority.publicKey, | ||
metadataAddress, | ||
}); | ||
}); | ||
|
||
it('can update to new address', async () => { | ||
const newMetadataAddress = PublicKey.unique(); | ||
const transaction = new Transaction().add( | ||
createUpdateMetadataPointerInstruction( | ||
mint.publicKey, | ||
mintAuthority.publicKey, | ||
newMetadataAddress, | ||
undefined, | ||
TEST_PROGRAM_ID | ||
) | ||
); | ||
await sendAndConfirmTransaction(connection, transaction, [payer, mintAuthority], undefined); | ||
|
||
const mintInfo = await getMint(connection, mint.publicKey, undefined, TEST_PROGRAM_ID); | ||
const metadataPointer = getMetadataPointerState(mintInfo); | ||
|
||
expect(metadataPointer).to.deep.equal({ | ||
authority: mintAuthority.publicKey, | ||
metadataAddress: newMetadataAddress, | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.