From e256ee7c8f4525c49c01099604f9a3190baa38c3 Mon Sep 17 00:00:00 2001 From: "Notorious D.E.V" Date: Fri, 13 Mar 2026 14:40:57 +0800 Subject: [PATCH 1/4] fix: correct Associated Token Program ID for Solana ATA derivation The ASSOCIATED_TOKEN_PROGRAM_ID constant was set to ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJe1bRS (not a real program), instead of the correct Solana mainnet program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL. This caused _get_ata() to derive incorrect destination ATAs for every Solana payment, resulting in the facilitator rejecting all payments with `invalid_exact_svm_payload_recipient_mismatch`. --- blockrun_llm/x402.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockrun_llm/x402.py b/blockrun_llm/x402.py index 647d545..7b8c34f 100644 --- a/blockrun_llm/x402.py +++ b/blockrun_llm/x402.py @@ -244,7 +244,7 @@ def extract_payment_details(payment_required: Dict[str, Any]) -> Dict[str, Any]: # SPL program IDs TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" -ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJe1bRS" +ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" # Compute budget defaults (match @x402/svm) DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1 From f7e4baff48e57b29a692f90f2e85759e1aa7e9ee Mon Sep 17 00:00:00 2001 From: "Notorious D.E.V" Date: Fri, 13 Mar 2026 14:44:37 +0800 Subject: [PATCH 2/4] test: add regression tests for ATA derivation and program ID Verifies ASSOCIATED_TOKEN_PROGRAM_ID matches the real Solana mainnet program, and that _get_ata() produces correct ATAs using a known on-chain address as a reference. --- tests/unit/test_x402.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/unit/test_x402.py b/tests/unit/test_x402.py index ec6a846..2040f7c 100644 --- a/tests/unit/test_x402.py +++ b/tests/unit/test_x402.py @@ -268,3 +268,23 @@ def test_payload_transaction_is_base64(self): # Should be valid base64 tx_bytes = base64.b64decode(decoded["payload"]["transaction"]) assert len(tx_bytes) > 0 + + +class TestAssociatedTokenProgramId: + """Verify the Associated Token Program ID is correct.""" + + def test_associated_token_program_id(self): + """ASSOCIATED_TOKEN_PROGRAM_ID must match Solana mainnet.""" + from blockrun_llm.x402 import ASSOCIATED_TOKEN_PROGRAM_ID + + assert ASSOCIATED_TOKEN_PROGRAM_ID == "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + + def test_ata_derivation(self): + """ATA derivation must match on-chain addresses.""" + from blockrun_llm.x402 import _get_ata, USDC_SOLANA + + # Known wallet -> known USDC ATA (verified on-chain) + owner = "CtJTYWPQSL5jw9B2JRHmpQjYCSSgUX3LRvmMBhq55HmQ" + expected_ata = "HZPPxg9ZyoHu4f2pj5uEEXsArLA2rnL9FtDgC8rrAp5Q" + + assert _get_ata(owner, USDC_SOLANA) == expected_ata From dde8dbe792fc6ed4fe30bb348501185424a8a653 Mon Sep 17 00:00:00 2001 From: "Notorious D.E.V" Date: Fri, 13 Mar 2026 20:17:02 +0800 Subject: [PATCH 3/4] fix: include v0 version prefix (0x80) when signing Solana transactions Solana v0 transactions require signatures over [0x80 + message_body], but the SDK was signing just [message_body]. This caused the facilitator to reject all payments with transaction_simulation_failed (SignatureFailure) because the signatures didn't match what the validator expected. --- blockrun_llm/x402.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blockrun_llm/x402.py b/blockrun_llm/x402.py index 7b8c34f..7da953f 100644 --- a/blockrun_llm/x402.py +++ b/blockrun_llm/x402.py @@ -383,7 +383,8 @@ def create_solana_payment_payload( # Partial sign: user signs, fee_payer (CDP) co-signs on server side # fee_payer is always first signer (index 0), owner is second (index 1) - msg_bytes = bytes(message) + # v0 messages must be signed with the 0x80 version prefix included + msg_bytes = b'\x80' + bytes(message) user_sig = keypair.sign_message(msg_bytes) null_sig = Signature.default() # placeholder for fee_payer tx = VersionedTransaction.populate(message, [null_sig, user_sig]) From f16e7f89a7f85dd5a8bae9db2409c57e007fe83c Mon Sep 17 00:00:00 2001 From: "Notorious D.E.V" Date: Fri, 13 Mar 2026 20:18:42 +0800 Subject: [PATCH 4/4] test: verify v0 transaction signature includes version prefix Ensures the user signature in Solana payment payloads is over 0x80 + message_body, not just message_body. Without the prefix, the facilitator's sigVerify simulation rejects the transaction. --- tests/unit/test_x402.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unit/test_x402.py b/tests/unit/test_x402.py index 2040f7c..efb5aef 100644 --- a/tests/unit/test_x402.py +++ b/tests/unit/test_x402.py @@ -270,6 +270,39 @@ def test_payload_transaction_is_base64(self): assert len(tx_bytes) > 0 + def test_v0_signature_includes_version_prefix(self): + """User signature must be over 0x80 + message_body for v0 transactions.""" + from blockrun_llm.x402 import create_solana_payment_payload + from solders.transaction import VersionedTransaction + from solders.keypair import Keypair + import json + import base64 + import base58 + + payload = create_solana_payment_payload( + private_key=self.TEST_BS58_KEY, + recipient=self.TEST_RECIPIENT, + amount="1000", + fee_payer=self.TEST_FEE_PAYER, + ) + decoded = json.loads(base64.b64decode(payload)) + tx_bytes = base64.b64decode(decoded["payload"]["transaction"]) + tx = VersionedTransaction.from_bytes(tx_bytes) + + # Recover the user keypair + secret = base58.b58decode(self.TEST_BS58_KEY) + keypair = Keypair.from_seed(secret[:32]) + + # The signing data for v0 must include the 0x80 prefix + msg_with_prefix = b'\x80' + bytes(tx.message) + + # Verify the user's signature (index 1) is over the prefixed message + from nacl.signing import VerifyKey + vk = VerifyKey(bytes(keypair.pubkey())) + # Should not raise + vk.verify(msg_with_prefix, bytes(tx.signatures[1])) + + class TestAssociatedTokenProgramId: """Verify the Associated Token Program ID is correct."""