diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index 61cbd76d..db45c651 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -929,8 +929,8 @@ func (tb *TxBuilder) determineInstructionID(txType uetypes.TxType, isNative bool // 2 (execute): [tx_id(32), utx_id(32), sender(20), token(32), gas_fee(8 BE), target(32), // accounts_buf(4 BE count + [pubkey(32) + writable(1)] per account), // ix_data_buf(4 BE len + data), rent_fee(8 BE)] -// 3 (revert SOL): [utx_id(32), tx_id(32), recipient(32), gas_fee(8 BE)] -// 4 (revert SPL): [utx_id(32), tx_id(32), mint(32), recipient(32), gas_fee(8 BE)] +// 3 (revert SOL): [tx_id(32), utx_id(32), recipient(32), gas_fee(8 BE)] +// 4 (revert SPL): [tx_id(32), utx_id(32), mint(32), recipient(32), gas_fee(8 BE)] // // The final message is hashed with keccak256 (Solana's keccak::hash = Ethereum's keccak256). func (tb *TxBuilder) constructTSSMessage( @@ -1000,15 +1000,15 @@ func (tb *TxBuilder) constructTSSMessage( binary.BigEndian.PutUint64(rentFeeBytes, rentFee) message = append(message, rentFeeBytes...) - case 3: // revert SOL — note: utx_id comes before tx_id (matches Rust) - message = append(message, universalTxID[:]...) + case 3: // revert SOL — tx_id before utx_id (same order as finalize) message = append(message, txID[:]...) + message = append(message, universalTxID[:]...) message = append(message, revertRecipient[:]...) message = append(message, gasFeeBytes...) - case 4: // revert SPL — note: includes the mint address for token identification - message = append(message, universalTxID[:]...) + case 4: // revert SPL — tx_id before utx_id, includes mint for token identification message = append(message, txID[:]...) + message = append(message, universalTxID[:]...) message = append(message, revertMint[:]...) message = append(message, revertRecipient[:]...) message = append(message, gasFeeBytes...) @@ -1372,8 +1372,8 @@ func (tb *TxBuilder) buildRevertData( // 15 associated_token_prog read/None ATA program // 16 recipient_ata mut/None Recipient's token account (withdraw SPL only) // --- Optional rate limit accounts (17-18) --- -// 17 rate_limit_config read/None Rate limit configuration (CEA path only) -// 18 token_rate_limit mut/None Per-token rate limit state (CEA path only) +// 17 rate_limit_config read/None Rate limit config PDA ["rate_limit_config"] (required when destination=gateway ie CEA path only)) +// 18 token_rate_limit mut/None Token rate limit PDA ["rate_limit", mint] (required when destination=gateway ie CEA path only) // --- Execute-only remaining accounts --- // 19+ remaining_accounts varies Accounts that the target program needs // @@ -1456,10 +1456,27 @@ func (tb *TxBuilder) buildWithdrawAndExecuteAccounts( } } - // rateLimitConfig: null (CEA withdrawal path only) - accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) - // tokenRateLimit: null (CEA withdrawal path only) - accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) + // Rate limiting accounts (#17-18): + // When destination is the gateway itself (CEA→UEA), pass real rate limit PDAs. + // Otherwise, pass None (gateway program ID sentinel). + if destinationProgram.Equals(tb.gatewayAddress) { + rateLimitConfigPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("rate_limit_config")}, tb.gatewayAddress) + accounts = append(accounts, &solana.AccountMeta{PublicKey: rateLimitConfigPDA, IsWritable: false, IsSigner: false}) + + // token_rate_limit PDA: seeds = ["rate_limit", token_mint] + // For native SOL, use Pubkey::default() (all zeros) as token_mint + var rateLimitMint solana.PublicKey + if !isNative { + rateLimitMint = mintPubkey + } + // rateLimitMint is zero-value (Pubkey::default()) for native SOL + tokenRateLimitPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("rate_limit"), rateLimitMint.Bytes()}, tb.gatewayAddress) + accounts = append(accounts, &solana.AccountMeta{PublicKey: tokenRateLimitPDA, IsWritable: true, IsSigner: false}) + } else { + // Not a CEA→UEA flow: rate limit accounts are None + accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) + accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) + } // For execute mode: append the target program's accounts as "remaining_accounts". // These are the accounts that the gateway will pass through via CPI to the target program. diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index 59bf2a95..bfc62894 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -29,8 +29,8 @@ import ( // ============================================================ // testGatewayAddress is a valid base58 Solana public key used for unit tests. -// It is NOT a real deployed gateway — only used for PDA derivations in offline tests. -const testGatewayAddress = "11111111111111111111111111111111" // system program, valid base58 +// Must NOT be SystemProgramID to avoid collisions with destinationProgram sentinel values. +const testGatewayAddress = "CFVSincHYbETh2k7w6u1ENEkjbSLtveRCEBupKidw2VS" func newTestBuilder(t *testing.T) *TxBuilder { t.Helper() @@ -504,9 +504,9 @@ func TestConstructTSSMessage(t *testing.T) { amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 500000) msg = append(msg, amountBE...) - // additional: utx_id, tx_id, recipient, gas_fee - msg = append(msg, utxID[:]...) + // additional: tx_id, utx_id, recipient, gas_fee msg = append(msg, txID[:]...) + msg = append(msg, utxID[:]...) msg = append(msg, revertRecipient[:]...) gasBE := make([]byte, 8) msg = append(msg, gasBE...) @@ -532,9 +532,9 @@ func TestConstructTSSMessage(t *testing.T) { amountBE := make([]byte, 8) binary.BigEndian.PutUint64(amountBE, 750000) msg = append(msg, amountBE...) - // additional: utx_id, tx_id, mint, recipient, gas_fee - msg = append(msg, utxID[:]...) + // additional: tx_id, utx_id, mint, recipient, gas_fee msg = append(msg, txID[:]...) + msg = append(msg, utxID[:]...) msg = append(msg, revertMint[:]...) msg = append(msg, revertRecipient[:]...) gasBE := make([]byte, 8) @@ -1364,7 +1364,7 @@ func TestEndToEndWithRealSignature(t *testing.T) { // ============================================================ const ( - devnetGatewayAddress = "CFVSincHYbETh2k7w6u1ENEkjbSLtveRCEBupKidw2VS" + devnetGatewayAddress = "DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp" devnetRPCURL = "https://api.devnet.solana.com" devnetGenesisHash = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG" devnetSPLMint = "EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF"