@@ -36,15 +36,17 @@ 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' ;
47- import { MAX_GAS_LIMIT_FOR_FT_TRANSFER } from './lib/constants' ;
49+ import { MAX_GAS_LIMIT_FOR_FT_TRANSFER , STORAGE_DEPOSIT } from './lib/constants' ;
4850
4951export interface SignTransactionOptions extends BaseSignTransactionOptions {
5052 txPrebuild : TransactionPrebuild ;
@@ -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,208 @@ export class Near extends BaseCoin {
10551073 }
10561074 auditEddsaPrivateKey ( prv , publicKey ?? '' ) ;
10571075 }
1076+
1077+ /**
1078+ * Validates a token enablement transaction by performing checks
1079+ * for NEAR protocol compliance and ensuring txParams matches transaction data.
1080+ *
1081+ * @param transaction - The NEAR transaction object to validate
1082+ * @param explainedTx - The same transaction data in explained format with parsed outputs and metadata
1083+ * @param txParams - The transaction parameters containing recipients and configuration
1084+ * @throws {Error } When any validation check fails, with descriptive error messages
1085+ * @private
1086+ */
1087+ private validateTokenEnablementTransaction (
1088+ transaction : Transaction ,
1089+ explainedTx : TransactionExplanation ,
1090+ txParams : TransactionParams
1091+ ) : void {
1092+ const transactionData = transaction . toJson ( ) ;
1093+ this . validateTxType ( txParams , explainedTx ) ;
1094+ this . validateSigner ( transactionData ) ;
1095+ this . validateRawReceiver ( transactionData , txParams ) ;
1096+ this . validatePublicKey ( transactionData ) ;
1097+ this . validateRawActions ( transactionData , txParams ) ;
1098+ this . validateBeneficiary ( explainedTx , txParams ) ;
1099+ this . validateTokenOutput ( explainedTx , txParams ) ;
1100+ }
1101+
1102+ // Validates that the signer ID exists in the transaction
1103+ private validateSigner ( transactionData : TxData ) : void {
1104+ if ( ! transactionData . signerId ) {
1105+ throw new Error ( 'Error on token enablements: missing signer ID in transaction' ) ;
1106+ }
1107+ }
1108+
1109+ private validateBeneficiary ( explainedTx : TransactionExplanation , txParams : TransactionParams ) : void {
1110+ if ( ! explainedTx . outputs || explainedTx . outputs . length === 0 ) {
1111+ throw new Error ( 'Error on token enablements: transaction has no outputs to validate beneficiary' ) ;
1112+ }
1113+
1114+ // NEAR token enablements only support a single recipient
1115+ if ( ! txParams . recipients || txParams . recipients . length === 0 ) {
1116+ throw new Error ( 'Error on token enablements: missing recipients in transaction parameters' ) ;
1117+ }
1118+
1119+ if ( txParams . recipients . length !== 1 ) {
1120+ throw new Error ( 'Error on token enablements: token enablement only supports a single recipient' ) ;
1121+ }
1122+
1123+ if ( explainedTx . outputs . length !== 1 ) {
1124+ throw new Error ( 'Error on token enablements: transaction must have exactly 1 output' ) ;
1125+ }
1126+
1127+ const output = explainedTx . outputs [ 0 ] ;
1128+ const recipient = txParams . recipients [ 0 ] ;
1129+
1130+ if ( ! recipient ?. address ) {
1131+ throw new Error ( 'Error on token enablements: missing beneficiary address in transaction parameters' ) ;
1132+ }
1133+
1134+ if ( output . address !== recipient . address ) {
1135+ throw new Error ( 'Error on token enablements: transaction beneficiary mismatch with user expectation' ) ;
1136+ }
1137+ }
1138+
1139+ // Validates that the raw transaction receiverId matches the expected token contract
1140+ private validateRawReceiver ( transactionData : TxData , txParams : TransactionParams ) : void {
1141+ if ( ! transactionData . receiverId ) {
1142+ throw new Error ( 'Error on token enablements: missing receiver ID in transaction' ) ;
1143+ }
1144+
1145+ const recipient = txParams . recipients ?. [ 0 ] ;
1146+ if ( ! recipient ?. tokenName ) {
1147+ throw new Error ( 'Error on token enablements: missing token name in transaction parameters' ) ;
1148+ }
1149+
1150+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( recipient . tokenName ) ;
1151+ if ( ! tokenInstance ) {
1152+ throw new Error ( `Error on token enablements: unknown token '${ recipient . tokenName } '` ) ;
1153+ }
1154+
1155+ if ( transactionData . receiverId !== tokenInstance . contractAddress ) {
1156+ throw new Error (
1157+ `Error on token enablements: receiver contract mismatch - expected '${ tokenInstance . contractAddress } ', got '${ transactionData . receiverId } '`
1158+ ) ;
1159+ }
1160+ }
1161+
1162+ // Validates token output information from explained transaction
1163+ private validateTokenOutput ( explainedTx : TransactionExplanation , txParams : TransactionParams ) : void {
1164+ if ( ! explainedTx . outputs || explainedTx . outputs . length !== 1 ) {
1165+ throw new Error ( 'Error on token enablements: transaction must have exactly 1 output' ) ;
1166+ }
1167+
1168+ const output = explainedTx . outputs [ 0 ] ;
1169+ const recipient = txParams . recipients ?. [ 0 ] ;
1170+
1171+ if ( ! output . tokenName ) {
1172+ throw new Error ( 'Error on token enablements: missing token name in transaction output' ) ;
1173+ }
1174+
1175+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( output . tokenName ) ;
1176+ if ( ! tokenInstance ) {
1177+ throw new Error ( `Error on token enablements: unknown token '${ output . tokenName } '` ) ;
1178+ }
1179+
1180+ if ( recipient ?. tokenName && recipient . tokenName !== output . tokenName ) {
1181+ throw new Error (
1182+ `Error on token enablements: token mismatch - user expects '${ recipient . tokenName } ', transaction has '${ output . tokenName } '`
1183+ ) ;
1184+ }
1185+ }
1186+
1187+ private validatePublicKey ( transactionData : TxData ) : void {
1188+ if ( ! transactionData . publicKey ) {
1189+ throw new Error ( 'Error on token enablements: missing public key in transaction' ) ;
1190+ }
1191+
1192+ // Validate ed25519 format: "ed25519:base58_encoded_key"
1193+ if ( ! transactionData . publicKey . startsWith ( 'ed25519:' ) ) {
1194+ throw new Error ( 'Error on token enablements: unsupported key type, expected ed25519' ) ;
1195+ }
1196+
1197+ const base58Part = transactionData . publicKey . substring ( 8 ) ;
1198+ if ( ! this . isValidPub ( base58Part ) ) {
1199+ throw new Error ( 'Error on token enablements: invalid public key format' ) ;
1200+ }
1201+ }
1202+
1203+ // Validates the raw transaction actions according to NEAR protocol spec
1204+ private validateRawActions ( transactionData : TxData , txParams : TransactionParams ) : void {
1205+ // Must have exactly 1 action (NEAR spec requirement)
1206+ if ( ! transactionData . actions || transactionData . actions . length !== 1 ) {
1207+ throw new Error ( 'Error on token enablements: must have exactly 1 action' ) ;
1208+ }
1209+
1210+ const action = transactionData . actions [ 0 ] ;
1211+
1212+ // Must be a functionCall action (not transfer)
1213+ if ( ! action . functionCall ) {
1214+ throw new Error ( 'Error on token enablements: action must be a function call' ) ;
1215+ }
1216+
1217+ // Must be storage_deposit method (NEAR spec requirement)
1218+ if ( action . functionCall . methodName !== 'storage_deposit' ) {
1219+ throw new Error (
1220+ `Error on token enablements: invalid method '${ action . functionCall . methodName } ', expected '${ STORAGE_DEPOSIT } '`
1221+ ) ;
1222+ }
1223+
1224+ // Validate args structure (should be JSON object)
1225+ if ( ! action . functionCall . args || typeof action . functionCall . args !== 'object' ) {
1226+ throw new Error ( 'Error on token enablements: invalid or missing function call arguments' ) ;
1227+ }
1228+
1229+ // Validate deposit exists and is valid
1230+ if ( ! action . functionCall . deposit ) {
1231+ throw new Error ( 'Error on token enablements: missing deposit in function call' ) ;
1232+ }
1233+
1234+ const depositAmount = new BigNumber ( action . functionCall . deposit ) ;
1235+ if ( depositAmount . isNaN ( ) || depositAmount . isLessThan ( 0 ) ) {
1236+ throw new Error ( 'Error on token enablements: invalid deposit amount in function call' ) ;
1237+ }
1238+
1239+ // Validate gas exists and is valid
1240+ if ( ! action . functionCall . gas ) {
1241+ throw new Error ( 'Error on token enablements: missing gas in function call' ) ;
1242+ }
1243+
1244+ const gasAmount = new BigNumber ( action . functionCall . gas ) ;
1245+ if ( gasAmount . isNaN ( ) || gasAmount . isLessThan ( 0 ) ) {
1246+ throw new Error ( 'Error on token enablements: invalid gas amount in function call' ) ;
1247+ }
1248+
1249+ // Validate deposit amount against expected storage deposit (merged from validateActions)
1250+ const recipient = txParams . recipients ?. [ 0 ] ;
1251+ if ( recipient ?. tokenName ) {
1252+ const tokenInstance = nearUtils . getTokenInstanceFromTokenName ( recipient . tokenName ) ;
1253+ if ( tokenInstance ?. storageDepositAmount && action . functionCall . deposit !== tokenInstance . storageDepositAmount ) {
1254+ throw new Error (
1255+ `Error on token enablements: deposit amount ${ action . functionCall . deposit } does not match expected storage deposit ${ tokenInstance . storageDepositAmount } `
1256+ ) ;
1257+ }
1258+ }
1259+
1260+ // Validate user-specified amount matches deposit (merged from validateActions)
1261+ if (
1262+ recipient ?. amount !== undefined &&
1263+ recipient . amount !== '0' &&
1264+ recipient . amount !== action . functionCall . deposit
1265+ ) {
1266+ throw new Error (
1267+ `Error on token enablements: user specified amount '${ recipient . amount } ' does not match storage deposit '${ action . functionCall . deposit } '`
1268+ ) ;
1269+ }
1270+ }
1271+
1272+ private validateTxType ( txParams : TransactionParams , explainedTx : TransactionExplanation ) : void {
1273+ const expectedType = TransactionType . StorageDeposit ;
1274+ const actualType = explainedTx . type ;
1275+
1276+ if ( actualType !== expectedType ) {
1277+ throw new Error ( `Invalid transaction type on token enablement: expected "${ expectedType } ", got "${ actualType } ".` ) ;
1278+ }
1279+ }
10581280}
0 commit comments