Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/puny-pillows-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solana-program/token-wrap": minor
---

Update idl for new metadata sync instructions
99 changes: 99 additions & 0 deletions clients/js/src/examples/sync-spl-to-token2022.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
address,
appendTransactionMessageInstructions,
assertIsSendableTransaction,
createKeyPairSignerFromBytes,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
getSignatureFromTransaction,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
} from '@solana/kit';
import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022';
import { getAddressEncoder, getProgramDerivedAddress, getUtf8Encoder } from '@solana/kit';
import {
findWrappedMintAuthorityPda,
findWrappedMintPda,
getSyncMetadataToToken2022Instruction,
} from '../index';

// =================================================================
// PREREQUISITES:
// =================================================================
// 1. An unwrapped SPL Token mint with Metaplex metadata must exist.
// 2. The corresponding wrapped Token-2022 mint for it must have been created
// via the `create-mint` command or `createMint` helper.
//
// This example ASSUMES these accounts already exist and focuses only on the
// transaction to sync the metadata.

// Replace these consts with the addresses from your setup
const PRIVATE_KEY_PAIR = new Uint8Array([
242, 30, 38, 177, 152, 71, 235, 193, 93, 30, 119, 131, 42, 186, 202, 7, 45, 250, 126, 135, 107,
137, 38, 91, 202, 212, 12, 8, 154, 213, 163, 200, 23, 237, 17, 163, 3, 135, 34, 126, 235, 146,
251, 18, 199, 101, 153, 249, 134, 88, 219, 68, 167, 136, 234, 195, 12, 34, 184, 85, 234, 25, 125,
94,
]);

// Source Mint: An existing SPL Token mint with Metaplex metadata
const UNWRAPPED_SPL_TOKEN_MINT = address('8owJWKMiKfMKYbPmobyZAwXibNFcY7Roj6quktaeqxGL');

async function main() {
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });

const payer = await createKeyPairSignerFromBytes(PRIVATE_KEY_PAIR);
const { value: blockhash } = await rpc.getLatestBlockhash().send();

console.log('======== Syncing: SPL Token -> Token-2022 ========');

// To sync from an SPL Token mint, the client must resolve and provide the
// Metaplex Metadata PDA as the `sourceMetadata` account.
// Derive it using the known Metadata program and seeds: ['metadata', programId, mint]
const TOKEN_METADATA_PROGRAM_ADDRESS = address('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
const [metaplexMetadataPda] = await getProgramDerivedAddress({
programAddress: TOKEN_METADATA_PROGRAM_ADDRESS,
seeds: [
getUtf8Encoder().encode('metadata'),
getAddressEncoder().encode(TOKEN_METADATA_PROGRAM_ADDRESS),
getAddressEncoder().encode(UNWRAPPED_SPL_TOKEN_MINT),
],
});
const [wrappedMint] = await findWrappedMintPda({
unwrappedMint: UNWRAPPED_SPL_TOKEN_MINT,
wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
});
const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({
wrappedMint,
});

const syncToT22Ix = getSyncMetadataToToken2022Instruction({
wrappedMint,
wrappedMintAuthority,
unwrappedMint: UNWRAPPED_SPL_TOKEN_MINT,
// When the source mint is a standard SPL Token, `sourceMetadata` MUST be
// the address of its Metaplex Metadata PDA.
sourceMetadata: metaplexMetadataPda,
});

const transaction = await pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(payer, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
tx => appendTransactionMessageInstructions([syncToT22Ix], tx),
tx => signTransactionMessageWithSigners(tx),
);
assertIsSendableTransaction(transaction);
const signature = getSignatureFromTransaction(transaction);
await sendAndConfirm(transaction, { commitment: 'confirmed' });

console.log('Successfully synced metadata to Token-2022 mint.');
console.log('Signature:', signature);
}

void main();
104 changes: 104 additions & 0 deletions clients/js/src/examples/sync-token2022-to-spl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
address,
appendTransactionMessageInstructions,
assertIsSendableTransaction,
createKeyPairSignerFromBytes,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
getAddressEncoder,
getProgramDerivedAddress,
getSignatureFromTransaction,
getUtf8Encoder,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
} from '@solana/kit';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import {
findWrappedMintAuthorityPda,
findWrappedMintPda,
getSyncMetadataToSplTokenInstruction,
} from '../index';

// =================================================================
// PREREQUISITES:
// =================================================================
// 1. An unwrapped Token-2022 mint with `TokenMetadata` and `MetadataPointer`
// extensions must exist. The pointer should point to the mint itself.
// 2. The corresponding wrapped SPL Token mint for it must have been created
// via the `create-mint` command or `createMint` helper.
// 3. The wrapped mint authority PDA must be funded with enough SOL to pay for
// the creation of the Metaplex metadata account, as it acts as the payer
// for the CPI to the Metaplex program.
//
// This example ASSUMES these accounts already exist and focuses only on the
// transaction to sync the metadata.

// Replace these consts with the addresses from your setup
const PRIVATE_KEY_PAIR = new Uint8Array([
242, 30, 38, 177, 152, 71, 235, 193, 93, 30, 119, 131, 42, 186, 202, 7, 45, 250, 126, 135, 107,
137, 38, 91, 202, 212, 12, 8, 154, 213, 163, 200, 23, 237, 17, 163, 3, 135, 34, 126, 235, 146,
251, 18, 199, 101, 153, 249, 134, 88, 219, 68, 167, 136, 234, 195, 12, 34, 184, 85, 234, 25, 125,
94,
]);

// Source Mint: An existing Token-2022 mint with metadata extensions
const UNWRAPPED_TOKEN_2022_MINT = address('5xte8yNSUTrTtfdptekeA4QJyo8zZdanpDJojrRaXP1Y');

async function main() {
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });

const payer = await createKeyPairSignerFromBytes(PRIVATE_KEY_PAIR);
const { value: blockhash } = await rpc.getLatestBlockhash().send();

console.log('======== Syncing: Token-2022 -> SPL Token ========');

// Derive the wrapped SPL Token mint PDA
const [wrappedMint] = await findWrappedMintPda({
unwrappedMint: UNWRAPPED_TOKEN_2022_MINT,
wrappedTokenProgram: TOKEN_PROGRAM_ADDRESS,
});

// Derive the Metaplex Metadata PDA for the wrapped mint.
const TOKEN_METADATA_PROGRAM_ADDRESS = address('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
const [metaplexMetadataPda] = await getProgramDerivedAddress({
programAddress: TOKEN_METADATA_PROGRAM_ADDRESS,
seeds: [
getUtf8Encoder().encode('metadata'),
getAddressEncoder().encode(TOKEN_METADATA_PROGRAM_ADDRESS),
getAddressEncoder().encode(wrappedMint),
],
});

const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({
wrappedMint,
});

const syncToSplIx = getSyncMetadataToSplTokenInstruction({
metaplexMetadata: metaplexMetadataPda,
wrappedMint,
wrappedMintAuthority,
unwrappedMint: UNWRAPPED_TOKEN_2022_MINT,
});

const transaction = await pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(payer, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
tx => appendTransactionMessageInstructions([syncToSplIx], tx),
tx => signTransactionMessageWithSigners(tx),
);
assertIsSendableTransaction(transaction);
const signature = getSignatureFromTransaction(transaction);
await sendAndConfirm(transaction, { commitment: 'confirmed' });

console.log('Successfully synced metadata to SPL Token Metaplex account.');
console.log('Signature:', signature);
}

void main();
28 changes: 28 additions & 0 deletions clients/js/src/generated/errors/tokenWrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,35 @@ export const TOKEN_WRAP_ERROR__ESCROW_MISMATCH = 0x7; // 7

export const TOKEN_WRAP_ERROR__ESCROW_IN_GOOD_STATE = 0x8; // 8

export const TOKEN_WRAP_ERROR__UNWRAPPED_MINT_HAS_NO_METADATA = 0x9; // 9

export const TOKEN_WRAP_ERROR__METAPLEX_METADATA_MISMATCH = 0xa; // 10

export const TOKEN_WRAP_ERROR__METADATA_POINTER_MISSING = 0xb; // 11

export const TOKEN_WRAP_ERROR__METADATA_POINTER_UNSET = 0xc; // 12

export const TOKEN_WRAP_ERROR__METADATA_POINTER_MISMATCH = 0xd; // 13

export const TOKEN_WRAP_ERROR__EXTERNAL_PROGRAM_RETURNED_NO_DATA = 0xe; // 14

export const TOKEN_WRAP_ERROR__NO_SYNCING_TO_TOKEN2022 = 0xf; // 15

export type TokenWrapError =
| typeof TOKEN_WRAP_ERROR__BACKPOINTER_MISMATCH
| typeof TOKEN_WRAP_ERROR__ESCROW_IN_GOOD_STATE
| typeof TOKEN_WRAP_ERROR__ESCROW_MISMATCH
| typeof TOKEN_WRAP_ERROR__ESCROW_OWNER_MISMATCH
| typeof TOKEN_WRAP_ERROR__EXTERNAL_PROGRAM_RETURNED_NO_DATA
| typeof TOKEN_WRAP_ERROR__INVALID_BACKPOINTER_OWNER
| typeof TOKEN_WRAP_ERROR__INVALID_WRAPPED_MINT_OWNER
| typeof TOKEN_WRAP_ERROR__METADATA_POINTER_MISMATCH
| typeof TOKEN_WRAP_ERROR__METADATA_POINTER_MISSING
| typeof TOKEN_WRAP_ERROR__METADATA_POINTER_UNSET
| typeof TOKEN_WRAP_ERROR__METAPLEX_METADATA_MISMATCH
| typeof TOKEN_WRAP_ERROR__MINT_AUTHORITY_MISMATCH
| typeof TOKEN_WRAP_ERROR__NO_SYNCING_TO_TOKEN2022
| typeof TOKEN_WRAP_ERROR__UNWRAPPED_MINT_HAS_NO_METADATA
| typeof TOKEN_WRAP_ERROR__WRAPPED_MINT_MISMATCH
| typeof TOKEN_WRAP_ERROR__ZERO_WRAP_AMOUNT;

Expand All @@ -50,9 +71,16 @@ if (process.env.NODE_ENV !== 'production') {
[TOKEN_WRAP_ERROR__ESCROW_IN_GOOD_STATE]: `The escrow account is in a good state and cannot be recreated`,
[TOKEN_WRAP_ERROR__ESCROW_MISMATCH]: `Escrow account address does not match expected ATA`,
[TOKEN_WRAP_ERROR__ESCROW_OWNER_MISMATCH]: `Unwrapped escrow token owner is not set to expected PDA`,
[TOKEN_WRAP_ERROR__EXTERNAL_PROGRAM_RETURNED_NO_DATA]: `External metadata program returned no data`,
[TOKEN_WRAP_ERROR__INVALID_BACKPOINTER_OWNER]: `Wrapped backpointer account owner is not the expected token wrap program`,
[TOKEN_WRAP_ERROR__INVALID_WRAPPED_MINT_OWNER]: `Wrapped mint account owner is not the expected token program`,
[TOKEN_WRAP_ERROR__METADATA_POINTER_MISMATCH]: `Provided source metadata account does not match pointer`,
[TOKEN_WRAP_ERROR__METADATA_POINTER_MISSING]: `Metadata pointer extension missing on mint`,
[TOKEN_WRAP_ERROR__METADATA_POINTER_UNSET]: `Metadata pointer is unset (None)`,
[TOKEN_WRAP_ERROR__METAPLEX_METADATA_MISMATCH]: `Metaplex metadata account address does not match expected PDA`,
[TOKEN_WRAP_ERROR__MINT_AUTHORITY_MISMATCH]: `Wrapped mint authority does not match expected PDA`,
[TOKEN_WRAP_ERROR__NO_SYNCING_TO_TOKEN2022]: `Instruction can only be used with spl-token wrapped mints`,
[TOKEN_WRAP_ERROR__UNWRAPPED_MINT_HAS_NO_METADATA]: `Unwrapped mint does not have the TokenMetadata extension`,
[TOKEN_WRAP_ERROR__WRAPPED_MINT_MISMATCH]: `Wrapped mint account address does not match expected PDA`,
[TOKEN_WRAP_ERROR__ZERO_WRAP_AMOUNT]: `Wrap amount should be positive`,
};
Expand Down
2 changes: 2 additions & 0 deletions clients/js/src/generated/instructions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@

export * from './closeStuckEscrow';
export * from './createMint';
export * from './syncMetadataToSplToken';
export * from './syncMetadataToToken2022';
export * from './unwrap';
export * from './wrap';
Loading