Skip to content

remove statics dependency from sol module #6272

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 4 commits into from
Jun 23, 2025
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
27 changes: 21 additions & 6 deletions modules/sdk-coin-sol/src/lib/ataInitializationBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export class AtaInitializationBuilder extends TransactionBuilder {
this._tokenAssociateRecipients.push({
ownerAddress: ataInitInstruction.params.ownerAddress,
tokenName: ataInitInstruction.params.tokenName,
tokenAddress: ataInitInstruction.params.mintAddress,
programId: ataInitInstruction.params.programId,
});
}
}
Expand Down Expand Up @@ -115,10 +117,15 @@ export class AtaInitializationBuilder extends TransactionBuilder {
}
validateOwnerAddress(recipient.ownerAddress);
const token = getSolTokenFromTokenName(recipient.tokenName);
if (!token) {
let tokenAddress: string;
if (recipient.tokenAddress) {
tokenAddress = recipient.tokenAddress;
} else if (token) {
tokenAddress = token.tokenAddress;
} else {
throw new BuildTransactionError('Invalid transaction: invalid token name, got: ' + recipient.tokenName);
}
validateMintAddress(token.tokenAddress);
validateMintAddress(tokenAddress);

this._tokenAssociateRecipients.push(recipient);
return this;
Expand All @@ -141,25 +148,33 @@ export class AtaInitializationBuilder extends TransactionBuilder {
await Promise.all(
this._tokenAssociateRecipients.map(async (recipient) => {
const token = getSolTokenFromTokenName(recipient.tokenName);
if (!token) {
let tokenAddress: string;
let programId: string;
if (recipient.tokenAddress && recipient.programId) {
tokenAddress = recipient.tokenAddress;
programId = recipient.programId;
} else if (token) {
tokenAddress = token.tokenAddress;
programId = token.programId;
} else {
throw new BuildTransactionError('Invalid transaction: invalid token name, got: ' + recipient.tokenName);
}

// Use the provided ataAddress if it exists, otherwise calculate it
let ataPk = recipient.ataAddress;
if (!ataPk) {
ataPk = await getAssociatedTokenAccountAddress(token.tokenAddress, recipient.ownerAddress);
ataPk = await getAssociatedTokenAccountAddress(tokenAddress, recipient.ownerAddress);
}

this._instructionsData.push({
type: InstructionBuilderTypes.CreateAssociatedTokenAccount,
params: {
mintAddress: token.tokenAddress,
mintAddress: tokenAddress,
ataAddress: ataPk,
ownerAddress: recipient.ownerAddress,
payerAddress: this._sender,
tokenName: recipient.tokenName,
programId: token.programId,
programId: programId,
},
});
})
Expand Down
5 changes: 5 additions & 0 deletions modules/sdk-coin-sol/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export interface TokenTransfer {
amount: string;
tokenName: string;
sourceAddress: string;
tokenAddress?: string;
decimalPlaces?: number;
programId?: string;
};
}

Expand Down Expand Up @@ -182,4 +185,6 @@ export class TokenAssociateRecipient {
ownerAddress: string;
tokenName: string;
ataAddress?: string;
tokenAddress?: string;
programId?: string;
}
69 changes: 59 additions & 10 deletions modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,23 @@ import { getInstructionType } from './utils';
export function instructionParamsFactory(
type: TransactionType,
instructions: TransactionInstruction[],
coinName?: string
coinName?: string,
instructionMetadata?: InstructionParams[],
_useTokenAddressTokenName?: boolean
): InstructionParams[] {
switch (type) {
case TransactionType.WalletInitialization:
return parseWalletInitInstructions(instructions);
case TransactionType.Send:
return parseSendInstructions(instructions);
return parseSendInstructions(instructions, instructionMetadata, _useTokenAddressTokenName);
case TransactionType.StakingActivate:
return parseStakingActivateInstructions(instructions);
case TransactionType.StakingDeactivate:
return parseStakingDeactivateInstructions(instructions, coinName);
case TransactionType.StakingWithdraw:
return parseStakingWithdrawInstructions(instructions);
case TransactionType.AssociatedTokenAccountInitialization:
return parseAtaInitInstructions(instructions);
return parseAtaInitInstructions(instructions, instructionMetadata, _useTokenAddressTokenName);
case TransactionType.CloseAssociatedTokenAccount:
return parseAtaCloseInstructions(instructions);
case TransactionType.StakingAuthorize:
Expand Down Expand Up @@ -120,7 +122,9 @@ function parseWalletInitInstructions(instructions: TransactionInstruction[]): Ar
* @returns {InstructionParams[]} An array containing instruction params for Send tx
*/
function parseSendInstructions(
instructions: TransactionInstruction[]
instructions: TransactionInstruction[],
instructionMetadata?: InstructionParams[],
_useTokenAddressTokenName?: boolean
): Array<Nonce | Memo | Transfer | TokenTransfer | AtaInit | AtaClose | SetPriorityFee> {
const instructionData: Array<Nonce | Memo | Transfer | TokenTransfer | AtaInit | AtaClose | SetPriorityFee> = [];
for (const instruction of instructions) {
Expand Down Expand Up @@ -160,7 +164,12 @@ function parseSendInstructions(
} else {
tokenTransferInstruction = decodeTransferCheckedInstruction(instruction, TOKEN_2022_PROGRAM_ID);
}
const tokenName = findTokenName(tokenTransferInstruction.keys.mint.pubkey.toString());
const tokenAddress = tokenTransferInstruction.keys.mint.pubkey.toString();
const tokenName = findTokenName(tokenAddress, instructionMetadata, _useTokenAddressTokenName);
let programIDForTokenTransfer: string | undefined;
if (instruction.programId) {
programIDForTokenTransfer = instruction.programId.toString();
}
const tokenTransfer: TokenTransfer = {
type: InstructionBuilderTypes.TokenTransfer,
params: {
Expand All @@ -169,13 +178,20 @@ function parseSendInstructions(
amount: tokenTransferInstruction.data.amount.toString(),
tokenName,
sourceAddress: tokenTransferInstruction.keys.source.pubkey.toString(),
tokenAddress: tokenAddress,
programId: programIDForTokenTransfer,
decimalPlaces: tokenTransferInstruction.data.decimals,
},
};
instructionData.push(tokenTransfer);
break;
case ValidInstructionTypesEnum.InitializeAssociatedTokenAccount:
const mintAddress = instruction.keys[ataInitInstructionKeysIndexes.MintAddress].pubkey.toString();
const mintTokenName = findTokenName(mintAddress);
const mintTokenName = findTokenName(mintAddress, instructionMetadata, _useTokenAddressTokenName);
let programID: string | undefined;
if (instruction.programId) {
programID = instruction.programId.toString();
}

const ataInit: AtaInit = {
type: InstructionBuilderTypes.CreateAssociatedTokenAccount,
Expand All @@ -185,6 +201,7 @@ function parseSendInstructions(
ownerAddress: instruction.keys[ataInitInstructionKeysIndexes.OwnerAddress].pubkey.toString(),
payerAddress: instruction.keys[ataInitInstructionKeysIndexes.PayerAddress].pubkey.toString(),
tokenName: mintTokenName,
programId: programID,
},
};
instructionData.push(ataInit);
Expand Down Expand Up @@ -652,7 +669,11 @@ const closeAtaInstructionKeysIndexes = {
* @param {TransactionInstruction[]} instructions - an array of supported Solana instructions
* @returns {InstructionParams[]} An array containing instruction params for Send tx
*/
function parseAtaInitInstructions(instructions: TransactionInstruction[]): Array<AtaInit | Memo | Nonce> {
function parseAtaInitInstructions(
instructions: TransactionInstruction[],
instructionMetadata?: InstructionParams[],
_useTokenAddressTokenName?: boolean
): Array<AtaInit | Memo | Nonce> {
const instructionData: Array<AtaInit | Memo | Nonce> = [];
let memo: Memo | undefined;

Expand All @@ -675,8 +696,11 @@ function parseAtaInitInstructions(instructions: TransactionInstruction[]): Array
break;
case ValidInstructionTypesEnum.InitializeAssociatedTokenAccount:
const mintAddress = instruction.keys[ataInitInstructionKeysIndexes.MintAddress].pubkey.toString();
const tokenName = findTokenName(mintAddress);

const tokenName = findTokenName(mintAddress, instructionMetadata, _useTokenAddressTokenName);
let programID: string | undefined;
if (instruction.programId) {
programID = instruction.programId.toString();
}
const ataInit: AtaInit = {
type: InstructionBuilderTypes.CreateAssociatedTokenAccount,
params: {
Expand All @@ -685,6 +709,7 @@ function parseAtaInitInstructions(instructions: TransactionInstruction[]): Array
ownerAddress: instruction.keys[ataInitInstructionKeysIndexes.OwnerAddress].pubkey.toString(),
payerAddress: instruction.keys[ataInitInstructionKeysIndexes.PayerAddress].pubkey.toString(),
tokenName,
programId: programID,
},
};
instructionData.push(ataInit);
Expand Down Expand Up @@ -831,7 +856,11 @@ function parseStakingAuthorizeRawInstructions(instructions: TransactionInstructi
return instructionData;
}

function findTokenName(mintAddress: string): string {
function findTokenName(
mintAddress: string,
instructionMetadata?: InstructionParams[],
_useTokenAddressTokenName?: boolean
): string {
let token: string | undefined;

coins.forEach((value, key) => {
Expand All @@ -840,6 +869,26 @@ function findTokenName(mintAddress: string): string {
}
});

if (!token && instructionMetadata) {
instructionMetadata.forEach((instruction) => {
if (
instruction.type === InstructionBuilderTypes.CreateAssociatedTokenAccount &&
instruction.params.mintAddress === mintAddress
) {
token = instruction.params.tokenName;
} else if (
instruction.type === InstructionBuilderTypes.TokenTransfer &&
instruction.params.tokenAddress === mintAddress
) {
token = instruction.params.tokenName;
}
});
}

if (!token && _useTokenAddressTokenName) {
token = mintAddress;
Comment on lines +888 to +889
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not pass tokenName undefined and token address as a separate fields and the populate token name and token address both in outputs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the approach was suggested by you, we can't pass the token name as undefined—these instructions are used in multiple places, which could lead to errors due to the undefined value.

}

assert(token);

return token;
Expand Down
32 changes: 24 additions & 8 deletions modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { coins, SolCoin } from '@bitgo/statics';
import { SolCoin } from '@bitgo/statics';
import {
createAssociatedTokenAccountInstruction,
createCloseAccountInstruction,
Expand Down Expand Up @@ -35,6 +35,7 @@ import {
WalletInit,
SetPriorityFee,
} from './iface';
import { getSolTokenFromTokenName } from './utils';

/**
* Construct Solana instructions from instructions params
Expand Down Expand Up @@ -157,28 +158,43 @@ function tokenTransferInstruction(data: TokenTransfer): TransactionInstruction[]
assert(amount, 'Missing amount param');
assert(tokenName, 'Missing token name');
assert(sourceAddress, 'Missing ata address');
const token = coins.get(data.params.tokenName);
assert(token instanceof SolCoin);
const token = getSolTokenFromTokenName(data.params.tokenName);
let tokenAddress: string;
let programId: string | undefined;
let decimalPlaces: number;
if (data.params.tokenAddress && data.params.decimalPlaces) {
tokenAddress = data.params.tokenAddress;
decimalPlaces = data.params.decimalPlaces;
programId = data.params.programId;
} else if (token) {
assert(token instanceof SolCoin);
tokenAddress = token.tokenAddress;
decimalPlaces = token.decimalPlaces;
programId = token.programId;
} else {
throw new Error('Invalid token name, got:' + data.params.tokenName);
}

let transferInstruction: TransactionInstruction;
if (token.programId === TOKEN_2022_PROGRAM_ID.toString()) {
if (programId === TOKEN_2022_PROGRAM_ID.toString()) {
transferInstruction = createTransferCheckedInstruction(
new PublicKey(sourceAddress),
new PublicKey(token.tokenAddress),
new PublicKey(tokenAddress),
new PublicKey(toAddress),
new PublicKey(fromAddress),
BigInt(amount),
token.decimalPlaces,
decimalPlaces,
[],
TOKEN_2022_PROGRAM_ID
);
} else {
transferInstruction = createTransferCheckedInstruction(
new PublicKey(sourceAddress),
new PublicKey(token.tokenAddress),
new PublicKey(tokenAddress),
new PublicKey(toAddress),
new PublicKey(fromAddress),
BigInt(amount),
token.decimalPlaces
decimalPlaces
);
}
return [transferInstruction];
Expand Down
Loading
Loading