Local Transaction API
The Local Transaction API provides maximum security and control for x402 payments. We build the payment transaction for you, but you sign and broadcast it using your own wallet and RPC endpoint.
Overview
Base URL: https://api.interface402.dev
Flow:
Request a payment transaction from Interface402
Receive base64-encoded unsigned transaction
Sign the transaction with your wallet
Broadcast to blockchain using your RPC endpoint
Why Use Local Transactions?
Full Control: You sign and send payment transactions yourself
Custom RPC: Use your own blockchain RPC infrastructure
Maximum Security: Private keys never leave your system
Transaction Transparency: Inspect payment transactions before signing
Institutional Grade: Meets compliance requirements for payment processing
Multi-Chain Support: Works with EVM and Solana chains
Endpoints
Build Send Payment Transaction
Build an unsigned transaction for sending a payment over the x402 protocol.
Endpoint: POST /transaction/send
Request Body:
walletAddress
string
Yes
Your wallet address (EVM or Solana format)
recipientAddress
string
Yes
Payment recipient's wallet address
amount
number
Yes
Payment amount in native currency
resource
string
No
Protected resource URL being accessed
metadata
object
No
Additional payment metadata
priorityFee
number
No
Network priority fee (default: auto)
Example Request:
curl -X POST https://api.interface402.dev/transaction/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"walletAddress": "7BgBvyjrZX8YTqjkKrfbSx9X8QP4NDaP1hj4VKMjqA5s",
"recipientAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": 0.001,
"resource": "https://api.example.com/protected-endpoint",
"metadata": {
"description": "API access payment"
},
"priorityFee": 0.0001
}'Success Response (200):
{
"transaction": "BASE64_ENCODED_TRANSACTION_DATA",
"lastValidBlockHeight": 123456789,
"paymentProof": "0x7f8a3b2c...",
"expiresAt": 1697234627890,
"estimatedConfirmation": "2-5 seconds"
}Build Receive Payment Transaction
Build an unsigned transaction for receiving and verifying a payment on the seller side.
Endpoint: POST /transaction/receive
Request Body:
walletAddress
string
Yes
Your receiving wallet address
paymentProof
string
Yes
Cryptographic payment proof from buyer
expectedAmount
number
Yes
Expected payment amount
resource
string
No
Protected resource being accessed
facilitatorUrl
string
No
Facilitator URL for verification
priorityFee
number
No
Network priority fee
Example Request:
curl -X POST https://api.interface402.dev/transaction/receive \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"walletAddress": "7BgBvyjrZX8YTqjkKrfbSx9X8QP4NDaP1hj4VKMjqA5s",
"paymentProof": "0x7f8a3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b",
"expectedAmount": 0.001,
"resource": "/api/protected-endpoint"
}'Success Response (200):
{
"transaction": "BASE64_ENCODED_TRANSACTION_DATA",
"lastValidBlockHeight": 123456789,
"verified": true,
"paymentId": "pay_3k9mN7PqRtV8sW2xY",
"expiresAt": 1697234627890
}Signing and Sending
After receiving the unsigned transaction, you need to sign and broadcast it.
JavaScript/TypeScript Example
import { Connection, Transaction, Keypair } from '@solana/web3.js';
async function signAndSendTransaction(
base64Transaction: string,
wallet: Keypair,
rpcEndpoint: string
) {
// Create connection to your RPC
const connection = new Connection(rpcEndpoint);
// Deserialize the transaction
const transaction = Transaction.from(
Buffer.from(base64Transaction, 'base64')
);
// Sign the transaction with your wallet
transaction.sign(wallet);
// Send the transaction
const signature = await connection.sendRawTransaction(
transaction.serialize()
);
console.log('Transaction sent:', signature);
// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature);
return {
signature,
confirmation
};
}
// Usage
async function executeSendPayment() {
// 1. Get unsigned payment transaction from Interface402
const response = await fetch('https://api.interface402.dev/transaction/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.LAYERX402_API_KEY}`
},
body: JSON.stringify({
walletAddress: wallet.publicKey.toBase58(),
recipientAddress: 'RECIPIENT_WALLET_ADDRESS',
amount: 0.001,
resource: 'https://api.example.com/protected-endpoint',
metadata: {
description: 'API access payment'
}
})
});
const data = await response.json();
// 2. Sign and send
const result = await signAndSendTransaction(
data.transaction,
wallet,
'https://api.mainnet-beta.solana.com'
);
console.log('Payment completed:', result);
console.log('Payment proof:', data.paymentProof);
}Using with Wallet Adapter (Browser)
import { useWallet, useConnection } from '@solana/wallet-adapter-react';
import { Transaction } from '@solana/web3.js';
function PaymentComponent() {
const { publicKey, signTransaction, sendTransaction } = useWallet();
const { connection } = useConnection();
const executePayment = async (recipientAddress, amount, resource) => {
if (!publicKey || !signTransaction) {
throw new Error('Wallet not connected');
}
// 1. Get unsigned payment transaction
const response = await fetch('https://api.interface402.dev/transaction/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.LAYERX402_API_KEY}`
},
body: JSON.stringify({
walletAddress: publicKey.toBase58(),
recipientAddress,
amount,
resource,
metadata: {
description: 'API access payment',
timestamp: Date.now()
}
})
});
const data = await response.json();
// 2. Deserialize transaction
const transaction = Transaction.from(
Buffer.from(data.transaction, 'base64')
);
// 3. Sign with wallet adapter
const signedTransaction = await signTransaction(transaction);
// 4. Send transaction
const signature = await connection.sendRawTransaction(
signedTransaction.serialize()
);
// 5. Confirm
await connection.confirmTransaction(signature);
console.log('Payment confirmed:', signature);
console.log('Payment proof:', data.paymentProof);
return { signature, paymentProof: data.paymentProof };
};
return (
<button onClick={() => executePayment('RECIPIENT_ADDRESS', 0.001, '/api/endpoint')}>
Send Payment
</button>
);
}Python Example
from solana.rpc.api import Client
from solana.transaction import Transaction
from solders.keypair import Keypair
import base64
import requests
def sign_and_send_transaction(
base64_transaction: str,
wallet: Keypair,
rpc_url: str
) -> str:
# Create RPC client
client = Client(rpc_url)
# Deserialize transaction
transaction_bytes = base64.b64decode(base64_transaction)
transaction = Transaction.deserialize(transaction_bytes)
# Sign transaction
transaction.sign(wallet)
# Send transaction
result = client.send_raw_transaction(transaction.serialize())
signature = result['result']
print(f'Transaction sent: {signature}')
# Wait for confirmation
client.confirm_transaction(signature)
return signature
# Usage
def execute_send_payment(wallet: Keypair):
# 1. Get unsigned payment transaction
response = requests.post(
'https://api.interface402.dev/transaction/send',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {os.getenv("LAYERX402_API_KEY")}'
},
json={
'walletAddress': str(wallet.pubkey()),
'recipientAddress': 'RECIPIENT_WALLET_ADDRESS',
'amount': 0.001,
'resource': 'https://api.example.com/protected-endpoint',
'metadata': {
'description': 'API access payment'
}
}
)
data = response.json()
# 2. Sign and send
signature = sign_and_send_transaction(
data['transaction'],
wallet,
'https://api.mainnet-beta.solana.com'
)
print(f'Payment completed: {signature}')
print(f'Payment proof: {data["paymentProof"]}')
return signatureRust Example
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
async fn sign_and_send_transaction(
base64_transaction: &str,
wallet: &Keypair,
rpc_url: &str,
) -> Result<String, Box<dyn std::error::Error>> {
// Create RPC client
let client = RpcClient::new(rpc_url.to_string());
// Deserialize transaction
let transaction_bytes = BASE64.decode(base64_transaction)?;
let mut transaction: Transaction = bincode::deserialize(&transaction_bytes)?;
// Sign transaction
transaction.sign(&[wallet], client.get_latest_blockhash()?);
// Send transaction
let signature = client.send_and_confirm_transaction(&transaction)?;
println!("Transaction sent: {}", signature);
Ok(signature.to_string())
}Transaction Expiration
Transactions have a limited validity period:
lastValidBlockHeight: Block height after which transaction is invalid
expiresAt: Unix timestamp when transaction expires (typically 60 seconds)
Always send transactions promptly after building them.
Best Practices
Verify Transaction Contents: Inspect the transaction before signing
Use Trusted RPC: Use your own RPC or a trusted provider
Handle Expiration: Build and send transactions quickly
Confirm Transactions: Always wait for confirmation
Store Private Keys Securely: Never expose private keys
Test Thoroughly: Test with small amounts first
Security Considerations
Inspect Before Signing
import { Transaction } from '@solana/web3.js';
function inspectTransaction(base64Tx: string) {
const tx = Transaction.from(Buffer.from(base64Tx, 'base64'));
console.log('Transaction details:');
console.log('- Instructions:', tx.instructions.length);
console.log('- Fee payer:', tx.feePayer?.toBase58());
console.log('- Recent blockhash:', tx.recentBlockhash);
// Inspect each instruction
tx.instructions.forEach((ix, i) => {
console.log(`Instruction ${i}:`, {
programId: ix.programId.toBase58(),
keys: ix.keys.length,
data: ix.data.length
});
});
}Simulate Before Sending
async function simulateTransaction(transaction: Transaction, connection: Connection) {
const simulation = await connection.simulateTransaction(transaction);
if (simulation.value.err) {
console.error('Simulation failed:', simulation.value.err);
throw new Error('Transaction would fail');
}
console.log('Simulation succeeded');
console.log('Logs:', simulation.value.logs);
return simulation;
}Error Handling
Same error codes as Lightning API apply. See Error Handling.
Next Steps
View WebSocket API for real-time data
Check Examples for complete integration code
Learn about Error Handling
Last updated
