Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions universalClient/chains/svm/tx_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -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.
Expand Down
14 changes: 7 additions & 7 deletions universalClient/chains/svm/tx_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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...)
Expand All @@ -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)
Expand Down Expand Up @@ -1364,7 +1364,7 @@ func TestEndToEndWithRealSignature(t *testing.T) {
// ============================================================

const (
devnetGatewayAddress = "CFVSincHYbETh2k7w6u1ENEkjbSLtveRCEBupKidw2VS"
devnetGatewayAddress = "DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp"
devnetRPCURL = "https://api.devnet.solana.com"
devnetGenesisHash = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG"
devnetSPLMint = "EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF"
Expand Down
Loading