Selaa lähdekoodia

Add Instruction discriminators to Associated Token Program

Loris Leiva 1 vuosi sitten
vanhempi
sitoutus
bad137e7ec

+ 45 - 1
clients/js/src/generated/instructions/createAssociatedToken.ts

@@ -8,14 +8,24 @@
 
 import {
   Address,
+  Codec,
+  Decoder,
+  Encoder,
   IAccountMeta,
   IAccountSignerMeta,
   IInstruction,
   IInstructionWithAccounts,
+  IInstructionWithData,
   ReadonlyAccount,
   TransactionSigner,
   WritableAccount,
   WritableSignerAccount,
+  combineCodec,
+  getStructDecoder,
+  getStructEncoder,
+  getU8Decoder,
+  getU8Encoder,
+  transformEncoder,
 } from '@solana/web3.js';
 import { findAssociatedTokenPda } from '../pdas';
 import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS } from '../programs';
@@ -39,6 +49,7 @@ export type CreateAssociatedTokenInstruction<
     | IAccountMeta<string> = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
   TRemainingAccounts extends readonly IAccountMeta<string>[] = [],
 > = IInstruction<TProgram> &
+  IInstructionWithData<Uint8Array> &
   IInstructionWithAccounts<
     [
       TAccountPayer extends string
@@ -62,6 +73,31 @@ export type CreateAssociatedTokenInstruction<
     ]
   >;
 
+export type CreateAssociatedTokenInstructionData = { discriminator: number };
+
+export type CreateAssociatedTokenInstructionDataArgs = {};
+
+export function getCreateAssociatedTokenInstructionDataEncoder(): Encoder<CreateAssociatedTokenInstructionDataArgs> {
+  return transformEncoder(
+    getStructEncoder([['discriminator', getU8Encoder()]]),
+    (value) => ({ ...value, discriminator: 0 })
+  );
+}
+
+export function getCreateAssociatedTokenInstructionDataDecoder(): Decoder<CreateAssociatedTokenInstructionData> {
+  return getStructDecoder([['discriminator', getU8Decoder()]]);
+}
+
+export function getCreateAssociatedTokenInstructionDataCodec(): Codec<
+  CreateAssociatedTokenInstructionDataArgs,
+  CreateAssociatedTokenInstructionData
+> {
+  return combineCodec(
+    getCreateAssociatedTokenInstructionDataEncoder(),
+    getCreateAssociatedTokenInstructionDataDecoder()
+  );
+}
+
 export type CreateAssociatedTokenAsyncInput<
   TAccountPayer extends string = string,
   TAccountAta extends string = string,
@@ -156,6 +192,7 @@ export async function getCreateAssociatedTokenInstructionAsync<
       getAccountMeta(accounts.tokenProgram),
     ],
     programAddress,
+    data: getCreateAssociatedTokenInstructionDataEncoder().encode({}),
   } as CreateAssociatedTokenInstruction<
     typeof ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
     TAccountPayer,
@@ -254,6 +291,7 @@ export function getCreateAssociatedTokenInstruction<
       getAccountMeta(accounts.tokenProgram),
     ],
     programAddress,
+    data: getCreateAssociatedTokenInstructionDataEncoder().encode({}),
   } as CreateAssociatedTokenInstruction<
     typeof ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
     TAccountPayer,
@@ -286,13 +324,16 @@ export type ParsedCreateAssociatedTokenInstruction<
     /** SPL Token program. */
     tokenProgram: TAccountMetas[5];
   };
+  data: CreateAssociatedTokenInstructionData;
 };
 
 export function parseCreateAssociatedTokenInstruction<
   TProgram extends string,
   TAccountMetas extends readonly IAccountMeta[],
 >(
-  instruction: IInstruction<TProgram> & IInstructionWithAccounts<TAccountMetas>
+  instruction: IInstruction<TProgram> &
+    IInstructionWithAccounts<TAccountMetas> &
+    IInstructionWithData<Uint8Array>
 ): ParsedCreateAssociatedTokenInstruction<TProgram, TAccountMetas> {
   if (instruction.accounts.length < 6) {
     // TODO: Coded error.
@@ -314,5 +355,8 @@ export function parseCreateAssociatedTokenInstruction<
       systemProgram: getNextAccount(),
       tokenProgram: getNextAccount(),
     },
+    data: getCreateAssociatedTokenInstructionDataDecoder().decode(
+      instruction.data
+    ),
   };
 }

+ 47 - 1
clients/js/src/generated/instructions/createAssociatedTokenIdempotent.ts

@@ -8,14 +8,24 @@
 
 import {
   Address,
+  Codec,
+  Decoder,
+  Encoder,
   IAccountMeta,
   IAccountSignerMeta,
   IInstruction,
   IInstructionWithAccounts,
+  IInstructionWithData,
   ReadonlyAccount,
   TransactionSigner,
   WritableAccount,
   WritableSignerAccount,
+  combineCodec,
+  getStructDecoder,
+  getStructEncoder,
+  getU8Decoder,
+  getU8Encoder,
+  transformEncoder,
 } from '@solana/web3.js';
 import { findAssociatedTokenPda } from '../pdas';
 import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS } from '../programs';
@@ -39,6 +49,7 @@ export type CreateAssociatedTokenIdempotentInstruction<
     | IAccountMeta<string> = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
   TRemainingAccounts extends readonly IAccountMeta<string>[] = [],
 > = IInstruction<TProgram> &
+  IInstructionWithData<Uint8Array> &
   IInstructionWithAccounts<
     [
       TAccountPayer extends string
@@ -62,6 +73,33 @@ export type CreateAssociatedTokenIdempotentInstruction<
     ]
   >;
 
+export type CreateAssociatedTokenIdempotentInstructionData = {
+  discriminator: number;
+};
+
+export type CreateAssociatedTokenIdempotentInstructionDataArgs = {};
+
+export function getCreateAssociatedTokenIdempotentInstructionDataEncoder(): Encoder<CreateAssociatedTokenIdempotentInstructionDataArgs> {
+  return transformEncoder(
+    getStructEncoder([['discriminator', getU8Encoder()]]),
+    (value) => ({ ...value, discriminator: 1 })
+  );
+}
+
+export function getCreateAssociatedTokenIdempotentInstructionDataDecoder(): Decoder<CreateAssociatedTokenIdempotentInstructionData> {
+  return getStructDecoder([['discriminator', getU8Decoder()]]);
+}
+
+export function getCreateAssociatedTokenIdempotentInstructionDataCodec(): Codec<
+  CreateAssociatedTokenIdempotentInstructionDataArgs,
+  CreateAssociatedTokenIdempotentInstructionData
+> {
+  return combineCodec(
+    getCreateAssociatedTokenIdempotentInstructionDataEncoder(),
+    getCreateAssociatedTokenIdempotentInstructionDataDecoder()
+  );
+}
+
 export type CreateAssociatedTokenIdempotentAsyncInput<
   TAccountPayer extends string = string,
   TAccountAta extends string = string,
@@ -156,6 +194,7 @@ export async function getCreateAssociatedTokenIdempotentInstructionAsync<
       getAccountMeta(accounts.tokenProgram),
     ],
     programAddress,
+    data: getCreateAssociatedTokenIdempotentInstructionDataEncoder().encode({}),
   } as CreateAssociatedTokenIdempotentInstruction<
     typeof ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
     TAccountPayer,
@@ -254,6 +293,7 @@ export function getCreateAssociatedTokenIdempotentInstruction<
       getAccountMeta(accounts.tokenProgram),
     ],
     programAddress,
+    data: getCreateAssociatedTokenIdempotentInstructionDataEncoder().encode({}),
   } as CreateAssociatedTokenIdempotentInstruction<
     typeof ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
     TAccountPayer,
@@ -286,13 +326,16 @@ export type ParsedCreateAssociatedTokenIdempotentInstruction<
     /** SPL Token program. */
     tokenProgram: TAccountMetas[5];
   };
+  data: CreateAssociatedTokenIdempotentInstructionData;
 };
 
 export function parseCreateAssociatedTokenIdempotentInstruction<
   TProgram extends string,
   TAccountMetas extends readonly IAccountMeta[],
 >(
-  instruction: IInstruction<TProgram> & IInstructionWithAccounts<TAccountMetas>
+  instruction: IInstruction<TProgram> &
+    IInstructionWithAccounts<TAccountMetas> &
+    IInstructionWithData<Uint8Array>
 ): ParsedCreateAssociatedTokenIdempotentInstruction<TProgram, TAccountMetas> {
   if (instruction.accounts.length < 6) {
     // TODO: Coded error.
@@ -314,5 +357,8 @@ export function parseCreateAssociatedTokenIdempotentInstruction<
       systemProgram: getNextAccount(),
       tokenProgram: getNextAccount(),
     },
+    data: getCreateAssociatedTokenIdempotentInstructionDataDecoder().decode(
+      instruction.data
+    ),
   };
 }

+ 47 - 1
clients/js/src/generated/instructions/recoverNestedAssociatedToken.ts

@@ -8,14 +8,24 @@
 
 import {
   Address,
+  Codec,
+  Decoder,
+  Encoder,
   IAccountMeta,
   IAccountSignerMeta,
   IInstruction,
   IInstructionWithAccounts,
+  IInstructionWithData,
   ReadonlyAccount,
   TransactionSigner,
   WritableAccount,
   WritableSignerAccount,
+  combineCodec,
+  getStructDecoder,
+  getStructEncoder,
+  getU8Decoder,
+  getU8Encoder,
+  transformEncoder,
 } from '@solana/web3.js';
 import { findAssociatedTokenPda } from '../pdas';
 import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS } from '../programs';
@@ -44,6 +54,7 @@ export type RecoverNestedAssociatedTokenInstruction<
     | IAccountMeta<string> = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
   TRemainingAccounts extends readonly IAccountMeta<string>[] = [],
 > = IInstruction<TProgram> &
+  IInstructionWithData<Uint8Array> &
   IInstructionWithAccounts<
     [
       TAccountNestedAssociatedAccountAddress extends string
@@ -72,6 +83,33 @@ export type RecoverNestedAssociatedTokenInstruction<
     ]
   >;
 
+export type RecoverNestedAssociatedTokenInstructionData = {
+  discriminator: number;
+};
+
+export type RecoverNestedAssociatedTokenInstructionDataArgs = {};
+
+export function getRecoverNestedAssociatedTokenInstructionDataEncoder(): Encoder<RecoverNestedAssociatedTokenInstructionDataArgs> {
+  return transformEncoder(
+    getStructEncoder([['discriminator', getU8Encoder()]]),
+    (value) => ({ ...value, discriminator: 2 })
+  );
+}
+
+export function getRecoverNestedAssociatedTokenInstructionDataDecoder(): Decoder<RecoverNestedAssociatedTokenInstructionData> {
+  return getStructDecoder([['discriminator', getU8Decoder()]]);
+}
+
+export function getRecoverNestedAssociatedTokenInstructionDataCodec(): Codec<
+  RecoverNestedAssociatedTokenInstructionDataArgs,
+  RecoverNestedAssociatedTokenInstructionData
+> {
+  return combineCodec(
+    getRecoverNestedAssociatedTokenInstructionDataEncoder(),
+    getRecoverNestedAssociatedTokenInstructionDataDecoder()
+  );
+}
+
 export type RecoverNestedAssociatedTokenAsyncInput<
   TAccountNestedAssociatedAccountAddress extends string = string,
   TAccountNestedTokenMintAddress extends string = string,
@@ -203,6 +241,7 @@ export async function getRecoverNestedAssociatedTokenInstructionAsync<
       getAccountMeta(accounts.tokenProgram),
     ],
     programAddress,
+    data: getRecoverNestedAssociatedTokenInstructionDataEncoder().encode({}),
   } as RecoverNestedAssociatedTokenInstruction<
     typeof ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
     TAccountNestedAssociatedAccountAddress,
@@ -321,6 +360,7 @@ export function getRecoverNestedAssociatedTokenInstruction<
       getAccountMeta(accounts.tokenProgram),
     ],
     programAddress,
+    data: getRecoverNestedAssociatedTokenInstructionDataEncoder().encode({}),
   } as RecoverNestedAssociatedTokenInstruction<
     typeof ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
     TAccountNestedAssociatedAccountAddress,
@@ -356,13 +396,16 @@ export type ParsedRecoverNestedAssociatedTokenInstruction<
     /** SPL Token program. */
     tokenProgram: TAccountMetas[6];
   };
+  data: RecoverNestedAssociatedTokenInstructionData;
 };
 
 export function parseRecoverNestedAssociatedTokenInstruction<
   TProgram extends string,
   TAccountMetas extends readonly IAccountMeta[],
 >(
-  instruction: IInstruction<TProgram> & IInstructionWithAccounts<TAccountMetas>
+  instruction: IInstruction<TProgram> &
+    IInstructionWithAccounts<TAccountMetas> &
+    IInstructionWithData<Uint8Array>
 ): ParsedRecoverNestedAssociatedTokenInstruction<TProgram, TAccountMetas> {
   if (instruction.accounts.length < 7) {
     // TODO: Coded error.
@@ -385,5 +428,8 @@ export function parseRecoverNestedAssociatedTokenInstruction<
       walletAddress: getNextAccount(),
       tokenProgram: getNextAccount(),
     },
+    data: getRecoverNestedAssociatedTokenInstructionDataDecoder().decode(
+      instruction.data
+    ),
   };
 }

+ 20 - 1
clients/js/src/generated/programs/associatedToken.ts

@@ -6,7 +6,7 @@
  * @see https://github.com/metaplex-foundation/kinobi
  */
 
-import { Address } from '@solana/web3.js';
+import { Address, containsBytes, getU8Encoder } from '@solana/web3.js';
 import {
   ParsedCreateAssociatedTokenIdempotentInstruction,
   ParsedCreateAssociatedTokenInstruction,
@@ -22,6 +22,25 @@ export enum AssociatedTokenInstruction {
   RecoverNestedAssociatedToken,
 }
 
+export function identifyAssociatedTokenInstruction(
+  instruction: { data: Uint8Array } | Uint8Array
+): AssociatedTokenInstruction {
+  const data =
+    instruction instanceof Uint8Array ? instruction : instruction.data;
+  if (containsBytes(data, getU8Encoder().encode(0), 0)) {
+    return AssociatedTokenInstruction.CreateAssociatedToken;
+  }
+  if (containsBytes(data, getU8Encoder().encode(1), 0)) {
+    return AssociatedTokenInstruction.CreateAssociatedTokenIdempotent;
+  }
+  if (containsBytes(data, getU8Encoder().encode(2), 0)) {
+    return AssociatedTokenInstruction.RecoverNestedAssociatedToken;
+  }
+  throw new Error(
+    'The provided instruction could not be identified as a associatedToken instruction.'
+  );
+}
+
 export type ParsedAssociatedTokenInstruction<
   TProgram extends string = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
 > =

+ 67 - 0
clients/js/test/createAssociatedToken.test.ts

@@ -0,0 +1,67 @@
+import {
+  Account,
+  appendTransactionMessageInstruction,
+  generateKeyPairSigner,
+  none,
+  pipe,
+} from '@solana/web3.js';
+import test from 'ava';
+import {
+  AccountState,
+  TOKEN_PROGRAM_ADDRESS,
+  Token,
+  fetchToken,
+  findAssociatedTokenPda,
+  getCreateAssociatedTokenInstructionAsync,
+} from '../src';
+import {
+  createDefaultSolanaClient,
+  createDefaultTransaction,
+  createMint,
+  generateKeyPairSignerWithSol,
+  signAndSendTransaction,
+} from './_setup';
+
+test('it creates a new associated token account', async (t) => {
+  // Given a mint account, its mint authority and a token owner.
+  const client = createDefaultSolanaClient();
+  const [payer, mintAuthority, owner] = await Promise.all([
+    generateKeyPairSignerWithSol(client),
+    generateKeyPairSigner(),
+    generateKeyPairSigner(),
+  ]);
+  const mint = await createMint(client, payer, mintAuthority.address);
+
+  // When we create and initialize a token account at this address.
+  const createAta = await getCreateAssociatedTokenInstructionAsync({
+    payer,
+    mint,
+    owner: owner.address,
+  });
+
+  await pipe(
+    await createDefaultTransaction(client, payer),
+    (tx) => appendTransactionMessageInstruction(createAta, tx),
+    (tx) => signAndSendTransaction(client, tx)
+  );
+
+  // Then we expect the token account to exist and have the following data.
+  const [ata] = await findAssociatedTokenPda({
+    mint,
+    owner: owner.address,
+    tokenProgram: TOKEN_PROGRAM_ADDRESS,
+  });
+  t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
+    address: ata,
+    data: {
+      mint,
+      owner: owner.address,
+      amount: 0n,
+      delegate: none(),
+      state: AccountState.Initialized,
+      isNative: none(),
+      delegatedAmount: 0n,
+      closeAuthority: none(),
+    },
+  });
+});

+ 67 - 0
clients/js/test/createAssociatedTokenIdempotent.test.ts

@@ -0,0 +1,67 @@
+import {
+  Account,
+  appendTransactionMessageInstruction,
+  generateKeyPairSigner,
+  none,
+  pipe,
+} from '@solana/web3.js';
+import test from 'ava';
+import {
+  AccountState,
+  TOKEN_PROGRAM_ADDRESS,
+  Token,
+  fetchToken,
+  findAssociatedTokenPda,
+  getCreateAssociatedTokenIdempotentInstructionAsync,
+} from '../src';
+import {
+  createDefaultSolanaClient,
+  createDefaultTransaction,
+  createMint,
+  generateKeyPairSignerWithSol,
+  signAndSendTransaction,
+} from './_setup';
+
+test('it creates a new associated token account', async (t) => {
+  // Given a mint account, its mint authority and a token owner.
+  const client = createDefaultSolanaClient();
+  const [payer, mintAuthority, owner] = await Promise.all([
+    generateKeyPairSignerWithSol(client),
+    generateKeyPairSigner(),
+    generateKeyPairSigner(),
+  ]);
+  const mint = await createMint(client, payer, mintAuthority.address);
+
+  // When we create and initialize a token account at this address.
+  const createAta = await getCreateAssociatedTokenIdempotentInstructionAsync({
+    payer,
+    mint,
+    owner: owner.address,
+  });
+
+  await pipe(
+    await createDefaultTransaction(client, payer),
+    (tx) => appendTransactionMessageInstruction(createAta, tx),
+    (tx) => signAndSendTransaction(client, tx)
+  );
+
+  // Then we expect the token account to exist and have the following data.
+  const [ata] = await findAssociatedTokenPda({
+    mint,
+    owner: owner.address,
+    tokenProgram: TOKEN_PROGRAM_ADDRESS,
+  });
+  t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
+    address: ata,
+    data: {
+      mint,
+      owner: owner.address,
+      amount: 0n,
+      delegate: none(),
+      state: AccountState.Initialized,
+      isNative: none(),
+      delegatedAmount: 0n,
+      closeAuthority: none(),
+    },
+  });
+});

+ 63 - 3
program/idl.json

@@ -2384,7 +2384,27 @@
               }
             }
           ],
-          "arguments": [],
+          "arguments": [
+            {
+              "kind": "instructionArgumentNode",
+              "name": "discriminator",
+              "type": {
+                "kind": "numberTypeNode",
+                "format": "u8",
+                "endian": "le"
+              },
+              "docs": [],
+              "defaultValue": { "kind": "numberValueNode", "number": 0 },
+              "defaultValueStrategy": "omitted"
+            }
+          ],
+          "discriminators": [
+            {
+              "kind": "fieldDiscriminatorNode",
+              "name": "discriminator",
+              "offset": 0
+            }
+          ],
           "name": "createAssociatedToken",
           "docs": [
             "Creates an associated token account for the given wallet address and",
@@ -2486,7 +2506,27 @@
               }
             }
           ],
-          "arguments": [],
+          "arguments": [
+            {
+              "kind": "instructionArgumentNode",
+              "name": "discriminator",
+              "type": {
+                "kind": "numberTypeNode",
+                "format": "u8",
+                "endian": "le"
+              },
+              "docs": [],
+              "defaultValue": { "kind": "numberValueNode", "number": 1 },
+              "defaultValueStrategy": "omitted"
+            }
+          ],
+          "discriminators": [
+            {
+              "kind": "fieldDiscriminatorNode",
+              "name": "discriminator",
+              "offset": 0
+            }
+          ],
           "name": "createAssociatedTokenIdempotent",
           "docs": [
             "Creates an associated token account for the given wallet address and",
@@ -2662,7 +2702,27 @@
               }
             }
           ],
-          "arguments": [],
+          "arguments": [
+            {
+              "kind": "instructionArgumentNode",
+              "name": "discriminator",
+              "type": {
+                "kind": "numberTypeNode",
+                "format": "u8",
+                "endian": "le"
+              },
+              "docs": [],
+              "defaultValue": { "kind": "numberValueNode", "number": 2 },
+              "defaultValueStrategy": "omitted"
+            }
+          ],
+          "discriminators": [
+            {
+              "kind": "fieldDiscriminatorNode",
+              "name": "discriminator",
+              "offset": 0
+            }
+          ],
           "name": "recoverNestedAssociatedToken",
           "docs": [
             "Transfers from and closes a nested associated token account: an",