Skip to content

Commit

Permalink
token-js: Added support for transfer hook instructions (#5096)
Browse files Browse the repository at this point in the history
* Update extensionType.ts

* Added support for transfer hook in @solana/spl-token js client

* Renamed programId to transferHookProgramId for clarity

* Added the TransferHookAccount extension type
  • Loading branch information
wjthieme authored Aug 24, 2023
1 parent 47f09c9 commit 0b720f9
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 2 deletions.
67 changes: 65 additions & 2 deletions docs/src/token-2022/extensions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,60 @@ Signature: 3ug4Ejs16jJgEm1WyBwDDxzh9xqPzQ3a2cmy1hSYiPFcLQi9U12HYF1Dbhzb2bx75SSyd
</TabItem>
<TabItem value="jsx" label="JS">

Coming soon!
```jsx
import {
clusterApiUrl,
sendAndConfirmTransaction,
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction,
LAMPORTS_PER_SOL,
} from '@solana/web3.js';

import {
ExtensionType,
createInitializeMintInstruction,
createInitializeTransferHookInstruction,
mintTo,
createAccount,
getMintLen,
TOKEN_2022_PROGRAM_ID,
} from '../src';

(async () => {
const payer = Keypair.generate();

const mintAuthority = Keypair.generate();
const mintKeypair = Keypair.generate();
const mint = mintKeypair.publicKey;

const extensions = [ExtensionType.TransferHook];
const mintLen = getMintLen(extensions);
const decimals = 9;
const transferHookProgramId = new PublicKey('7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj')

const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
const mintTransaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint,
space: mintLen,
lamports: mintLamports,
programId: TOKEN_2022_PROGRAM_ID,
}),
createInitializeTransferHookInstruction(mint, payer.publicKey, transferHookProgramId, TOKEN_2022_PROGRAM_ID),
createInitializeMintInstruction(mint, decimals, mintAuthority.publicKey, null, TOKEN_2022_PROGRAM_ID)
);
await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
})();
```

</TabItem>
</Tabs>
Expand All @@ -1407,7 +1460,17 @@ Signature: 3Ffw6yjseDsL3Az5n2LjdwXXwVPYxDF3JUU1JC1KGAEb1LE68S9VN4ebtAyvKeYMHvhjd
</TabItem>
<TabItem value="jsx" label="JS">

Coming soon!
```js
await updateTransferHook(
connection,
payer, mint,
newTransferHookProgramId,
payer.publicKey,
[],
undefined,
TOKEN_2022_PROGRAM_ID
);
```

</TabItem>
</Tabs>
63 changes: 63 additions & 0 deletions token/js/examples/transferHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
clusterApiUrl,
sendAndConfirmTransaction,
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction,
LAMPORTS_PER_SOL,
} from '@solana/web3.js';

import {
ExtensionType,
createInitializeMintInstruction,
createInitializeTransferHookInstruction,
getMintLen,
TOKEN_2022_PROGRAM_ID,
updateTransferHook,
} from '../src';

(async () => {
const payer = Keypair.generate();

const mintAuthority = Keypair.generate();
const mintKeypair = Keypair.generate();
const mint = mintKeypair.publicKey;

const extensions = [ExtensionType.TransferHook];
const mintLen = getMintLen(extensions);
const decimals = 9;
const transferHookPogramId = new PublicKey('7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj');
const newTransferHookProgramId = new PublicKey('7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj');

const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });

const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
const mintTransaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint,
space: mintLen,
lamports: mintLamports,
programId: TOKEN_2022_PROGRAM_ID,
}),
createInitializeTransferHookInstruction(mint, payer.publicKey, transferHookPogramId, TOKEN_2022_PROGRAM_ID),
createInitializeMintInstruction(mint, decimals, mintAuthority.publicKey, null, TOKEN_2022_PROGRAM_ID)
);
await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);

await updateTransferHook(
connection,
payer,
mint,
newTransferHookProgramId,
payer.publicKey,
[],
undefined,
TOKEN_2022_PROGRAM_ID
);
})();
14 changes: 14 additions & 0 deletions token/js/src/extensions/extensionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MINT_CLOSE_AUTHORITY_SIZE } from './mintCloseAuthority.js';
import { NON_TRANSFERABLE_SIZE, NON_TRANSFERABLE_ACCOUNT_SIZE } from './nonTransferable.js';
import { PERMANENT_DELEGATE_SIZE } from './permanentDelegate.js';
import { TRANSFER_FEE_AMOUNT_SIZE, TRANSFER_FEE_CONFIG_SIZE } from './transferFee/index.js';
import { TRANSFER_HOOK_ACCOUNT_SIZE, TRANSFER_HOOK_SIZE } from './transferHook/index.js';

export enum ExtensionType {
Uninitialized,
Expand All @@ -28,6 +29,8 @@ export enum ExtensionType {
CpiGuard,
PermanentDelegate,
NonTransferableAccount,
TransferHook,
TransferHookAccount,
}

export const TYPE_SIZE = 2;
Expand Down Expand Up @@ -65,6 +68,10 @@ export function getTypeLen(e: ExtensionType): number {
return PERMANENT_DELEGATE_SIZE;
case ExtensionType.NonTransferableAccount:
return NON_TRANSFERABLE_ACCOUNT_SIZE;
case ExtensionType.TransferHook:
return TRANSFER_HOOK_SIZE;
case ExtensionType.TransferHookAccount:
return TRANSFER_HOOK_ACCOUNT_SIZE;
default:
throw Error(`Unknown extension type: ${e}`);
}
Expand All @@ -79,6 +86,7 @@ export function isMintExtension(e: ExtensionType): boolean {
case ExtensionType.NonTransferable:
case ExtensionType.InterestBearingConfig:
case ExtensionType.PermanentDelegate:
case ExtensionType.TransferHook:
return true;
case ExtensionType.Uninitialized:
case ExtensionType.TransferFeeAmount:
Expand All @@ -87,6 +95,7 @@ export function isMintExtension(e: ExtensionType): boolean {
case ExtensionType.MemoTransfer:
case ExtensionType.CpiGuard:
case ExtensionType.NonTransferableAccount:
case ExtensionType.TransferHookAccount:
return false;
default:
throw Error(`Unknown extension type: ${e}`);
Expand All @@ -101,6 +110,7 @@ export function isAccountExtension(e: ExtensionType): boolean {
case ExtensionType.MemoTransfer:
case ExtensionType.CpiGuard:
case ExtensionType.NonTransferableAccount:
case ExtensionType.TransferHookAccount:
return true;
case ExtensionType.Uninitialized:
case ExtensionType.TransferFeeConfig:
Expand All @@ -110,6 +120,7 @@ export function isAccountExtension(e: ExtensionType): boolean {
case ExtensionType.NonTransferable:
case ExtensionType.InterestBearingConfig:
case ExtensionType.PermanentDelegate:
case ExtensionType.TransferHook:
return false;
default:
throw Error(`Unknown extension type: ${e}`);
Expand All @@ -124,6 +135,8 @@ export function getAccountTypeOfMintType(e: ExtensionType): ExtensionType {
return ExtensionType.ConfidentialTransferAccount;
case ExtensionType.NonTransferable:
return ExtensionType.NonTransferableAccount;
case ExtensionType.TransferHook:
return ExtensionType.TransferHookAccount;
case ExtensionType.TransferFeeAmount:
case ExtensionType.ConfidentialTransferAccount:
case ExtensionType.CpiGuard:
Expand All @@ -135,6 +148,7 @@ export function getAccountTypeOfMintType(e: ExtensionType): ExtensionType {
case ExtensionType.InterestBearingConfig:
case ExtensionType.PermanentDelegate:
case ExtensionType.NonTransferableAccount:
case ExtensionType.TransferHookAccount:
return ExtensionType.Uninitialized;
}
}
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 @@ -9,3 +9,4 @@ export * from './mintCloseAuthority.js';
export * from './nonTransferable.js';
export * from './transferFee/index.js';
export * from './permanentDelegate.js';
export * from './transferHook/index.js';
67 changes: 67 additions & 0 deletions token/js/src/extensions/transferHook/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { ConfirmOptions, Connection, PublicKey, Signer, TransactionSignature } from '@solana/web3.js';
import { sendAndConfirmTransaction, Transaction } from '@solana/web3.js';
import { getSigners } from '../../actions/internal.js';
import { TOKEN_2022_PROGRAM_ID } from '../../constants.js';
import { createInitializeTransferHookInstruction, createUpdateTransferHookInstruction } from './instructions.js';

/**
* Initialize a transfer hook on a mint
*
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param mint Mint to initialize with extension
* @param authority Transfer hook authority account
* @param transferHookProgramId The transfer hook program account
* @param confirmOptions Options for confirming the transaction
* @param programId SPL Token program account
*
* @return Signature of the confirmed transaction
*/
export async function initializeTransferHook(
connection: Connection,
payer: Signer,
mint: PublicKey,
authority: PublicKey,
transferHookProgramId: PublicKey,
confirmOptions?: ConfirmOptions,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TransactionSignature> {
const transaction = new Transaction().add(
createInitializeTransferHookInstruction(mint, authority, transferHookProgramId, programId)
);

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

/**
* Update the transfer hook program on a mint
*
* @param connection Connection to use
* @param payer Payer of the transaction fees
* @param mint Mint to modify
* @param transferHookProgramId New transfer hook program account
* @param authority Transfer hook update authority
* @param multiSigners Signing accounts if `freezeAuthority` 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 updateTransferHook(
connection: Connection,
payer: Signer,
mint: PublicKey,
transferHookProgramId: PublicKey,
authority: PublicKey,
multiSigners: Signer[] = [],
confirmOptions?: ConfirmOptions,
programId = TOKEN_2022_PROGRAM_ID
): Promise<TransactionSignature> {
const [authorityPublicKey, signers] = getSigners(authority, multiSigners);

const transaction = new Transaction().add(
createUpdateTransferHookInstruction(mint, authorityPublicKey, transferHookProgramId, signers, programId)
);

return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
}
3 changes: 3 additions & 0 deletions token/js/src/extensions/transferHook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './actions.js';
export * from './instructions.js';
export * from './state.js';
Loading

0 comments on commit 0b720f9

Please sign in to comment.