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
48 changes: 0 additions & 48 deletions modules/sdk-coin-sol/src/config/token2022StaticConfig.ts

This file was deleted.

21 changes: 21 additions & 0 deletions modules/sdk-coin-sol/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ export interface Transfer {
};
}

/**
* Extra account metadata required by a Token-2022 Transfer Hook.
*
* These are resolved live (in the order the hook's ExtraAccountMetaList requires)
* and supplied to the instruction factory. See {@link TokenTransfer}.
*/
export interface ExtraAccountMeta {
/** The base58-encoded public key of the account */
pubkey: string;
/** Whether the account must sign the transaction */
isSigner: boolean;
/** Whether the account is writable */
isWritable: boolean;
}

export interface TokenTransfer {
type: InstructionBuilderTypes.TokenTransfer;
params: {
Expand All @@ -91,6 +106,12 @@ export interface TokenTransfer {
programId?: string;
/** Withheld transfer fee in raw base units */
fee?: string;
/**
* Resolved Transfer Hook extra account metas, in the exact order the hook
* requires. Only used for Token-2022 transfers whose mint has a Transfer
* Hook extension; resolved live by the caller (offline builders never fetch).
*/
transferHookAccounts?: ExtraAccountMeta[];
};
}

Expand Down
30 changes: 30 additions & 0 deletions modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
AtaInit,
AtaRecoverNested,
Burn,
ExtraAccountMeta,
InstructionParams,
Memo,
MintTo,
Expand Down Expand Up @@ -237,6 +238,12 @@ function parseSendInstructions(
if (instruction.programId) {
programIDForTokenTransfer = instruction.programId.toString();
}
const transferHookAccounts = findTransferHookAccounts(
ttKeys.owner.pubkey.toString(),
ttKeys.destination.pubkey.toString(),
tokenAddress,
instructionMetadata
);
const tokenTransfer: TokenTransfer = {
type: InstructionBuilderTypes.TokenTransfer,
params: {
Expand All @@ -249,6 +256,7 @@ function parseSendInstructions(
programId: programIDForTokenTransfer,
decimalPlaces: ttDecimals,
...(ttFee !== undefined ? { fee: ttFee } : {}),
...(transferHookAccounts ? { transferHookAccounts } : {}),
},
};
instructionData.push(tokenTransfer);
Expand Down Expand Up @@ -1334,3 +1342,25 @@ export function findTokenName(

return token;
}

export function findTransferHookAccounts(
fromAddress: string,
toAddress: string,
tokenAddress: string,
instructionMetadata?: InstructionParams[]
): ExtraAccountMeta[] | undefined {
let transferHookAccounts: ExtraAccountMeta[] | undefined;

instructionMetadata?.forEach((instruction) => {
if (
instruction.type === InstructionBuilderTypes.TokenTransfer &&
instruction.params.tokenAddress === tokenAddress &&
instruction.params.fromAddress === fromAddress &&
instruction.params.toAddress === toAddress
) {
transferHookAccounts = instruction.params.transferHookAccounts;
}
});

return transferHookAccounts;
}
32 changes: 13 additions & 19 deletions modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
AtaClose,
AtaInit,
AtaRecoverNested,
ExtraAccountMeta,
InstructionParams,
Memo,
MintTo,
Expand All @@ -50,7 +51,6 @@ import {
} from './iface';
import { computeTransferFee, getSolTokenFromTokenName, isValidBase64, isValidHex } from './utils';
import { depositSolInstructions, withdrawStakeInstructions } from './jitoStakePoolOperations';
import { getToken2022Config, TransferHookConfig } from './token2022Config';

/**
* Construct Solana instructions from instructions params
Expand Down Expand Up @@ -245,10 +245,10 @@ function tokenTransferInstruction(data: TokenTransfer): TransactionInstruction[]
TOKEN_2022_PROGRAM_ID
);
}
// Check if this token has a transfer hook configuration
const tokenConfig = getToken2022Config(tokenAddress);
if (tokenConfig?.transferHook) {
addTransferHookAccounts(transferInstruction, tokenConfig.transferHook);
// Append any resolved Transfer Hook extra accounts. These are resolved live by
// the caller (offline builders never fetch) and supplied in the required order.
if (data.params.transferHookAccounts?.length) {
addTransferHookAccounts(transferInstruction, data.params.transferHookAccounts);
}
} else {
transferInstruction = createTransferCheckedInstruction(
Expand Down Expand Up @@ -787,22 +787,16 @@ function upsertAccountMeta(keys: AccountMeta[], meta: AccountMeta): void {
}
}

function buildStaticTransferHookAccounts(transferHook: TransferHookConfig): AccountMeta[] {
const metas: AccountMeta[] = [];
if (transferHook.extraAccountMetas?.length) {
for (const meta of transferHook.extraAccountMetas) {
metas.push({
pubkey: new PublicKey(meta.pubkey),
isSigner: meta.isSigner,
isWritable: meta.isWritable,
});
}
}
return metas;
function buildTransferHookAccountMetas(extraAccountMetas: ExtraAccountMeta[]): AccountMeta[] {
return extraAccountMetas.map((meta) => ({
pubkey: new PublicKey(meta.pubkey),
isSigner: meta.isSigner,
isWritable: meta.isWritable,
}));
}

function addTransferHookAccounts(instruction: TransactionInstruction, transferHook: TransferHookConfig): void {
const extraMetas = buildStaticTransferHookAccounts(transferHook);
function addTransferHookAccounts(instruction: TransactionInstruction, extraAccountMetas: ExtraAccountMeta[]): void {
const extraMetas = buildTransferHookAccountMetas(extraAccountMetas);
for (const meta of extraMetas) {
upsertAccountMeta(instruction.keys, meta);
}
Expand Down
56 changes: 0 additions & 56 deletions modules/sdk-coin-sol/src/lib/token2022Config.ts

This file was deleted.

19 changes: 18 additions & 1 deletion modules/sdk-coin-sol/src/lib/tokenTransferBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
validateOwnerAddress,
} from './utils';
import { InstructionBuilderTypes } from './constants';
import { AtaInit, TokenAssociateRecipient, TokenTransfer, SetPriorityFee } from './iface';
import { AtaInit, ExtraAccountMeta, TokenAssociateRecipient, TokenTransfer, SetPriorityFee } from './iface';
import assert from 'assert';
import { TransactionBuilder } from './transactionBuilder';
import _ from 'lodash';
Expand All @@ -29,12 +29,28 @@ const UNSIGNED_BIGINT_MAX = BigInt('18446744073709551615');
export class TokenTransferBuilder extends TransactionBuilder {
private _sendParams: SendParams[] = [];
private _createAtaParams: TokenAssociateRecipient[];
private _transferHookAccounts?: ExtraAccountMeta[];

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._createAtaParams = [];
}

/**
* Set the resolved Token-2022 Transfer Hook extra account metas for this transfer.
*
* These must be resolved live by the caller (e.g. via `Sol.resolveTransferHookAccounts`)
* since builders remain offline and never perform RPC. The order is significant and
* must match the hook's ExtraAccountMetaList.
*
* @param {ExtraAccountMeta[]} metas - resolved extra account metas, in hook order
* @returns {TokenTransferBuilder} This transaction builder
*/
transferHookAccounts(metas: ExtraAccountMeta[]): this {
this._transferHookAccounts = metas;
return this;
}

protected get transactionType(): TransactionType {
return TransactionType.Send;
}
Expand Down Expand Up @@ -153,6 +169,7 @@ export class TokenTransferBuilder extends TransactionBuilder {
tokenAddress: tokenAddress,
programId: programId,
decimalPlaces: decimals,
...(this._transferHookAccounts ? { transferHookAccounts: this._transferHookAccounts } : {}),
},
};
})
Expand Down
19 changes: 18 additions & 1 deletion modules/sdk-coin-sol/src/lib/transferBuilderV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from './utils';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import assert from 'assert';
import { AtaInit, TokenAssociateRecipient, TokenTransfer, Transfer, SetPriorityFee } from './iface';
import { AtaInit, ExtraAccountMeta, TokenAssociateRecipient, TokenTransfer, Transfer, SetPriorityFee } from './iface';
import { InstructionBuilderTypes } from './constants';
import _ from 'lodash';

Expand All @@ -29,11 +29,27 @@ const UNSIGNED_BIGINT_MAX = BigInt('18446744073709551615');
export class TransferBuilderV2 extends TransactionBuilder {
private _sendParams: SendParams[] = [];
private _createAtaParams: TokenAssociateRecipient[];
private _transferHookAccounts?: ExtraAccountMeta[];
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._createAtaParams = [];
}

/**
* Set the resolved Token-2022 Transfer Hook extra account metas for this transfer.
*
* These must be resolved live by the caller (e.g. via `Sol.resolveTransferHookAccounts`)
* since builders remain offline and never perform RPC. The order is significant and
* must match the hook's ExtraAccountMetaList.
*
* @param {ExtraAccountMeta[]} metas - resolved extra account metas, in hook order
* @returns {TransferBuilderV2} This transaction builder
*/
transferHookAccounts(metas: ExtraAccountMeta[]): this {
this._transferHookAccounts = metas;
return this;
}

protected get transactionType(): TransactionType {
return TransactionType.Send;
}
Expand Down Expand Up @@ -164,6 +180,7 @@ export class TransferBuilderV2 extends TransactionBuilder {
tokenAddress: tokenAddress,
programId: programId,
decimalPlaces: decimals,
...(this._transferHookAccounts ? { transferHookAccounts: this._transferHookAccounts } : {}),
},
};
} else {
Expand Down
Loading
Loading