Overview
Beacon Cloud uses on-chain cryptocurrency payment verification to provide paid AI inference services. Payments are accepted in USDC on Base and Solana networks, with real-time transaction verification before processing requests.
Payment verification is only required when using the beacon-ai-cloud provider. Self-hosted instances can disable this entirely.
Architecture
Payment Flow
1. Initial Request (No Payment)
When a client requests generation with provider: "beacon-ai-cloud" but no payment headers:
curl -X POST https://beacon-api.com/generate \
-H "Content-Type: application/json" \
-d '{
"name": "my-repo",
"provider": "beacon-ai-cloud",
"source_files": [...]
}'
Response (402 Payment Required):
HTTP/ 1.1 402 Payment Required
x-payment-run-id: 550e8400 -e 29 b -41 d 4 -a 716-446655440000
x-payment-amount: 0.09
x-payment-currency: USDC
x-payment-address-base: 0 x 833589 fCD 6 eDb 6E08 f 4 c 7 C 32 D 4 f 71 b 54 bdA 02913
x-payment-address-solana: EPjFWdd 5 AufqSSqeM 2 qN 1 xzybapC 8 G 4 wEGGkZwyTDt 1 v
{
"success" : false ,
"error" : "Payment required"
}
Implementation in src/main.rs:248-261:
let rid = db :: create_run ( & req . repo_context . name) . await ? ;
let amount = std :: env :: var ( "PAYMENT_AMOUNT_USDC" )
. unwrap_or_else ( | _ | "0.09" . to_string ());
let w_base = std :: env :: var ( "BEACON_WALLET_BASE" ) . unwrap_or_default ();
let w_sol = std :: env :: var ( "BEACON_WALLET_SOLANA" ) . unwrap_or_default ();
return Err ( errors :: BeaconError :: PaymentRequired {
run_id : rid ,
amount ,
base_addr : w_base ,
sol_addr : w_sol ,
});
2. Client Sends Payment
The client sends USDC to one of the provided addresses:
Base Network
Solana Network
# Using a wallet SDK or CLI
cast send 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
"transfer(address,uint256)" \
< BEACON_WALLE T > \
90000 # 0.09 USDC (6 decimals)
3. Retry with Transaction Hash
After the transaction confirms, the client retries with payment headers:
curl -X POST https://beacon-api.com/generate \
-H "Content-Type: application/json" \
-H "x-payment-run-id: 550e8400-e29b-41d4-a716-446655440000" \
-H "x-payment-chain: base" \
-H "x-payment-txn-hash: 0xabcd1234..." \
-d '{...}'
Transaction Verification
Base (Ethereum L2)
Beacon verifies Base transactions by:
Fetching the transaction receipt via RPC
Checking transaction status is successful
Finding USDC Transfer events
Validating recipient and amount
Implementation (src/verifier.rs:23-52):
async fn verify_base (
txn_hash : & str ,
expected_amount : f64 ,
expected_address : & str ,
) -> Result < bool > {
let rpc_url = std :: env :: var ( "BASE_RPC_URL" )
. unwrap_or_else ( | _ | "https://mainnet.base.org" . to_string ());
let provider = Provider :: < Http > :: try_from ( rpc_url ) ? ;
// Get transaction receipt
let hash = H256 :: from_str ( txn_hash ) ? ;
let receipt = provider . get_transaction_receipt ( hash ) . await ?
. context ( "Base transaction receipt not found" ) ? ;
// Check transaction succeeded
if receipt . status != Some ( 1. into ()) {
return Ok ( false );
}
// Verify USDC transfer event
let usdc_addr = Address :: from_str ( BASE_USDC ) ? ; // 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
let receiver_addr = Address :: from_str ( expected_address ) ? ;
let transfer_topic = H256 :: from_str (
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
) ? ;
for log in receipt . logs {
if log . address == usdc_addr
&& log . topics . len () == 3
&& log . topics[ 0 ] == transfer_topic
{
let to = Address :: from_slice ( & log . topics[ 2 ][ 12 .. ]);
if to == receiver_addr {
let value = U256 :: from_big_endian ( & log . data);
let amount_f64 = value . as_u128 () as f64 / 1_000_000.0 ; // USDC has 6 decimals
// Allow 0.001 USDC tolerance
if ( amount_f64 - expected_amount ) . abs () < 0.001 {
return Ok ( true );
}
}
}
}
Ok ( false )
}
Key Details:
USDC Contract : 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Transfer Event : Transfer(address indexed from, address indexed to, uint256 value)
Decimals : 6 (divide by 1,000,000)
Tolerance : ±0.001 USDC
Solana
Solana verification uses JSON-RPC to:
Fetch transaction details
Compare pre/post token balances
Validate the balance increase matches payment
Implementation (src/verifier.rs:54-94):
async fn verify_solana (
txn_hash : & str ,
expected_amount : f64 ,
expected_address : & str ,
) -> Result < bool > {
let rpc_url = std :: env :: var ( "SOLANA_RPC_URL" )
. unwrap_or_else ( | _ | "https://api.mainnet-beta.solana.com" . to_string ());
let client = reqwest :: Client :: new ();
let resp = client . post ( rpc_url )
. json ( & json! ({
"jsonrpc" : "2.0" ,
"id" : 1 ,
"method" : "getTransaction" ,
"params" : [
txn_hash ,
{ "encoding" : "json" , "maxSupportedTransactionVersion" : 0 }
]
}))
. send () . await ?. json :: < Value >() . await ? ;
let meta = & resp [ "result" ][ "meta" ];
if meta . is_null () { return Ok ( false ); }
// Check pre/post token balances
let pre = meta [ "preTokenBalances" ] . as_array () ? ;
let post = meta [ "postTokenBalances" ] . as_array () ? ;
let mut pre_val = 0.0 ;
for b in pre {
if b [ "mint" ] == SOLANA_USDC && b [ "owner" ] == expected_address {
pre_val = b [ "uiTokenAmount" ][ "uiAmount" ] . as_f64 () . unwrap_or ( 0.0 );
break ;
}
}
let mut post_val = 0.0 ;
for b in post {
if b [ "mint" ] == SOLANA_USDC && b [ "owner" ] == expected_address {
post_val = b [ "uiTokenAmount" ][ "uiAmount" ] . as_f64 () . unwrap_or ( 0.0 );
break ;
}
}
// Verify amount difference
if ( post_val - pre_val - expected_amount ) . abs () < 0.001 {
return Ok ( true );
}
Ok ( false )
}
Key Details:
USDC Mint : EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Balance Comparison : postBalance - preBalance
Tolerance : ±0.001 USDC
Replay Attack Prevention
Beacon prevents transaction replay attacks by tracking used transaction hashes in the database.
Check Implementation (src/main.rs:219-221 and src/db.rs:147-160):
if db :: payment_already_used ( txn ) . await . unwrap_or ( false ) {
return Err ( errors :: BeaconError :: TransactionAlreadyUsed );
}
pub async fn payment_already_used ( txn_hash : & str ) -> Result < bool > {
let db = client () ? ;
let resp = db . from ( PAYMENTS_TABLE )
. eq ( "txn_hash" , txn_hash )
. select ( "id" )
. execute ()
. await ? ;
let body = resp . text () . await ? ;
let records : Value = serde_json :: from_str ( & body ) ? ;
Ok ( records . as_array () . map ( | a | ! a . is_empty ()) . unwrap_or ( false ))
}
Once verified, the transaction hash is recorded:
db :: record_payment ( rid , txn , ch , None ) . await . ok ();
Database Schema
Runs Table
CREATE TABLE runs (
id UUID PRIMARY KEY ,
repo_name TEXT NOT NULL ,
provider TEXT NOT NULL ,
status TEXT NOT NULL , -- 'pending', 'paid', 'complete', 'failed'
txn_hash TEXT ,
chain TEXT ,
agents_md TEXT ,
error TEXT ,
created_at TIMESTAMP DEFAULT NOW ()
);
Payments Table
CREATE TABLE payments (
id UUID PRIMARY KEY ,
run_id UUID REFERENCES runs(id),
txn_hash TEXT UNIQUE NOT NULL ,
chain TEXT NOT NULL ,
amount_usdc NUMERIC NOT NULL ,
from_address TEXT ,
confirmed BOOLEAN DEFAULT true,
confirmed_at TIMESTAMP DEFAULT NOW ()
);
Error Handling
Transaction Not Found
HTTP/ 1.1 500 Internal Server Error
{
"success" : false ,
"error" : "Verification failed: Base transaction receipt not found"
}
Insufficient Payment
HTTP/ 1.1 402 Payment Required
{
"success" : false ,
"error" : "Payment not verified"
}
Transaction Already Used
HTTP/ 1.1 409 Conflict
{
"success" : false ,
"error" : "Transaction hash already used"
}
Testing Payment Verification
Using Testnets
For development, use testnet configurations:
BASE_RPC_URL = https://sepolia.base.org
SOLANA_RPC_URL = https://api.devnet.solana.com
BEACON_WALLET_BASE = 0xYourTestWallet
BEACON_WALLET_SOLANA = YourDevnetWallet
PAYMENT_AMOUNT_USDC = 0.01
Mock Verification
For local testing, you can bypass verification:
// In src/verifier.rs, add mock mode
pub async fn verify_payment (
chain : & str ,
txn_hash : & str ,
expected_amount : f64 ,
expected_address : & str ,
) -> Result < bool > {
if std :: env :: var ( "MOCK_PAYMENT_VERIFICATION" ) . is_ok () {
return Ok ( true ); // Always verify in test mode
}
// ... existing verification logic
}
Security Considerations
Important security notes:
Always verify transaction finality (confirmations) before accepting
Use secure RPC endpoints (not public, rate-limited ones)
Implement idempotency to handle duplicate requests
Monitor for suspicious patterns (same address, rapid requests)
Keep wallet private keys in secure vaults (HSM, KMS)
Best Practices
Confirmation Depth : Wait for sufficient confirmations
Base: 10+ blocks (~20 seconds)
Solana: Finalized commitment level
RPC Redundancy : Use multiple RPC providers
let providers = vec! [
"https://mainnet.base.org" ,
"https://base-mainnet.g.alchemy.com/v2/YOUR_KEY" ,
];
Amount Tolerance : Keep tight tolerance (0.001 USDC)
Transaction Indexing : Index all payment transactions for auditing
Self-Hosted: Disabling Payments
To run Beacon without payment verification:
Remove beacon-ai-cloud provider checks
Use direct AI provider APIs (Gemini, Claude, OpenAI)
Set your own API keys in environment variables
# Self-hosted configuration
GEMINI_API_KEY = your_key
REDIS_URL = redis://localhost:6379
# No SUPABASE or payment variables needed
Generate without payment:
beacon generate ./repo --provider gemini
Next Steps
Configuration Configure payment wallets and RPC endpoints
Custom Deployment Deploy with payment verification enabled