Skip to content
Closed
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
70 changes: 70 additions & 0 deletions packages/wasm-solana/src/intent/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const SYSTEM_PROGRAM_ID: &str = "11111111111111111111111111111111";
// Constants
const STAKE_ACCOUNT_SPACE: u64 = 200;
const STAKE_ACCOUNT_RENT: u64 = 2282880; // ~0.00228288 SOL
const MAX_NATIVE_TRANSFER_LAMPORTS: u64 = 5_000_000_000; // 5 SOL

/// Build a transaction from a BitGo intent.
///
Expand Down Expand Up @@ -180,6 +181,13 @@ fn build_payment(
})?;
let lamports: u64 = *amount;

if lamports > MAX_NATIVE_TRANSFER_LAMPORTS {
return Err(WasmSolanaError::new(&format!(
"Native transfer amount {} lamports exceeds maximum of {} lamports (5 SOL)",
lamports, MAX_NATIVE_TRANSFER_LAMPORTS
)));
}

instructions.push(system_ix::transfer(&fee_payer, &to_pubkey, lamports));
}

Expand Down Expand Up @@ -1163,6 +1171,68 @@ mod tests {
);
}

#[test]
fn test_build_payment_rejects_amount_exceeding_max() {
// 5 SOL + 1 lamport should be rejected
let intent = serde_json::json!({
"intentType": "payment",
"recipients": [{
"address": { "address": "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH" },
"amount": { "value": "5000000001" }
}]
});

let result = build_from_intent(&intent, &test_params());
assert!(result.is_err(), "Should fail for amount exceeding 5 SOL");
assert!(
result.unwrap_err().to_string().contains("exceeds maximum"),
"Error should mention exceeds maximum"
);
}

#[test]
fn test_build_payment_allows_exactly_max_amount() {
// Exactly 5 SOL should succeed
let intent = serde_json::json!({
"intentType": "payment",
"recipients": [{
"address": { "address": "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH" },
"amount": { "value": "5000000000" }
}]
});

let result = build_from_intent(&intent, &test_params());
assert!(
result.is_ok(),
"Should succeed for exactly 5 SOL: {:?}",
result
);
}

#[test]
fn test_build_payment_rejects_any_recipient_exceeding_max() {
// Multi-recipient where second exceeds max
let intent = serde_json::json!({
"intentType": "payment",
"recipients": [
{
"address": { "address": "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH" },
"amount": { "value": "1000000" }
},
{
"address": { "address": "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN" },
"amount": { "value": "6000000000" }
}
]
});

let result = build_from_intent(&intent, &test_params());
assert!(
result.is_err(),
"Should fail when any recipient exceeds 5 SOL"
);
}

#[test]
fn test_build_marinade_unstake_requires_recipients() {
let intent = serde_json::json!({
Expand Down
42 changes: 42 additions & 0 deletions packages/wasm-solana/test/intentBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,48 @@ describe("buildFromIntent", function () {
assert.equal(transfers.length, 2, "Should have 2 transfer instructions");
});

it("should reject native transfer exceeding 5 SOL", function () {
const intent = {
intentType: "payment",
recipients: [
{
address: { address: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH" },
amount: { value: 5000000001n }, // 5 SOL + 1 lamport
},
],
};

assert.throws(
() => {
buildFromIntent(intent, {
feePayer,
nonce: { type: "blockhash", value: blockhash },
});
},
/exceeds maximum/,
"Should reject transfer exceeding 5 SOL",
);
});

it("should allow native transfer of exactly 5 SOL", function () {
const intent = {
intentType: "payment",
recipients: [
{
address: { address: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH" },
amount: { value: 5000000000n }, // exactly 5 SOL
},
],
};

const result = buildFromIntent(intent, {
feePayer,
nonce: { type: "blockhash", value: blockhash },
});

assert(result.transaction instanceof Transaction, "Should return Transaction object");
});

it("should include memo when provided", function () {
const intent = {
intentType: "payment",
Expand Down