@@ -45,7 +45,7 @@ import {
4545import { BaseCoin as StaticsBaseCoin , CoinFamily , coins , Nep141Token , Networks } from '@bitgo/statics' ;
4646
4747import { KeyPair as NearKeyPair , Transaction , TransactionBuilder , TransactionBuilderFactory } from './lib' ;
48- import { TxData , TransactionExplanation } from './lib/iface' ;
48+ import { TransactionExplanation } from './lib/iface' ;
4949import nearUtils from './lib/utils' ;
5050import { MAX_GAS_LIMIT_FOR_FT_TRANSFER } from './lib/constants' ;
5151
@@ -1036,6 +1036,18 @@ export class Near extends BaseCoin {
10361036 } ) ;
10371037
10381038 if ( ! _ . isEqual ( filteredOutputs , filteredRecipients ) ) {
1039+ // For enabletoken, provide more specific error messages for address mismatches
1040+ if ( txParams . type === 'enabletoken' ) {
1041+ const mismatchedAddresses = txParams . recipients
1042+ ?. filter (
1043+ ( recipient , index ) => ! filteredOutputs [ index ] || recipient . address !== filteredOutputs [ index ] . address
1044+ )
1045+ . map ( ( recipient ) => recipient . address ) ;
1046+
1047+ if ( mismatchedAddresses && mismatchedAddresses . length > 0 ) {
1048+ throw new Error ( `Address mismatch: ${ mismatchedAddresses . join ( ', ' ) } ` ) ;
1049+ }
1050+ }
10391051 throw new Error ( 'Tx outputs does not match with expected txParams recipients' ) ;
10401052 }
10411053 for ( const recipients of txParams . recipients ) {
@@ -1082,141 +1094,198 @@ export class Near extends BaseCoin {
10821094 }
10831095 }
10841096
1085- // Validates that the transaction matches what the user expects
10861097 private validateTokenEnablementTransaction (
10871098 transaction : Transaction ,
10881099 explainedTx : TransactionExplanation ,
10891100 txParams : TransactionParams
10901101 ) : void {
1091- const transactionData = transaction . toJson ( ) ;
1092-
1093- // Validate each aspect of the transaction separately
10941102 this . validateTxType ( txParams , explainedTx ) ;
1095- this . validateSigner ( transactionData ) ;
1096- this . validateReceiver ( transactionData , explainedTx ) ;
1097- this . validatePublicKey ( transactionData , explainedTx ) ;
1098- this . validateActions ( transactionData , explainedTx ) ;
1099- this . validateAddresses ( txParams , explainedTx ) ;
1103+ this . validateSigner ( transaction ) ;
1104+ this . validateRawReceiver ( transaction , txParams ) ;
1105+ this . validatePublicKey ( transaction ) ;
1106+ this . validateRawActions ( transaction , txParams ) ;
1107+ this . validateBeneficiary ( explainedTx , txParams ) ;
1108+ this . validateTokenOutput ( explainedTx , txParams ) ;
11001109 }
11011110
11021111 // Validates that the signer ID exists in the transaction
1103- private validateSigner ( transactionData : TxData ) : void {
1112+ private validateSigner ( transaction : Transaction ) : void {
1113+ const transactionData = transaction . toJson ( ) ;
11041114 if ( ! transactionData . signerId ) {
11051115 throw new Error ( 'Error on token enablements: missing signer ID in transaction' ) ;
11061116 }
11071117 }
11081118
1109- // Validates that the receiver ID exists in the transaction
1110- private validateReceiver ( transactionData : TxData , explainedTx : TransactionExplanation ) : void {
1119+ private validateBeneficiary ( explainedTx : TransactionExplanation , txParams : TransactionParams ) : void {
1120+ if ( ! explainedTx . outputs || explainedTx . outputs . length === 0 ) {
1121+ throw new Error ( 'Error on token enablements: transaction has no outputs to validate beneficiary' ) ;
1122+ }
1123+
1124+ const output = explainedTx . outputs [ 0 ] ;
1125+ const recipient = txParams . recipients ?. [ 0 ] ;
1126+
1127+ if ( ! recipient ?. address ) {
1128+ throw new Error ( 'Error on token enablements: missing beneficiary address in transaction parameters' ) ;
1129+ }
1130+
1131+ if ( output . address !== recipient . address ) {
1132+ throw new Error ( 'Error on token enablements: transaction beneficiary mismatch with user expectation' ) ;
1133+ }
1134+ }
1135+
1136+ // Validates that the raw transaction receiverId matches the expected token contract
1137+ private validateRawReceiver ( transaction : Transaction , txParams : TransactionParams ) : void {
1138+ const transactionData = transaction . toJson ( ) ;
1139+
11111140 if ( ! transactionData . receiverId ) {
11121141 throw new Error ( 'Error on token enablements: missing receiver ID in transaction' ) ;
11131142 }
11141143
1115- // Get the expected contract address for the token being enabled
1116- if ( ! explainedTx . outputs [ 0 ] ?. tokenName ) {
1144+ const recipient = txParams . recipients ?. [ 0 ] ;
1145+ if ( ! recipient ?. tokenName ) {
1146+ throw new Error ( 'Error on token enablements: missing token name in transaction parameters' ) ;
1147+ }
1148+
1149+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( recipient . tokenName ) ;
1150+ if ( ! tokenInstance ) {
1151+ throw new Error ( `Error on token enablements: unknown token '${ recipient . tokenName } '` ) ;
1152+ }
1153+
1154+ if ( transactionData . receiverId !== tokenInstance . contractAddress ) {
1155+ throw new Error (
1156+ `Error on token enablements: receiver contract mismatch - expected '${ tokenInstance . contractAddress } ', got '${ transactionData . receiverId } '`
1157+ ) ;
1158+ }
1159+ }
1160+
1161+ // Validates token output information from explained transaction
1162+ private validateTokenOutput ( explainedTx : TransactionExplanation , txParams : TransactionParams ) : void {
1163+ if ( ! explainedTx . outputs || explainedTx . outputs . length !== 1 ) {
1164+ throw new Error ( 'Error on token enablements: transaction must have exactly 1 output' ) ;
1165+ }
1166+
1167+ const output = explainedTx . outputs [ 0 ] ;
1168+ const recipient = txParams . recipients ?. [ 0 ] ;
1169+
1170+ if ( ! output . tokenName ) {
11171171 throw new Error ( 'Error on token enablements: missing token name in transaction output' ) ;
11181172 }
1119- const tokenName = explainedTx . outputs [ 0 ] . tokenName ;
1120- const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( tokenName ) ;
1173+
1174+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( output . tokenName ) ;
11211175 if ( ! tokenInstance ) {
1122- throw new Error ( `Error on token enablements: unknown token '${ tokenName } '` ) ;
1176+ throw new Error ( `Error on token enablements: unknown token '${ output . tokenName } '` ) ;
11231177 }
1124- const expectedContractAddress = tokenInstance ?. contractAddress ;
11251178
1126- if ( transactionData . receiverId !== expectedContractAddress ) {
1127- throw new Error ( 'Error on token enablements: receiver contract mismatch' ) ;
1179+ if ( recipient ?. tokenName && recipient . tokenName !== output . tokenName ) {
1180+ throw new Error (
1181+ `Error on token enablements: token mismatch - user expects '${ recipient . tokenName } ', transaction has '${ output . tokenName } '`
1182+ ) ;
11281183 }
11291184 }
11301185
1131- // Validates that the public key exists in the transaction
1132- private validatePublicKey ( transactionData : TxData , explainedTx : TransactionExplanation ) : void {
1186+ private validatePublicKey ( transaction : Transaction ) : void {
1187+ const transactionData = transaction . toJson ( ) ;
1188+
11331189 if ( ! transactionData . publicKey ) {
11341190 throw new Error ( 'Error on token enablements: missing public key in transaction' ) ;
11351191 }
1136- // NEAR public keys must be in the format "ed25519:base58_encoded_key"
1192+
1193+ // Validate ed25519 format: "ed25519:base58_encoded_key"
11371194 if ( ! transactionData . publicKey . startsWith ( 'ed25519:' ) ) {
1138- throw new Error ( 'Error on token enablements: invalid public key format, must start with " ed25519:" ' ) ;
1195+ throw new Error ( 'Error on token enablements: unsupported key type, expected ed25519' ) ;
11391196 }
11401197
1141- // Extract the base58 part and validate it
1142- const base58Part = transactionData . publicKey . substring ( 8 ) ; // Remove "ed25519:" prefix
1143- if ( ! base58Part || base58Part . length === 0 ) {
1144- throw new Error ( 'Error on token enablements: invalid public key format, missing base58 encoded key' ) ;
1198+ // Validate base58 part after "ed25519:"
1199+ const base58Part = transactionData . publicKey . substring ( 8 ) ;
1200+ if ( ! base58Part || base58Part . length !== 44 ) {
1201+ // ed25519 keys are 32 bytes = 44 base58 chars
1202+ throw new Error ( 'Error on token enablements: invalid ed25519 public key format' ) ;
11451203 }
11461204
1147- // Validate base58 encoding (basic check)
1205+ // Validate it's actually valid base58
11481206 try {
1149- const decoded = base58 . decode ( base58Part ) ;
1207+ const decoded = nearAPI . utils . serialize . base_decode ( base58Part ) ;
11501208 if ( decoded . length !== 32 ) {
1151- throw new Error ( 'Error on token enablements: invalid public key length, must be 32 bytes when decoded ' ) ;
1209+ throw new Error ( 'Error on token enablements: invalid ed25519 public key length' ) ;
11521210 }
1153- } catch ( error ) {
1154- throw new Error ( 'Error on token enablements: invalid public key base58 encoding' ) ;
1211+ } catch {
1212+ throw new Error ( 'Error on token enablements: invalid base58 encoding in public key ' ) ;
11551213 }
11561214 }
11571215
1158- // Validates that the actions exist in the transaction and follow NEAR token enablement spec
1159- private validateActions ( transactionData : TxData , explainedTx : TransactionExplanation ) : void {
1160- // Check that actions array exists and is not empty
1161- if ( ! transactionData . actions || transactionData . actions . length === 0 ) {
1162- throw new Error ( 'Error on token enablements: missing actions in transaction' ) ;
1163- }
1216+ // Validates the raw transaction actions according to NEAR protocol spec
1217+ private validateRawActions ( transaction : Transaction , txParams : TransactionParams ) : void {
1218+ const transactionData = transaction . toJson ( ) ;
11641219
1165- // Token enablement must have exactly ONE action (NEAR spec requirement)
1166- if ( transactionData . actions . length !== 1 ) {
1167- throw new Error (
1168- `Error on token enablements: must have exactly 1 action, found ${ transactionData . actions . length } `
1169- ) ;
1220+ // Must have exactly 1 action (NEAR spec requirement)
1221+ if ( ! transactionData . actions || transactionData . actions . length !== 1 ) {
1222+ throw new Error ( 'Error on token enablements: must have exactly 1 action' ) ;
11701223 }
11711224
11721225 const action = transactionData . actions [ 0 ] ;
11731226
1174- // The action must be a FunctionCall (not Transfer or other action types )
1227+ // Must be a functionCall action (not transfer )
11751228 if ( ! action . functionCall ) {
1176- throw new Error ( 'Error on token enablements: action must be a FunctionCall, not Transfer or other type ' ) ;
1229+ throw new Error ( 'Error on token enablements: action must be a function call ' ) ;
11771230 }
11781231
1179- // Method name must be exactly " storage_deposit" (NEAR NEP-145 requirement)
1232+ // Must be storage_deposit method (NEAR spec requirement)
11801233 if ( action . functionCall . methodName !== 'storage_deposit' ) {
11811234 throw new Error (
1182- `Error on token enablements: invalid method name " ${ action . functionCall . methodName } ", must be " storage_deposit" `
1235+ `Error on token enablements: invalid method ' ${ action . functionCall . methodName } ', expected ' storage_deposit' `
11831236 ) ;
11841237 }
11851238
1186- // Validate function call arguments structure
1239+ // Validate args structure (should be JSON object)
11871240 if ( ! action . functionCall . args || typeof action . functionCall . args !== 'object' ) {
1188- throw new Error ( 'Error on token enablements: missing or invalid function call arguments' ) ;
1241+ throw new Error ( 'Error on token enablements: invalid or missing function call arguments' ) ;
11891242 }
11901243
1191- // Validate gas amount is reasonable (not zero, not excessively high)
1192- const gas = action . functionCall . gas ;
1193- if ( ! gas || gas === '0' ) {
1194- throw new Error ( 'Error on token enablements: gas amount cannot be zero' ) ;
1244+ // Validate deposit exists and is valid
1245+ if ( ! action . functionCall . deposit ) {
1246+ throw new Error ( 'Error on token enablements: missing deposit in function call' ) ;
11951247 }
11961248
1197- // Validate deposit amount exists and is valid
1198- const deposit = action . functionCall . deposit ;
1199- if ( ! deposit ) {
1200- throw new Error ( 'Error on token enablements: missing deposit amount' ) ;
1249+ const depositAmount = new BigNumber ( action . functionCall . deposit ) ;
1250+ if ( depositAmount . isNaN ( ) || depositAmount . isLessThan ( 0 ) ) {
1251+ throw new Error ( 'Error on token enablements: invalid deposit amount in function call' ) ;
12011252 }
12021253
1203- const depositBN = new BigNumber ( deposit ) ;
1204- if ( depositBN . isNaN ( ) || depositBN . isLessThan ( 0 ) ) {
1205- throw new Error ( 'Error on token enablements: invalid deposit amount format ' ) ;
1254+ // Validate gas exists and is valid
1255+ if ( ! action . functionCall . gas ) {
1256+ throw new Error ( 'Error on token enablements: missing gas in function call ' ) ;
12061257 }
12071258
1208- // Cross-validate deposit amount with explained transaction
1209- if ( explainedTx . outputs ?. [ 0 ] ?. amount && deposit !== explainedTx . outputs [ 0 ] . amount ) {
1259+ const gasAmount = new BigNumber ( action . functionCall . gas ) ;
1260+ if ( gasAmount . isNaN ( ) || gasAmount . isLessThan ( 0 ) ) {
1261+ throw new Error ( 'Error on token enablements: invalid gas amount in function call' ) ;
1262+ }
1263+
1264+ // Validate deposit amount against expected storage deposit (merged from validateActions)
1265+ const recipient = txParams . recipients ?. [ 0 ] ;
1266+ if ( recipient ?. tokenName ) {
1267+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( recipient . tokenName ) ;
1268+ if ( tokenInstance ?. storageDepositAmount && action . functionCall . deposit !== tokenInstance . storageDepositAmount ) {
1269+ throw new Error (
1270+ `Error on token enablements: deposit amount ${ action . functionCall . deposit } does not match expected storage deposit ${ tokenInstance . storageDepositAmount } `
1271+ ) ;
1272+ }
1273+ }
1274+
1275+ // Validate user-specified amount matches deposit (merged from validateActions)
1276+ if (
1277+ recipient ?. amount !== undefined &&
1278+ recipient . amount !== '0' &&
1279+ recipient . amount !== action . functionCall . deposit
1280+ ) {
12101281 throw new Error (
1211- `Error on token enablements: deposit amount mismatch - action has " ${ deposit } ", explained tx has " ${ explainedTx . outputs [ 0 ] . amount } " `
1282+ `Error on token enablements: user specified amount ' ${ recipient . amount } ' does not match storage deposit ' ${ action . functionCall . deposit } ' `
12121283 ) ;
12131284 }
12141285 }
12151286
1216- // Validates that the transaction type matches the expected type for the given txParams
12171287 private validateTxType ( txParams : TransactionParams , explainedTx : TransactionExplanation ) : void {
12181288 if ( txParams . type === 'enabletoken' ) {
1219- // For NEAR token enablement, we expect TransactionType.StorageDeposit
12201289 const expectedType = TransactionType . StorageDeposit ;
12211290 const actualType = explainedTx . type ;
12221291
@@ -1227,27 +1296,4 @@ export class Near extends BaseCoin {
12271296 }
12281297 }
12291298 }
1230-
1231- //Validates that addresses match between parameters and explained transaction
1232- private validateAddresses ( txParams : TransactionParams , explainedTx : TransactionExplanation ) : void {
1233- if ( ! txParams . recipients ) {
1234- // This is ok since sometimes users do not input recipients for consolidation requests as they are generated by the server
1235- return ;
1236- }
1237- if ( ! explainedTx . outputs ) {
1238- throw new Error ( 'Error on token enablements: transaction has no outputs but recipients were expected' ) ;
1239- }
1240-
1241- if ( txParams . recipients . length !== explainedTx . outputs . length ) {
1242- throw new Error ( 'Error on token enablements: output count does not match recipients count' ) ;
1243- }
1244-
1245- const mismatchedAddresses = txParams . recipients
1246- . filter ( ( recipient , index ) => recipient . address !== explainedTx . outputs [ index ] . address )
1247- . map ( ( recipient ) => recipient . address ) ;
1248-
1249- if ( mismatchedAddresses . length > 0 ) {
1250- throw new Error ( `Address mismatch: ${ mismatchedAddresses . join ( ', ' ) } ` ) ;
1251- }
1252- }
12531299}
0 commit comments