Skip to main content

Overview

meridian_x402 is the Solana (SVM) port of the Meridian facilitator. It is the equivalent of X402ProxyFacilitator.transferWithAuthorization on EVM: it pulls USDC from a payer, subtracts platform and treasury fees, and settles the remainder to the recipient — atomically, in a single transaction. This first version is intentionally minimal. It settles same-chain USDC payments on Solana and nothing else: no cross-chain (Across) routing, no MRDN cashback, and no batched settlement (those live only on the EVM facilitator today).
Integrations should read the program ID, facilitator pubkey, and on-chain config from GET /v1/solana/facilitator at runtime rather than hardcoding them, so they keep working across any redeploys.

Deployment

NetworkClusterProgram IDStatus
solanamainnet-betaRo6hz1smrm5zDh73849eDqKna9dE1EkPsWekAB5rBWmLive
solana-devnetdevnetRo6hz1smrm5zDh73849eDqKna9dE1EkPsWekAB5rBWmLive
Built with Anchor 0.32.1. The config account is a PDA at seeds ["config"], derived from the program ID. USDC mints the program settles against:
NetworkUSDC Mint
solanaEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
solana-devnet4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU

Why it looks different from the EVM facilitator

On EVM, the payer cannot join the settlement transaction without paying gas, so x402 relies on EIP-3009: the payer signs an off-chain authorization, and the facilitator submits it on-chain where the token contract verifies the signature. Solana needs none of that machinery:
  1. A transaction can have multiple signers, and any of them can pay the fee. The payer (from) signs the settlement transaction as the authority of their USDC token account. The backend signer co-signs as payer (fee payer) and submits it. The payer’s transaction signature is the authorization — no in-program signature verification required.
  2. Settlement is atomic within one transaction, so there is no escrow hop. The EVM contract first pulls the full amount into itself and then distributes; here the program splits funds directly from the payer’s token account via up to three transfer_checked CPIs (platform fee, treasury fee, recipient net).
From the client’s perspective the x402 flow is unchanged: What is kept for parity with the EVM transferWithAuthorization:
  • valid_after / valid_before settlement window, checked against the on-chain clock
  • a 32-byte nonce for x402 payment correlation and idempotency
  • identical fee economics (platform fee off gross, treasury fee off the remainder)

Architecture

Config account

A single global PDA (seeds = ["config"]) holds the program’s configuration:
FieldTypePurpose
authorityPubkeyAdmin: fee updates, pause, treasury / signer rotation
backend_signerPubkeyBackend signer allowed to submit settlements
treasuryPubkeyWallet that owns the treasury-fee token account
usdc_mintPubkeyThe only mint the program settles
treasury_fee_bpsu16Protocol fee in basis points (max 1000 = 10%)
pausedboolCircuit breaker for the settlement entrypoint
bumpu8Config PDA bump

Instructions

InstructionAccessPurpose
initialize(backend_signer, treasury, treasury_fee_bps)one-timeCreate the Config PDA
transfer_with_authorization(value, valid_after, valid_before, nonce, platform_fee_bps)payer + backend signer signSettle a payment
set_treasury_fee_bps(bps)authorityUpdate the protocol fee
set_treasury(pubkey)authorityRotate the treasury wallet
set_backend_signer(pubkey)authorityRotate the backend signer
set_paused(bool)authorityPause / unpause settlement
transfer_with_authorization account list:
AccountSignerNotes
payerBackend signer (facilitator): pays the tx fee
fromThe paying user (USDC token authority)
configConfig PDA
usdc_mintMust match config.usdc_mint
from_tokenPayer’s USDC associated token account (authority = from)
recipient_tokenRecipient’s USDC associated token account
platform_tokenOptional — omit for payments without a platform fee
treasury_tokenUSDC token account owned by config.treasury
token_programSPL Token / Token-2022 program
The settlement transaction does not create any token accounts. The payer, recipient, platform (if used), and treasury USDC associated token accounts must already exist, or settlement fails. The backend signer pays only the transaction fee, never rent.

Fee math

Identical to the EVM facilitator’s _settleAndDistribute:
platform_fee = value * platform_fee_bps / 10_000       (0 if no platform account)
remainder    = value - platform_fee
treasury_fee = remainder * treasury_fee_bps / 10_000   (skipped if platform == treasury)
net_amount   = remainder - treasury_fee                (→ recipient)
Both fees are capped at MAX_FEE_BPS = 1000 (10%). Multiplication is widened to u128 so it cannot overflow. Each settlement emits an InstantSettlement event with the full breakdown:
InstantSettlement {
    from,            // payer wallet
    recipient,       // recipient token account
    platform,        // Option<platform token account>
    nonce,           // [u8; 32] — x402 payment correlation id
    gross_amount,    // value
    platform_fee,
    treasury_fee,
    net_amount,      // credited to the recipient
}

Replay protection & the nonce

Replay protection is transaction-level, matching the reference x402 SVM scheme. The paying user signs the settlement transaction itself, which embeds a recent blockhash: the runtime rejects duplicate signatures, and the transaction expires with the blockhash. Settling the same payment again would require a fresh signature from the payer. No nonce account is created on-chain, so no rent is locked per payment. The 32-byte nonce argument is carried into the InstantSettlement event for x402 payment correlation, and the facilitator additionally refuses to co-sign a nonce it has already settled (a settlement cache in the payments table).

Errors

ErrorMeaning
PausedThe settlement entrypoint is paused
UnauthorizedCaller is not the config authority / backend signer
FeeTooHighA fee exceeds MAX_FEE_BPS (10%)
InvalidValuevalue (or net after fees) is not greater than zero
AuthorizationNotYetValidnow <= valid_after
AuthorizationExpirednow >= valid_before
InvalidMintMint does not match config.usdc_mint
(Treasury-token ownership is enforced by an Anchor account constraint, which surfaces as a generic constraint error rather than a program-specific code.)

How settlement is validated

The client builds and payer-signs the settlement transaction; the facilitator’s POST /v1/settle acts as the co-signing safety boundary. Before it adds the backend signer’s signature and submits, it checks that the transaction does exactly one thing the backend signer is willing to pay for:
  • the fee payer is the backend signer, and the transaction contains exactly one transfer_with_authorization instruction against the Meridian program
  • payer (account 0) is the backend signer; from (account 1) is a different signer
  • config is the expected Config PDA and the token program is the SPL Token program
  • the mint equals paymentRequirements.asset, and value equals maxAmountRequired
  • the validity window is still open (valid_after <= now, valid_before >= now + 6s)
  • from_token, recipient_token, and any platform_token are the correct associated token accounts for the resolved recipient / platform, and platform_fee_bps matches the organization’s configured fee
  • the payer’s wallet signature is present and valid, and the backend signer has not already signed
Because the program only moves USDC with the payer’s signature and never creates accounts, a structurally valid transaction means the backend signer risks only the transaction fee. The recipient (and platform, in platform mode) is resolved from the caller’s Meridian organization settings, not blindly trusted from the request.

Deliberately omitted (vs. EVM V6)

Settlement is instant to every party, so there are no fee-accrual balances or withdraw functions. Also out of scope for this version, all of which remain EVM-only for now:
  • MRDN cashback
  • Cross-chain (Across) settlement
  • Batch payments

Building & running locally

The program lives in contracts-sol/ in the Meridian monorepo. Requires the Solana CLI (Agave) and Anchor 0.32.1 (avm use 0.32.1).
cd contracts-sol
anchor build   # compile + generate the IDL (target/idl/meridian_x402.json)
anchor test    # spin up a local validator and run the test suite
The test suite covers the exact fee splits (with and without a platform fee), nonce replay rejection, the validity window, the fee cap, a missing payer signature, the treasury-as-platform skip, pause / unpause, and admin access control. See contracts-sol/README.md and contracts-sol/SETUP.md for deploying to devnet and running the facilitator + pay app against it.

References