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,84 @@ export class Sol extends BaseCoin {
233237 return Math . pow ( 10 , this . _staticsCoin . decimalPlaces ) ;
234238 }
235239
240+ verifyTxType ( expectedTypeFromUserParams : string , actualTypeFromDecoded : string | undefined ) : void {
241+ const matchFromUserToDecodedType = BLIND_SIGNING_TX_TYPES_TO_CHECK [ expectedTypeFromUserParams ] ;
242+ if ( matchFromUserToDecodedType !== actualTypeFromDecoded ) {
243+ throw new Error (
244+ `Invalid transaction type on token enablement: expected "${ matchFromUserToDecodedType } ", got "${ actualTypeFromDecoded } ".`
245+ ) ;
246+ }
247+ }
248+
249+ throwIfMissingTokenEnablementsOrReturn ( explanation : TransactionExplanation ) : ITokenEnablement [ ] {
250+ if ( ! explanation . tokenEnablements || explanation . tokenEnablements . length === 0 )
251+ throw new Error ( 'Missing tx token enablements data on token enablement tx prebuild' ) ;
252+ return explanation . tokenEnablements ;
253+ }
254+
255+ throwIfMissingEnableTokenConfigOrReturn ( txParams : TransactionParams ) : TokenEnablement [ ] {
256+ if ( ! txParams . enableTokens || txParams . enableTokens . length === 0 ) throw new Error ( 'Missing enable token config' ) ;
257+ return txParams . enableTokens ;
258+ }
259+
260+ verifyTokenName ( tokenEnablementsPrebuild : ITokenEnablement [ ] , enableTokensConfig : TokenEnablement [ ] ) : void {
261+ enableTokensConfig . forEach ( ( enableTokenConfig ) => {
262+ const expectedTokenName = enableTokenConfig . name ;
263+ tokenEnablementsPrebuild . forEach ( ( tokenEnablement ) => {
264+ if ( ! tokenEnablement . tokenName ) throw new Error ( 'Missing token name on token enablement tx' ) ;
265+ if ( tokenEnablement . tokenName !== expectedTokenName )
266+ throw new Error (
267+ `Invalid token name: expected ${ expectedTokenName } , got ${ tokenEnablement . tokenName } on token enablement tx`
268+ ) ;
269+ } ) ;
270+ } ) ;
271+ }
272+
273+ async verifyTokenAddress (
274+ tokenEnablementsPrebuild : ITokenEnablement [ ] ,
275+ enableTokensConfig : TokenEnablement [ ]
276+ ) : Promise < void > {
277+ for ( const enableTokenConfig of enableTokensConfig ) {
278+ const expectedTokenAddress = enableTokenConfig . address ;
279+ const expectedTokenName = enableTokenConfig . name ;
280+
281+ if ( ! expectedTokenAddress ) throw new Error ( 'Missing token address on token enablement tx' ) ;
282+ if ( ! expectedTokenName ) throw new Error ( 'Missing token name on token enablement tx' ) ;
283+
284+ for ( const tokenEnablement of tokenEnablementsPrebuild ) {
285+ let tokenMintAddress : Readonly < SolCoin > | undefined ;
286+ try {
287+ tokenMintAddress = getSolTokenFromTokenName ( expectedTokenName ) ;
288+ } catch {
289+ throw new Error ( `Unable to derive ATA for token address: ${ expectedTokenAddress } ` ) ;
290+ }
291+ if (
292+ ! tokenMintAddress ||
293+ tokenMintAddress . tokenAddress === undefined ||
294+ tokenMintAddress . programId === undefined
295+ ) {
296+ throw new Error ( `Unable to get token mint address for ${ expectedTokenName } ` ) ;
297+ }
298+ let ata : string ;
299+ try {
300+ ata = await getAssociatedTokenAccountAddress (
301+ tokenMintAddress . tokenAddress ,
302+ expectedTokenAddress ,
303+ true ,
304+ tokenMintAddress . programId
305+ ) ;
306+ } catch {
307+ throw new Error ( `Unable to derive ATA for token address: ${ expectedTokenAddress } ` ) ;
308+ }
309+ if ( ata !== tokenEnablement . address ) {
310+ throw new Error (
311+ `Invalid token address: expected ${ ata } , got ${ tokenEnablement . address } on token enablement tx`
312+ ) ;
313+ }
314+ }
315+ }
316+ }
317+
236318 async verifyTransaction ( params : SolVerifyTransactionOptions ) : Promise < boolean > {
237319 // asset name to transfer amount map
238320 const totalAmount : Record < string , BigNumber > = { } ;
@@ -261,6 +343,15 @@ export class Sol extends BaseCoin {
261343 transaction . fromRawTransaction ( rawTxBase64 ) ;
262344 const explainedTx = transaction . explainTransaction ( ) ;
263345
346+ if ( txParams . type === 'enabletoken' && verificationOptions ?. verifyTokenEnablement ) {
347+ this . verifyTxType ( txParams . type , explainedTx . type ) ;
348+ const tokenEnablementsPrebuild = this . throwIfMissingTokenEnablementsOrReturn ( explainedTx ) ;
349+ const enableTokensConfig = this . throwIfMissingEnableTokenConfigOrReturn ( txParams ) ;
350+
351+ this . verifyTokenName ( tokenEnablementsPrebuild , enableTokensConfig ) ;
352+ await this . verifyTokenAddress ( tokenEnablementsPrebuild , enableTokensConfig ) ;
353+ }
354+
264355 // users do not input recipients for consolidation requests as they are generated by the server
265356 if ( txParams . recipients !== undefined ) {
266357 const filteredRecipients = txParams . recipients ?. map ( ( recipient ) =>
0 commit comments