@@ -36,13 +36,15 @@ import {
3636 SignedTransaction ,
3737 SignTransactionOptions as BaseSignTransactionOptions ,
3838 TokenEnablementConfig ,
39- TransactionExplanation ,
39+ TransactionParams ,
40+ TransactionType ,
4041 VerifyAddressOptions ,
4142 VerifyTransactionOptions ,
4243} from '@bitgo/sdk-core' ;
4344import { BaseCoin as StaticsBaseCoin , CoinFamily , coins , Nep141Token , Networks } from '@bitgo/statics' ;
4445
4546import { KeyPair as NearKeyPair , Transaction , TransactionBuilder , TransactionBuilderFactory } from './lib' ;
47+ import { TransactionExplanation , TxData } from './lib/iface' ;
4648import nearUtils from './lib/utils' ;
4749import { MAX_GAS_LIMIT_FOR_FT_TRANSFER } from './lib/constants' ;
4850
@@ -1000,6 +1002,10 @@ export class Near extends BaseCoin {
10001002 const explainedTx = transaction . explainTransaction ( ) ;
10011003
10021004 // users do not input recipients for consolidation requests as they are generated by the server
1005+ if ( txParams . type === 'enabletoken' && params . verification ?. verifyTokenEnablement ) {
1006+ this . validateTokenEnablementTransaction ( transaction , explainedTx , txParams ) ;
1007+ }
1008+
10031009 if ( txParams . recipients !== undefined ) {
10041010 if ( txParams . type === 'enabletoken' ) {
10051011 const tokenName = explainedTx . outputs [ 0 ] . tokenName ;
@@ -1031,6 +1037,18 @@ export class Near extends BaseCoin {
10311037 } ) ;
10321038
10331039 if ( ! _ . isEqual ( filteredOutputs , filteredRecipients ) ) {
1040+ // For enabletoken, provide more specific error messages for address mismatches
1041+ if ( txParams . type === 'enabletoken' && params . verification ?. verifyTokenEnablement ) {
1042+ const mismatchedAddresses = txParams . recipients
1043+ ?. filter (
1044+ ( recipient , index ) => ! filteredOutputs [ index ] || recipient . address !== filteredOutputs [ index ] . address
1045+ )
1046+ . map ( ( recipient ) => recipient . address ) ;
1047+
1048+ if ( mismatchedAddresses && mismatchedAddresses . length > 0 ) {
1049+ throw new Error ( `Address mismatch: ${ mismatchedAddresses . join ( ', ' ) } ` ) ;
1050+ }
1051+ }
10341052 throw new Error ( 'Tx outputs does not match with expected txParams recipients' ) ;
10351053 }
10361054 for ( const recipients of txParams . recipients ) {
@@ -1055,4 +1073,212 @@ export class Near extends BaseCoin {
10551073 }
10561074 auditEddsaPrivateKey ( prv , publicKey ?? '' ) ;
10571075 }
1076+
1077+ private validateTokenEnablementTransaction (
1078+ transaction : Transaction ,
1079+ explainedTx : TransactionExplanation ,
1080+ txParams : TransactionParams
1081+ ) : void {
1082+ const transactionData = transaction . toJson ( ) ;
1083+ this . validateTxType ( txParams , explainedTx ) ;
1084+ this . validateSigner ( transactionData ) ;
1085+ this . validateRawReceiver ( transactionData , txParams ) ;
1086+ this . validatePublicKey ( transactionData ) ;
1087+ this . validateRawActions ( transactionData , txParams ) ;
1088+ this . validateBeneficiary ( explainedTx , txParams ) ;
1089+ this . validateTokenOutput ( explainedTx , txParams ) ;
1090+ }
1091+
1092+ // Validates that the signer ID exists in the transaction
1093+ private validateSigner ( transactionData : TxData ) : void {
1094+ if ( ! transactionData . signerId ) {
1095+ throw new Error ( 'Error on token enablements: missing signer ID in transaction' ) ;
1096+ }
1097+ }
1098+
1099+ private validateBeneficiary ( explainedTx : TransactionExplanation , txParams : TransactionParams ) : void {
1100+ if ( ! explainedTx . outputs || explainedTx . outputs . length === 0 ) {
1101+ throw new Error ( 'Error on token enablements: transaction has no outputs to validate beneficiary' ) ;
1102+ }
1103+
1104+ // NEAR token enablements only support a single recipient
1105+ if ( ! txParams . recipients || txParams . recipients . length === 0 ) {
1106+ throw new Error ( 'Error on token enablements: missing recipients in transaction parameters' ) ;
1107+ }
1108+
1109+ if ( txParams . recipients . length !== 1 ) {
1110+ throw new Error ( 'Error on token enablements: token enablement only supports a single recipient' ) ;
1111+ }
1112+
1113+ if ( explainedTx . outputs . length !== 1 ) {
1114+ throw new Error ( 'Error on token enablements: transaction must have exactly 1 output' ) ;
1115+ }
1116+
1117+ const output = explainedTx . outputs [ 0 ] ;
1118+ const recipient = txParams . recipients [ 0 ] ;
1119+
1120+ if ( ! recipient ?. address ) {
1121+ throw new Error ( 'Error on token enablements: missing beneficiary address in transaction parameters' ) ;
1122+ }
1123+
1124+ if ( output . address !== recipient . address ) {
1125+ throw new Error ( 'Error on token enablements: transaction beneficiary mismatch with user expectation' ) ;
1126+ }
1127+ }
1128+
1129+ // Validates that the raw transaction receiverId matches the expected token contract
1130+ private validateRawReceiver ( transactionData : TxData , txParams : TransactionParams ) : void {
1131+ if ( ! transactionData . receiverId ) {
1132+ throw new Error ( 'Error on token enablements: missing receiver ID in transaction' ) ;
1133+ }
1134+
1135+ const recipient = txParams . recipients ?. [ 0 ] ;
1136+ if ( ! recipient ?. tokenName ) {
1137+ throw new Error ( 'Error on token enablements: missing token name in transaction parameters' ) ;
1138+ }
1139+
1140+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( recipient . tokenName ) ;
1141+ if ( ! tokenInstance ) {
1142+ throw new Error ( `Error on token enablements: unknown token '${ recipient . tokenName } '` ) ;
1143+ }
1144+
1145+ if ( transactionData . receiverId !== tokenInstance . contractAddress ) {
1146+ throw new Error (
1147+ `Error on token enablements: receiver contract mismatch - expected '${ tokenInstance . contractAddress } ', got '${ transactionData . receiverId } '`
1148+ ) ;
1149+ }
1150+ }
1151+
1152+ // Validates token output information from explained transaction
1153+ private validateTokenOutput ( explainedTx : TransactionExplanation , txParams : TransactionParams ) : void {
1154+ if ( ! explainedTx . outputs || explainedTx . outputs . length !== 1 ) {
1155+ throw new Error ( 'Error on token enablements: transaction must have exactly 1 output' ) ;
1156+ }
1157+
1158+ const output = explainedTx . outputs [ 0 ] ;
1159+ const recipient = txParams . recipients ?. [ 0 ] ;
1160+
1161+ if ( ! output . tokenName ) {
1162+ throw new Error ( 'Error on token enablements: missing token name in transaction output' ) ;
1163+ }
1164+
1165+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( output . tokenName ) ;
1166+ if ( ! tokenInstance ) {
1167+ throw new Error ( `Error on token enablements: unknown token '${ output . tokenName } '` ) ;
1168+ }
1169+
1170+ if ( recipient ?. tokenName && recipient . tokenName !== output . tokenName ) {
1171+ throw new Error (
1172+ `Error on token enablements: token mismatch - user expects '${ recipient . tokenName } ', transaction has '${ output . tokenName } '`
1173+ ) ;
1174+ }
1175+ }
1176+
1177+ private validatePublicKey ( transactionData : TxData ) : void {
1178+ if ( ! transactionData . publicKey ) {
1179+ throw new Error ( 'Error on token enablements: missing public key in transaction' ) ;
1180+ }
1181+
1182+ // Validate ed25519 format: "ed25519:base58_encoded_key"
1183+ if ( ! transactionData . publicKey . startsWith ( 'ed25519:' ) ) {
1184+ throw new Error ( 'Error on token enablements: unsupported key type, expected ed25519' ) ;
1185+ }
1186+
1187+ // Validate base58 part after "ed25519:"
1188+ const base58Part = transactionData . publicKey . substring ( 8 ) ;
1189+ if ( ! base58Part || base58Part . length !== 44 ) {
1190+ // ed25519 keys are 32 bytes = 44 base58 chars
1191+ throw new Error ( 'Error on token enablements: invalid ed25519 public key format' ) ;
1192+ }
1193+
1194+ // Validate it's actually valid base58
1195+ let decoded ;
1196+ try {
1197+ decoded = nearAPI . utils . serialize . base_decode ( base58Part ) ;
1198+ } catch {
1199+ throw new Error ( 'Error on token enablements: invalid base58 encoding in public key' ) ;
1200+ }
1201+
1202+ if ( ! decoded || decoded . length !== 32 ) {
1203+ throw new Error ( 'Error on token enablements: invalid ed25519 public key length' ) ;
1204+ }
1205+ }
1206+
1207+ // Validates the raw transaction actions according to NEAR protocol spec
1208+ private validateRawActions ( transactionData : TxData , txParams : TransactionParams ) : void {
1209+ // Must have exactly 1 action (NEAR spec requirement)
1210+ if ( ! transactionData . actions || transactionData . actions . length !== 1 ) {
1211+ throw new Error ( 'Error on token enablements: must have exactly 1 action' ) ;
1212+ }
1213+
1214+ const action = transactionData . actions [ 0 ] ;
1215+
1216+ // Must be a functionCall action (not transfer)
1217+ if ( ! action . functionCall ) {
1218+ throw new Error ( 'Error on token enablements: action must be a function call' ) ;
1219+ }
1220+
1221+ // Must be storage_deposit method (NEAR spec requirement)
1222+ if ( action . functionCall . methodName !== 'storage_deposit' ) {
1223+ throw new Error (
1224+ `Error on token enablements: invalid method '${ action . functionCall . methodName } ', expected 'storage_deposit'`
1225+ ) ;
1226+ }
1227+
1228+ // Validate args structure (should be JSON object)
1229+ if ( ! action . functionCall . args || typeof action . functionCall . args !== 'object' ) {
1230+ throw new Error ( 'Error on token enablements: invalid or missing function call arguments' ) ;
1231+ }
1232+
1233+ // Validate deposit exists and is valid
1234+ if ( ! action . functionCall . deposit ) {
1235+ throw new Error ( 'Error on token enablements: missing deposit in function call' ) ;
1236+ }
1237+
1238+ const depositAmount = new BigNumber ( action . functionCall . deposit ) ;
1239+ if ( depositAmount . isNaN ( ) || depositAmount . isLessThan ( 0 ) ) {
1240+ throw new Error ( 'Error on token enablements: invalid deposit amount in function call' ) ;
1241+ }
1242+
1243+ // Validate gas exists and is valid
1244+ if ( ! action . functionCall . gas ) {
1245+ throw new Error ( 'Error on token enablements: missing gas in function call' ) ;
1246+ }
1247+
1248+ const gasAmount = new BigNumber ( action . functionCall . gas ) ;
1249+ if ( gasAmount . isNaN ( ) || gasAmount . isLessThan ( 0 ) ) {
1250+ throw new Error ( 'Error on token enablements: invalid gas amount in function call' ) ;
1251+ }
1252+
1253+ // Validate deposit amount against expected storage deposit (merged from validateActions)
1254+ const recipient = txParams . recipients ?. [ 0 ] ;
1255+ if ( recipient ?. tokenName ) {
1256+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( recipient . tokenName ) ;
1257+ if ( tokenInstance ?. storageDepositAmount && action . functionCall . deposit !== tokenInstance . storageDepositAmount ) {
1258+ throw new Error (
1259+ `Error on token enablements: deposit amount ${ action . functionCall . deposit } does not match expected storage deposit ${ tokenInstance . storageDepositAmount } `
1260+ ) ;
1261+ }
1262+ }
1263+
1264+ // Validate user-specified amount matches deposit (merged from validateActions)
1265+ if (
1266+ recipient ?. amount !== undefined &&
1267+ recipient . amount !== '0' &&
1268+ recipient . amount !== action . functionCall . deposit
1269+ ) {
1270+ throw new Error (
1271+ `Error on token enablements: user specified amount '${ recipient . amount } ' does not match storage deposit '${ action . functionCall . deposit } '`
1272+ ) ;
1273+ }
1274+ }
1275+
1276+ private validateTxType ( txParams : TransactionParams , explainedTx : TransactionExplanation ) : void {
1277+ const expectedType = TransactionType . StorageDeposit ;
1278+ const actualType = explainedTx . type ;
1279+
1280+ if ( actualType !== expectedType ) {
1281+ throw new Error ( `Invalid transaction type on token enablement: expected "${ expectedType } ", got "${ actualType } ".` ) ;
1282+ }
1283+ }
10581284}
0 commit comments