scematica_protocol/
client.rs1use anyhow::{bail, Result};
10use base64::Engine;
11use solana_sdk::{
12 compute_budget::ComputeBudgetInstruction, message::Message, pubkey::Pubkey, signature::Keypair,
13 signer::Signer, transaction::Transaction,
14};
15use spl_associated_token_account::get_associated_token_address;
16use spl_token::instruction::transfer_checked;
17use std::str::FromStr;
18
19use crate::types::{PaymentPayload, PaymentRequirements, SvmExactPayload, X402_VERSION};
20
21pub fn build_payment_payload(
30 payer: &Keypair,
31 requirements: &PaymentRequirements,
32 token_decimals: u8,
33) -> Result<PaymentPayload> {
34 let payer_pubkey = payer.pubkey();
35 let asset_mint = Pubkey::from_str(&requirements.asset)
36 .map_err(|_| anyhow::anyhow!("Invalid asset mint: {}", requirements.asset))?;
37 let pay_to = Pubkey::from_str(&requirements.pay_to)
38 .map_err(|_| anyhow::anyhow!("Invalid pay_to address: {}", requirements.pay_to))?;
39
40 let source_ata = get_associated_token_address(&payer_pubkey, &asset_mint);
41 let dest_ata = get_associated_token_address(&pay_to, &asset_mint);
42
43 let cu_limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(50_000);
45 let cu_price_ix = ComputeBudgetInstruction::set_compute_unit_price(1);
46
47 let transfer_ix = transfer_checked(
49 &spl_token::id(),
50 &source_ata,
51 &asset_mint,
52 &dest_ata,
53 &payer_pubkey,
54 &[],
55 requirements.amount,
56 token_decimals,
57 )?;
58
59 let message = Message::new(
61 &[cu_limit_ix, cu_price_ix, transfer_ix],
62 None, );
64 let mut tx = Transaction::new_unsigned(message);
65 tx.partial_sign(&[payer], solana_sdk::hash::Hash::default());
66
67 let tx_bytes = bincode::serialize(&tx)?;
68 let tx_b64 = base64::engine::general_purpose::STANDARD.encode(&tx_bytes);
69
70 if requirements.scheme != "exact" {
71 bail!(
72 "Only 'exact' scheme is supported; got '{}'",
73 requirements.scheme
74 );
75 }
76
77 Ok(PaymentPayload {
78 x402_version: X402_VERSION,
79 scheme: "exact".into(),
80 network: requirements.network.clone(),
81 payload: SvmExactPayload {
82 transaction: tx_b64,
83 },
84 })
85}
86
87pub fn encode_payment_header(payload: &PaymentPayload) -> Result<String> {
89 let json = serde_json::to_vec(payload)?;
90 Ok(base64::engine::general_purpose::STANDARD.encode(&json))
91}