@bitgo/wasm-solana 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,311 +0,0 @@
1
- "use strict";
2
- /**
3
- * High-level transaction explanation.
4
- *
5
- * Builds on top of `parseTransaction` (WASM) to provide a structured
6
- * "explain" view of a Solana transaction: type, outputs, inputs, fee, etc.
7
- *
8
- * The WASM parser returns raw individual instructions. This module combines
9
- * related instruction sequences into higher-level operations and derives the
10
- * overall transaction type.
11
- */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.TransactionType = void 0;
14
- exports.explainTransaction = explainTransaction;
15
- const parser_js_1 = require("./parser.js");
16
- // =============================================================================
17
- // Public types
18
- // =============================================================================
19
- var TransactionType;
20
- (function (TransactionType) {
21
- TransactionType["Send"] = "Send";
22
- TransactionType["StakingActivate"] = "StakingActivate";
23
- TransactionType["StakingDeactivate"] = "StakingDeactivate";
24
- TransactionType["StakingWithdraw"] = "StakingWithdraw";
25
- TransactionType["StakingAuthorize"] = "StakingAuthorize";
26
- TransactionType["StakingDelegate"] = "StakingDelegate";
27
- TransactionType["WalletInitialization"] = "WalletInitialization";
28
- TransactionType["AssociatedTokenAccountInitialization"] = "AssociatedTokenAccountInitialization";
29
- })(TransactionType || (exports.TransactionType = TransactionType = {}));
30
- /** Solana base fee per signature (protocol constant). */
31
- const DEFAULT_LAMPORTS_PER_SIGNATURE = 5000n;
32
- /**
33
- * Scan for multi-instruction patterns that should be combined:
34
- *
35
- * 1. CreateAccount + StakeInitialize [+ StakingDelegate] → StakingActivate
36
- * - With Delegate following = NATIVE staking
37
- * - Without Delegate = MARINADE staking (Marinade's program handles delegation)
38
- * 2. CreateAccount + NonceInitialize → WalletInitialization
39
- * - BitGo creates a nonce account during wallet initialization
40
- */
41
- function detectCombinedPattern(instructions) {
42
- for (let i = 0; i < instructions.length - 1; i++) {
43
- const curr = instructions[i];
44
- const next = instructions[i + 1];
45
- if (curr.type === "CreateAccount" && next.type === "StakeInitialize") {
46
- return {
47
- kind: "StakingActivate",
48
- fromAddress: curr.fromAddress,
49
- stakingAddress: curr.newAddress,
50
- amount: curr.amount,
51
- };
52
- }
53
- if (curr.type === "CreateAccount" && next.type === "NonceInitialize") {
54
- return {
55
- kind: "WalletInitialization",
56
- fromAddress: curr.fromAddress,
57
- nonceAddress: curr.newAddress,
58
- amount: curr.amount,
59
- };
60
- }
61
- }
62
- return null;
63
- }
64
- // =============================================================================
65
- // Transaction type derivation
66
- // =============================================================================
67
- const BOILERPLATE_TYPES = new Set([
68
- "NonceAdvance",
69
- "Memo",
70
- "SetComputeUnitLimit",
71
- "SetPriorityFee",
72
- ]);
73
- function deriveTransactionType(instructions, combined, memo) {
74
- if (combined)
75
- return TransactionType[combined.kind];
76
- // Marinade deactivate: Transfer + memo containing "PrepareForRevoke"
77
- if (memo?.includes("PrepareForRevoke"))
78
- return TransactionType.StakingDeactivate;
79
- // Jito pool operations map to staking types
80
- if (instructions.some((i) => i.type === "StakePoolDepositSol"))
81
- return TransactionType.StakingActivate;
82
- if (instructions.some((i) => i.type === "StakePoolWithdrawStake"))
83
- return TransactionType.StakingDeactivate;
84
- // ATA-only transactions (ignoring boilerplate like nonce/memo/compute budget)
85
- const meaningful = instructions.filter((i) => !BOILERPLATE_TYPES.has(i.type));
86
- if (meaningful.length > 0 && meaningful.every((i) => i.type === "CreateAssociatedTokenAccount")) {
87
- return TransactionType.AssociatedTokenAccountInitialization;
88
- }
89
- // For staking instructions, the instruction type IS the transaction type
90
- const staking = instructions.find((i) => i.type in TransactionType);
91
- if (staking)
92
- return TransactionType[staking.type];
93
- return TransactionType.Send;
94
- }
95
- // =============================================================================
96
- // Transaction ID extraction
97
- // =============================================================================
98
- // Base58 encoding of 64 zero bytes. Unsigned transactions have all-zero
99
- // signatures which encode to this constant.
100
- const ALL_ZEROS_BASE58 = "1111111111111111111111111111111111111111111111111111111111111111";
101
- function extractTransactionId(signatures) {
102
- const sig = signatures[0];
103
- if (!sig || sig === ALL_ZEROS_BASE58)
104
- return undefined;
105
- return sig;
106
- }
107
- // =============================================================================
108
- // Main export
109
- // =============================================================================
110
- /**
111
- * Explain a Solana transaction.
112
- *
113
- * Takes raw transaction bytes and fee parameters, then returns a structured
114
- * explanation including transaction type, outputs, inputs, fee, memo, and
115
- * associated-token-account owner mappings.
116
- *
117
- * @param input - Raw transaction bytes (caller is responsible for decoding base64/hex)
118
- * @param options - Fee parameters for calculating the total fee
119
- * @returns An ExplainedTransaction with all fields populated
120
- *
121
- * @example
122
- * ```typescript
123
- * import { explainTransaction } from '@bitgo/wasm-solana';
124
- *
125
- * const txBytes = Buffer.from(txBase64, 'base64');
126
- * const explained = explainTransaction(txBytes, {
127
- * lamportsPerSignature: 5000n,
128
- * tokenAccountRentExemptAmount: 2039280n,
129
- * });
130
- * console.log(explained.type); // "Send", "StakingActivate", etc.
131
- * ```
132
- */
133
- function explainTransaction(input, options) {
134
- const { lamportsPerSignature, tokenAccountRentExemptAmount } = options;
135
- const parsed = (0, parser_js_1.parseTransactionData)(input);
136
- // --- Transaction ID ---
137
- const id = extractTransactionId(parsed.signatures);
138
- // --- Fee calculation ---
139
- // Base fee = numSignatures × lamportsPerSignature
140
- let fee = BigInt(parsed.numSignatures) *
141
- (lamportsPerSignature !== undefined
142
- ? BigInt(lamportsPerSignature)
143
- : DEFAULT_LAMPORTS_PER_SIGNATURE);
144
- // Each CreateAssociatedTokenAccount instruction creates a new token account,
145
- // which requires a rent-exempt deposit. Add that to the fee.
146
- const ataCount = parsed.instructionsData.filter((i) => i.type === "CreateAssociatedTokenAccount").length;
147
- if (ataCount > 0 && tokenAccountRentExemptAmount !== undefined) {
148
- fee += BigInt(ataCount) * BigInt(tokenAccountRentExemptAmount);
149
- }
150
- // --- Extract memo (needed before type derivation) ---
151
- let memo;
152
- for (const instr of parsed.instructionsData) {
153
- if (instr.type === "Memo") {
154
- memo = instr.memo;
155
- }
156
- }
157
- // --- Detect combined instruction patterns ---
158
- const combined = detectCombinedPattern(parsed.instructionsData);
159
- const txType = deriveTransactionType(parsed.instructionsData, combined, memo);
160
- // Marinade deactivate: Transfer + PrepareForRevoke memo.
161
- // The Transfer is a contract interaction (not a real value transfer),
162
- // so we skip it from outputs.
163
- const isMarinadeDeactivate = txType === TransactionType.StakingDeactivate &&
164
- memo !== undefined &&
165
- memo.includes("PrepareForRevoke");
166
- // --- Extract outputs and inputs ---
167
- const outputs = [];
168
- const inputs = [];
169
- if (combined?.kind === "StakingActivate") {
170
- // Combined native/Marinade staking activate — the staking address receives
171
- // the full amount from the funding account.
172
- outputs.push({
173
- address: combined.stakingAddress,
174
- amount: combined.amount,
175
- });
176
- inputs.push({
177
- address: combined.fromAddress,
178
- value: combined.amount,
179
- });
180
- }
181
- else if (combined?.kind === "WalletInitialization") {
182
- // Wallet initialization — funds the new nonce account.
183
- outputs.push({
184
- address: combined.nonceAddress,
185
- amount: combined.amount,
186
- });
187
- inputs.push({
188
- address: combined.fromAddress,
189
- value: combined.amount,
190
- });
191
- }
192
- else {
193
- // Process individual instructions for outputs/inputs
194
- for (const instr of parsed.instructionsData) {
195
- switch (instr.type) {
196
- case "Transfer":
197
- // Skip Transfer for Marinade deactivate — it's a program interaction,
198
- // not a real value transfer to an external address.
199
- if (isMarinadeDeactivate)
200
- break;
201
- outputs.push({
202
- address: instr.toAddress,
203
- amount: instr.amount,
204
- });
205
- inputs.push({
206
- address: instr.fromAddress,
207
- value: instr.amount,
208
- });
209
- break;
210
- case "TokenTransfer":
211
- outputs.push({
212
- address: instr.toAddress,
213
- amount: instr.amount,
214
- tokenName: instr.tokenAddress,
215
- });
216
- inputs.push({
217
- address: instr.fromAddress,
218
- value: instr.amount,
219
- });
220
- break;
221
- case "StakingActivate":
222
- outputs.push({
223
- address: instr.stakingAddress,
224
- amount: instr.amount,
225
- });
226
- inputs.push({
227
- address: instr.fromAddress,
228
- value: instr.amount,
229
- });
230
- break;
231
- case "StakingWithdraw":
232
- // Withdraw: SOL flows FROM the staking address TO the recipient.
233
- // `fromAddress` is the recipient (where funds go),
234
- // `stakingAddress` is the source.
235
- outputs.push({
236
- address: instr.fromAddress,
237
- amount: instr.amount,
238
- });
239
- inputs.push({
240
- address: instr.stakingAddress,
241
- value: instr.amount,
242
- });
243
- break;
244
- case "StakePoolDepositSol":
245
- // Jito liquid staking: SOL is deposited into the stake pool.
246
- // The funding account is debited; output goes to the pool address.
247
- outputs.push({
248
- address: instr.stakePool,
249
- amount: instr.lamports,
250
- });
251
- inputs.push({
252
- address: instr.fundingAccount,
253
- value: instr.lamports,
254
- });
255
- break;
256
- // StakingDeactivate, StakingAuthorize, StakingDelegate,
257
- // StakePoolWithdrawStake, NonceAdvance, CreateAccount,
258
- // StakeInitialize, NonceInitialize, SetComputeUnitLimit,
259
- // SetPriorityFee, CreateAssociatedTokenAccount,
260
- // CloseAssociatedTokenAccount, Memo, Unknown
261
- // — no value inputs/outputs.
262
- }
263
- }
264
- }
265
- // --- Output amount ---
266
- // Only count native SOL outputs (no tokenName). Token amounts are in different
267
- // denominations and shouldn't be mixed with SOL lamports.
268
- const outputAmount = outputs.filter((o) => !o.tokenName).reduce((sum, o) => sum + o.amount, 0n);
269
- // --- ATA owner mapping and token enablements ---
270
- const ataOwnerMap = {};
271
- const tokenEnablements = [];
272
- for (const instr of parsed.instructionsData) {
273
- if (instr.type === "CreateAssociatedTokenAccount") {
274
- ataOwnerMap[instr.ataAddress] = instr.ownerAddress;
275
- tokenEnablements.push({
276
- address: instr.ataAddress,
277
- mintAddress: instr.mintAddress,
278
- });
279
- }
280
- }
281
- // --- Staking authorize ---
282
- let stakingAuthorize;
283
- for (const instr of parsed.instructionsData) {
284
- if (instr.type === "StakingAuthorize") {
285
- stakingAuthorize = {
286
- stakingAddress: instr.stakingAddress,
287
- oldAuthorizeAddress: instr.oldAuthorizeAddress,
288
- newAuthorizeAddress: instr.newAuthorizeAddress,
289
- authorizeType: instr.authorizeType,
290
- custodianAddress: instr.custodianAddress,
291
- };
292
- break;
293
- }
294
- }
295
- return {
296
- id,
297
- type: txType,
298
- feePayer: parsed.feePayer,
299
- fee,
300
- blockhash: parsed.nonce,
301
- durableNonce: parsed.durableNonce,
302
- outputs,
303
- inputs,
304
- outputAmount,
305
- memo,
306
- ataOwnerMap,
307
- tokenEnablements,
308
- stakingAuthorize,
309
- numSignatures: parsed.numSignatures,
310
- };
311
- }
@@ -1,100 +0,0 @@
1
- /**
2
- * High-level transaction explanation.
3
- *
4
- * Builds on top of `parseTransaction` (WASM) to provide a structured
5
- * "explain" view of a Solana transaction: type, outputs, inputs, fee, etc.
6
- *
7
- * The WASM parser returns raw individual instructions. This module combines
8
- * related instruction sequences into higher-level operations and derives the
9
- * overall transaction type.
10
- */
11
- export declare enum TransactionType {
12
- Send = "Send",
13
- StakingActivate = "StakingActivate",
14
- StakingDeactivate = "StakingDeactivate",
15
- StakingWithdraw = "StakingWithdraw",
16
- StakingAuthorize = "StakingAuthorize",
17
- StakingDelegate = "StakingDelegate",
18
- WalletInitialization = "WalletInitialization",
19
- AssociatedTokenAccountInitialization = "AssociatedTokenAccountInitialization"
20
- }
21
- export interface ExplainOptions {
22
- /** Defaults to 5000 (Solana protocol constant). */
23
- lamportsPerSignature?: bigint | number | string;
24
- tokenAccountRentExemptAmount?: bigint | number | string;
25
- }
26
- export interface ExplainedOutput {
27
- address: string;
28
- amount: bigint;
29
- tokenName?: string;
30
- }
31
- export interface ExplainedInput {
32
- address: string;
33
- value: bigint;
34
- }
35
- export interface TokenEnablement {
36
- /** The ATA address being created */
37
- address: string;
38
- /** The SPL token mint address */
39
- mintAddress: string;
40
- }
41
- export interface StakingAuthorizeInfo {
42
- stakingAddress: string;
43
- oldAuthorizeAddress: string;
44
- newAuthorizeAddress: string;
45
- authorizeType: "Staker" | "Withdrawer";
46
- custodianAddress?: string;
47
- }
48
- export interface ExplainedTransaction {
49
- /** Transaction ID (base58 signature). Undefined if the transaction is unsigned. */
50
- id: string | undefined;
51
- type: TransactionType;
52
- feePayer: string;
53
- fee: bigint;
54
- blockhash: string;
55
- durableNonce?: {
56
- walletNonceAddress: string;
57
- authWalletAddress: string;
58
- };
59
- outputs: ExplainedOutput[];
60
- inputs: ExplainedInput[];
61
- outputAmount: bigint;
62
- memo?: string;
63
- /**
64
- * Maps ATA address → owner address for CreateAssociatedTokenAccount instructions.
65
- * Allows resolving newly-created token account ownership without an external lookup.
66
- */
67
- ataOwnerMap: Record<string, string>;
68
- /**
69
- * Token enablements from CreateAssociatedTokenAccount instructions.
70
- * Contains the ATA address and mint address (consumer resolves token names).
71
- */
72
- tokenEnablements: TokenEnablement[];
73
- /** Staking authorize details, present when the transaction changes stake authority. */
74
- stakingAuthorize?: StakingAuthorizeInfo;
75
- numSignatures: number;
76
- }
77
- /**
78
- * Explain a Solana transaction.
79
- *
80
- * Takes raw transaction bytes and fee parameters, then returns a structured
81
- * explanation including transaction type, outputs, inputs, fee, memo, and
82
- * associated-token-account owner mappings.
83
- *
84
- * @param input - Raw transaction bytes (caller is responsible for decoding base64/hex)
85
- * @param options - Fee parameters for calculating the total fee
86
- * @returns An ExplainedTransaction with all fields populated
87
- *
88
- * @example
89
- * ```typescript
90
- * import { explainTransaction } from '@bitgo/wasm-solana';
91
- *
92
- * const txBytes = Buffer.from(txBase64, 'base64');
93
- * const explained = explainTransaction(txBytes, {
94
- * lamportsPerSignature: 5000n,
95
- * tokenAccountRentExemptAmount: 2039280n,
96
- * });
97
- * console.log(explained.type); // "Send", "StakingActivate", etc.
98
- * ```
99
- */
100
- export declare function explainTransaction(input: Uint8Array, options: ExplainOptions): ExplainedTransaction;