@@ -893,6 +893,75 @@ def test_solana_sign_versioned_v0_static_verified(self):
893893 self .assertEqual (len (resp .signature ), 64 )
894894 self .assertFalse (all (b == 0 for b in resp .signature ))
895895
896+ def test_solana_sign_x402_zero_lut_usdc_payment (self ):
897+ """Official x402 SVM shape clear-signs without blind signing.
898+
899+ The sponsor is fee payer, the KeepKey key is the token authority, the
900+ payment is TransferChecked, and payTo is supplied separately so the
901+ device must derive and verify its associated token account itself.
902+ """
903+ self .requires_firmware ("7.15.0" )
904+ self .requires_fullFeature ()
905+ self .setup_mnemonic_allallall ()
906+
907+ authority = self ._get_from_pubkey ()
908+ sponsor = b'\x10 ' * 32
909+ source = b'\x30 ' * 32
910+ pay_to = bytes ([
911+ 0xea , 0x4a , 0x6c , 0x63 , 0xe2 , 0x9c , 0x52 , 0x0a ,
912+ 0xbe , 0xf5 , 0x50 , 0x7b , 0x13 , 0x2e , 0xc5 , 0xf9 ,
913+ 0x95 , 0x47 , 0x76 , 0xae , 0xbe , 0xbe , 0x7b , 0x92 ,
914+ 0x42 , 0x1e , 0xea , 0x69 , 0x14 , 0x46 , 0xd2 , 0x2c ,
915+ ])
916+ destination_ata = bytes ([
917+ 0x67 , 0x30 , 0x2e , 0x49 , 0x18 , 0x94 , 0xd7 , 0x49 ,
918+ 0x2e , 0xa6 , 0xbe , 0x4f , 0x91 , 0x4e , 0xa4 , 0xf4 ,
919+ 0x5f , 0xa1 , 0x42 , 0xe6 , 0x45 , 0x86 , 0x7c , 0x91 ,
920+ 0x64 , 0xa2 , 0x76 , 0xd5 , 0xdd , 0x76 , 0xf0 , 0x76 ,
921+ ])
922+ usdc_mint = bytes ([
923+ 0xc6 , 0xfa , 0x7a , 0xf3 , 0xbe , 0xdb , 0xad , 0x3a ,
924+ 0x3d , 0x65 , 0xf3 , 0x6a , 0xab , 0xc9 , 0x74 , 0x31 ,
925+ 0xb1 , 0xbb , 0xe4 , 0xc2 , 0xd2 , 0xf6 , 0xe0 , 0xe4 ,
926+ 0x7c , 0xa6 , 0x02 , 0x03 , 0x45 , 0x2f , 0x5d , 0x61 ,
927+ ])
928+
929+ accounts = [
930+ sponsor , authority , source , destination_ata , usdc_mint ,
931+ self .COMPUTE_BUDGET_PROGRAM , self .TOKEN_PROGRAM ,
932+ self .MEMO_PROGRAM ,
933+ ]
934+ raw_tx = bytearray ([0x80 , 2 , 0 , 3 , len (accounts )])
935+ for account in accounts :
936+ raw_tx .extend (account )
937+ raw_tx .extend (b'\xbb ' * 32 )
938+ raw_tx .append (4 )
939+
940+ # ComputeBudget::SetComputeUnitLimit(120000)
941+ raw_tx .extend (bytes ([5 , 0 , 5 , 2 ]))
942+ raw_tx .extend (struct .pack ('<I' , 120000 ))
943+ # ComputeBudget::SetComputeUnitPrice(1000 micro-lamports)
944+ raw_tx .extend (bytes ([5 , 0 , 9 , 3 ]))
945+ raw_tx .extend (struct .pack ('<Q' , 1000 ))
946+ # SPL TransferChecked(source, mint, destination ATA, authority)
947+ raw_tx .extend (bytes ([6 , 4 , 2 , 4 , 3 , 1 , 10 , 12 ]))
948+ raw_tx .extend (struct .pack ('<Q' , 2000 ))
949+ raw_tx .append (6 )
950+ # Required x402 uniqueness memo: a 16-byte nonce encoded as hex.
951+ memo = b'00112233445566778899aabbccddeeff'
952+ raw_tx .extend (bytes ([7 , 1 , 1 , len (memo )]))
953+ raw_tx .extend (memo )
954+ raw_tx .append (0 ) # zero address-lookup tables
955+
956+ token_info = messages .SolanaTokenInfo (
957+ mint = usdc_mint , symbol = "USDC" , decimals = 6 )
958+ self .client .apply_policy ('AdvancedMode' , False )
959+ response = self .client .solana_sign_tx (
960+ parse_path ("m/44'/501'/0'/0'" ), bytes (raw_tx ),
961+ token_info = [token_info ], token_recipient_owner = [pay_to ])
962+ self .assertEqual (len (response .signature ), 64 )
963+ self .assertFalse (all (b == 0 for b in response .signature ))
964+
896965 def test_solana_sign_versioned_v0_opaque (self ):
897966 """Versioned v0 transaction whose instruction reaches into an address
898967 lookup table (an account index at or beyond the static account
0 commit comments