22 * @prettier
33 */
44
5+ import { TOKEN_2022_PROGRAM_ID , TOKEN_PROGRAM_ID } from '@solana/spl-token' ;
56import BigNumber from 'bignumber.js' ;
67import * as base58 from 'bs58' ;
8+ import * as _ from 'lodash' ;
9+ import * as request from 'superagent' ;
710
811import {
12+ AuditDecryptedKeyParams ,
913 BaseBroadcastTransactionOptions ,
1014 BaseBroadcastTransactionResult ,
1115 BaseCoin ,
@@ -16,6 +20,7 @@ import {
1620 EDDSAMethods ,
1721 EDDSAMethodTypes ,
1822 Environments ,
23+ ITokenEnablement ,
1924 KeyPair ,
2025 Memo ,
2126 MethodNotImplementedError ,
@@ -27,29 +32,28 @@ import {
2732 MPCTx ,
2833 MPCTxs ,
2934 MPCUnsignedTx ,
35+ MultisigType ,
36+ multisigTypes ,
3037 OvcInput ,
3138 OvcOutput ,
3239 ParsedTransaction ,
40+ PopulatedIntent ,
41+ PrebuildTransactionWithIntentOptions ,
3342 PresignTransactionOptions ,
3443 PublicKey ,
3544 RecoveryTxRequest ,
3645 SignedTransaction ,
3746 SignTransactionOptions ,
47+ TokenEnablement ,
3848 TokenEnablementConfig ,
3949 TransactionExplanation ,
50+ TransactionParams ,
4051 TransactionRecipient ,
4152 VerifyAddressOptions ,
4253 VerifyTransactionOptions ,
43- MultisigType ,
44- multisigTypes ,
45- AuditDecryptedKeyParams ,
46- PopulatedIntent ,
47- PrebuildTransactionWithIntentOptions ,
4854} from '@bitgo/sdk-core' ;
4955import { auditEddsaPrivateKey , getDerivationPath } from '@bitgo/sdk-lib-mpc' ;
50- import { BaseNetwork , CoinFamily , coins , BaseCoin as StaticsBaseCoin } from '@bitgo/statics' ;
51- import * as _ from 'lodash' ;
52- import * as request from 'superagent' ;
56+ import { BaseNetwork , CoinFamily , coins , SolCoin , BaseCoin as StaticsBaseCoin } from '@bitgo/statics' ;
5357import { KeyPair as SolKeyPair , Transaction , TransactionBuilder , TransactionBuilderFactory } from './lib' ;
5458import {
5559 getAssociatedTokenAccountAddress ,
@@ -60,7 +64,6 @@ import {
6064 isValidPublicKey ,
6165 validateRawTransaction ,
6266} from './lib/utils' ;
63- import { TOKEN_2022_PROGRAM_ID , TOKEN_PROGRAM_ID } from '@solana/spl-token' ;
6467
6568export const DEFAULT_SCAN_FACTOR = 20 ; // default number of receive addresses to scan for funds
6669
@@ -173,6 +176,7 @@ export interface SolConsolidationRecoveryOptions extends MPCConsolidationRecover
173176}
174177
175178const HEX_REGEX = / ^ [ 0 - 9 a - f A - F ] + $ / ;
179+ const BLIND_SIGNING_TX_TYPES_TO_CHECK = { enabletoken : 'AssociatedTokenAccountInitialization' } ;
176180
177181export class Sol extends BaseCoin {
178182 protected readonly _staticsCoin : Readonly < StaticsBaseCoin > ;
@@ -233,6 +237,92 @@ export class Sol extends BaseCoin {
233237 return Math . pow ( 10 , this . _staticsCoin . decimalPlaces ) ;
234238 }
235239
240+ verifyTxType ( txParams : TransactionParams , actualTypeFromDecoded : string | undefined ) : void {
241+ // do nothing, let the tx fail way down as always
242+ const expectedTypeFromUserParams = txParams . type ;
243+ if ( expectedTypeFromUserParams === undefined || actualTypeFromDecoded === undefined ) return ;
244+
245+ const correctPrebuildTxType = BLIND_SIGNING_TX_TYPES_TO_CHECK [ expectedTypeFromUserParams ] ;
246+
247+ if ( correctPrebuildTxType && correctPrebuildTxType !== actualTypeFromDecoded ) {
248+ throw new Error (
249+ `Tx type "${ actualTypeFromDecoded } " does not match expected txParams type "${ expectedTypeFromUserParams } "`
250+ ) ;
251+ }
252+ }
253+
254+ throwIfMissingTokenEnablementsOrReturn ( explanation : TransactionExplanation ) : ITokenEnablement [ ] {
255+ if ( ! explanation . tokenEnablements || explanation . tokenEnablements . length === 0 )
256+ throw new Error ( 'Missing tx token enablements data on token enablement tx prebuild' ) ;
257+ return explanation . tokenEnablements ;
258+ }
259+
260+ throwIfMissingEnableTokenConfigOrReturn ( txParams : TransactionParams ) : TokenEnablement {
261+ if ( ! txParams . enableTokens || txParams . enableTokens . length === 0 ) throw new Error ( 'Missing enable token config' ) ;
262+ if ( txParams . enableTokens . length > 1 )
263+ throw new Error ( 'Multiple token enablement not supported in a single transaction' ) ;
264+ return txParams . enableTokens [ 0 ] ;
265+ }
266+
267+ verifyTokenName ( tokenEnablementsPrebuild : ITokenEnablement [ ] , enableTokensConfig : TokenEnablement ) : void {
268+ const expectedTokenName = enableTokensConfig . name ;
269+ tokenEnablementsPrebuild . forEach ( ( tokenEnablement ) => {
270+ if ( ! tokenEnablement . tokenName ) throw new Error ( 'Missing token name on token enablement tx' ) ;
271+ if ( tokenEnablement . tokenName !== expectedTokenName )
272+ throw new Error (
273+ `Invalid token name: expected ${ expectedTokenName } , got ${ tokenEnablement . tokenName } on token enablement tx`
274+ ) ;
275+ } ) ;
276+ }
277+
278+ async verifyTokenAddress (
279+ tokenEnablementsPrebuild : ITokenEnablement [ ] ,
280+ enableTokensConfig : TokenEnablement
281+ ) : Promise < void > {
282+ const expectedTokenAddress = enableTokensConfig . address ;
283+ const expectedTokenName = enableTokensConfig . name ;
284+
285+ if ( ! expectedTokenAddress ) throw new Error ( 'Missing token address on token enablement tx' ) ;
286+ if ( ! expectedTokenName ) throw new Error ( 'Missing token name on token enablement tx' ) ;
287+
288+ for ( const tokenEnablement of tokenEnablementsPrebuild ) {
289+ let tokenMintAddress : Readonly < SolCoin > | undefined ;
290+ try {
291+ tokenMintAddress = getSolTokenFromTokenName ( expectedTokenName ) ;
292+ } catch {
293+ throw new Error ( `Unable to derive ATA for token address: ${ expectedTokenAddress } ` ) ;
294+ }
295+ if (
296+ ! tokenMintAddress ||
297+ tokenMintAddress . tokenAddress === undefined ||
298+ tokenMintAddress . programId === undefined
299+ ) {
300+ throw new Error ( `Unable to get token mint address for ${ expectedTokenName } ` ) ;
301+ }
302+ let ata : string ;
303+ try {
304+ ata = await getAssociatedTokenAccountAddress (
305+ tokenMintAddress . tokenAddress ,
306+ expectedTokenAddress ,
307+ true ,
308+ tokenMintAddress . programId
309+ ) ;
310+ } catch {
311+ throw new Error ( `Unable to derive ATA for token address: ${ expectedTokenAddress } ` ) ;
312+ }
313+ if ( ata !== tokenEnablement . address ) {
314+ throw new Error (
315+ `Invalid token address: expected ${ ata } , got ${ tokenEnablement . address } on token enablement tx`
316+ ) ;
317+ }
318+ }
319+ }
320+
321+ verifyNoOutputs ( explanation : TransactionExplanation ) : void {
322+ if ( ! explanation . outputs ) return ;
323+ if ( explanation . outputs . length > 0 ) throw new Error ( 'Invalid token enablement tx: no outputs allow' ) ;
324+ }
325+
236326 async verifyTransaction ( params : SolVerifyTransactionOptions ) : Promise < boolean > {
237327 // asset name to transfer amount map
238328 const totalAmount : Record < string , BigNumber > = { } ;
@@ -261,6 +351,16 @@ export class Sol extends BaseCoin {
261351 transaction . fromRawTransaction ( rawTxBase64 ) ;
262352 const explainedTx = transaction . explainTransaction ( ) ;
263353
354+ this . verifyTxType ( txParams , explainedTx . type ) ;
355+ if ( txParams . type === 'enabletoken' ) {
356+ const tokenEnablementsPrebuild = this . throwIfMissingTokenEnablementsOrReturn ( explainedTx ) ;
357+ const enableTokensConfig = this . throwIfMissingEnableTokenConfigOrReturn ( txParams ) ;
358+
359+ this . verifyTokenName ( tokenEnablementsPrebuild , enableTokensConfig ) ;
360+ await this . verifyTokenAddress ( tokenEnablementsPrebuild , enableTokensConfig ) ;
361+ this . verifyNoOutputs ( explainedTx ) ;
362+ }
363+
264364 // users do not input recipients for consolidation requests as they are generated by the server
265365 if ( txParams . recipients !== undefined ) {
266366 const filteredRecipients = txParams . recipients ?. map ( ( recipient ) =>
0 commit comments