Solana Reference Implementation

The SPX protocol is chain-agnostic. This is one implementation — an Anchor program on Solana that proves the protocol works. It's deployed on devnet with 77 passing tests. Anyone could write their own implementation on any chain that supports Ed25519.

Program ID (devnet): GX6RbhyuZnWC3CRDzrVreZR76dbstsS2VcpttdAWVMpQ

Account Types

EscrowAccount (206 bytes)

PDA seed: ["escrow", owner_pubkey, label]

pub struct EscrowAccount {
    pub owner: Pubkey,              // Can withdraw, freeze, close
    pub agent: Pubkey,              // Signs vouchers off-chain
    pub deposited: u64,             // Total ever deposited (append-only)
    pub settled: u64,               // Total settled to services (append-only)
    pub total_withdrawn: u64,       // Total withdrawn by owner (append-only)
    pub created_at: i64,            // Cross-session replay protection
    pub expires_at: i64,            // 0 = no expiry
    pub state: EscrowState,         // Active | Frozen | Closed
    pub vault: Pubkey,              // ATA holding escrowed tokens
    pub label: String,              // Max 16 bytes, for multi-escrow
    pub mint: Pubkey,               // SPL token mint (e.g. USDC)
    pub rent_subsidy_per_service: u64,
}

Available balance: deposited - settled - total_withdrawn

An owner can create multiple escrows with different labels — one per agent, per budget, or per use case.

SettlementRecord (97 bytes)

PDA seed: ["settlement", escrow_pubkey, service_pubkey]

pub struct SettlementRecord {
    pub escrow: Pubkey,
    pub service: Pubkey,
    pub settled_amount: u64,        // Cumulative settled to this service
    pub last_nonce: u64,            // Replay protection
    pub escrow_created_at: i64,     // Cross-session binding
}

Created automatically on first settlement. The vendor pays rent, but the escrow reimburses the SOL cost via rent_subsidy_per_service.

ConfigAccount (107 bytes)

PDA seed: ["config"]

pub struct ConfigAccount {
    pub authority: Pubkey,          // Cold key — can transfer operator
    pub operator: Pubkey,           // Hot key — can update fee/treasury
    pub treasury: Pubkey,           // Receives protocol fees
    pub fee_bps: u16,              // Max 1000 (10%)
}

Instructions

Configuration

InstructionSignerDescription
initialize_configAuthorityCreate the global config (once)
update_configOperatorUpdate fee rate or treasury address
transfer_operatorAuthorityRotate the operator key

Escrow Management

InstructionSignerDescription
create_escrowOwnerCreate a new escrow with label, mint, agent, expiry
depositOwnerFund the escrow vault (works when Active or Frozen)
withdrawOwnerWithdraw unspent funds
freezeOwnerBlock all settlements
unfreezeOwnerResume settlements
update_agentOwnerRotate the agent signing key
extend_expiryOwnerPush the expiry date forward
close_escrowOwnerClose escrow and reclaim rent (must be fully settled)

Settlement

InstructionSignerDescription
settleServiceSubmit a voucher and collect payment
close_settlement_recordServiceReclaim settlement record rent

Settlement Flow

The settle instruction is the core of the program (337 lines). Here's what it does:

1. Read Ed25519 precompile instruction from sysvar
2. Verify signature matches escrow.agent key
3. Verify escrow_created_at matches the escrow
4. Verify nonce > settlement_record.last_nonce
5. Compute delta = voucher.cumulative - settlement_record.settled_amount
6. Compute fee = (delta as u128 * fee_bps as u128) / 10_000
7. Transfer (delta - fee) tokens → service
8. Transfer fee tokens → treasury (if fee > 0)
9. Update escrow.settled += delta
10. Update settlement_record (new cumulative, new nonce)
11. Emit Settled event

The Ed25519 verify instruction must be at transaction index 0. The settle instruction reads it from the sysvar — it doesn't re-verify the signature itself.

Fee Model

  • Rate: 50 basis points (0.5%) on the settlement delta
  • Basis: Applied to the incremental amount, not the cumulative total
  • Overflow protection: u128 intermediate for delta * fee_bps
  • Dust handling: If fee rounds to 0, skip the treasury transfer
  • Cap: Maximum 1,000 bps (10%), enforced on-chain
Settlement DeltaFee (0.5%)Service Receives
$500.00$2.50$497.50
$1.00$0.005$0.995
$0.0001$0 (dust)$0.0001

Events

The program emits events on every state change for off-chain indexing:

ConfigInitialized, ConfigUpdated, EscrowCreated, Deposited, Settled, Withdrawn, AgentUpdated, EscrowFrozen, EscrowClosed

Error Codes

CodeNameMeaning
6000InvalidEd25519ProgramWrong program for signature verification
6001InvalidEd25519InstructionMalformed Ed25519 instruction
6002SignatureMismatchVoucher signature doesn't match agent key
6003InvalidNonceNonce ≤ last settled nonce
6004InsufficientFundsEscrow balance can't cover the delta
6005OverflowArithmetic overflow in fee calculation
6006EscrowNotActiveEscrow is frozen or closed
6007InvalidAmountZero or negative amount
6008LabelTooLongLabel exceeds 16 bytes
6009ExpiryInPastExpiry timestamp is in the past
6010EscrowExpiredEscrow has expired
6011FeeTooHighFee exceeds 1,000 bps cap
6012SessionMismatchVoucher's created_at doesn't match escrow
6013EscrowNotSettledCan't close — unsettled balance remains
6014UnauthorizedSigner is not the owner/operator/authority
6015InvalidMintToken mint doesn't match escrow
6016AlreadyFrozenEscrow is already frozen
6017NotFrozenEscrow is not frozen (can't unfreeze)
6018InvalidTreasuryTreasury account doesn't match config
6019ExpiryNotExtendedNew expiry is not later than current
6020InvalidServiceKeyVoucher service key doesn't match signer
6021InvalidEscrowKeyVoucher escrow key doesn't match account

Deployment

The program is managed through Squads multisig on devnet:

  • Upgrade authority → Squads vault (requires multisig approval)
  • Config authority → Cold key in Squads
  • Operator → Hot key for fee/treasury updates
  • Treasury → Separate wallet for fee collection

The deployer keypair is ephemeral — created for initial deployment, then authority is transferred to Squads.

Next: SDK & integration →