solana-x402-compliance
This server screens Solana addresses for compliance risks and handles x402 micro-payments automatically.
Inspects a Solana address against sanctions and threat-intelligence databases.
Returns a risk score, verdict, and flags for each screened address.
Verdicts are
CLEAR_TO_TRANSACT(score 0) for clear counterparties orBLOCKED(score 100) for malicious/sanctioned addresses.Flags include
OFAC_SANCTIONEDand exploit/drainer detection (EXPLOIT_DRAINER).Automatically negotiates and settles x402 payment-channel micro-payments for each screening call.
Provides compliance screening for Solana addresses, returning a risk score and verdict (CLEAR_TO_TRANSACT or BLOCKED), and supports settling compliance micropayments via x402 payment channels on Solana.
Solana Enterprise Payment Gateway
High-Throughput HTTP 402 Payment Channel Middleware
Language / runtime note. The Maven build targets Java 21 bytecode (
<java.version>21</java.version>), the Spring Boot 3.4 baseline. Container images build and run on Eclipse Temurin JDK 25 (eclipse-temurin:25-jdk/25-jre-alpine). Both are stated explicitly because the host toolchain is JDK 25 while the source level remains Java 21.
An institutional-grade, zero-Web3-SDK middleware that meters HTTP APIs with
the x402 protocol and
RFC 9110 §15.5.3
402 Payment Required semantics. It validates Ed25519-signed, off-chain payment
vouchers in-memory in under 5ms on the request hot path, persists every
verification to an append-only PostgreSQL audit ledger, and sweeps cumulative
channel balances on-chain in batched settlement transactions — all without any
Node.js sidecar, Python bridge, or generic Web3 Java wrapper.
AI Agent Integration (Model Context Protocol)
Autonomous AI agents can screen Solana addresses and settle compliance
micro-payments through our published Model Context Protocol
(MCP) server. The server is a zero-dependency x402 compliance tool: it speaks
the RFC 9110 402 Payment Required challenge-and-response protocol natively,
signs Ed25519 channel vouchers in-memory (Node.js built-in crypto, no Web3
SDK), and negotiates settlement on every call.
Direct Execution
npx -y @msantagiulianab/x402-mcp-serverConfiguration
The server reads two environment variables:
Variable | Value | Purpose |
|
| Gateway root URL |
|
| x402 payment channel id |
Claude Desktop
Add an entry to claude_desktop_config.json.
macOS / Linux
{
"mcpServers": {
"solana-x402-compliance": {
"command": "npx",
"args": ["-y", "@msantagiulianab/x402-mcp-server"],
"env": {
"X402_GATEWAY_URL": "https://msb-solana-enterprise-payment-gateway.duckdns.org",
"X402_CHANNEL_ID": "chan_smoke_test_001"
}
}
}
}Windows
{
"mcpServers": {
"solana-x402-compliance": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@msantagiulianab/x402-mcp-server"],
"env": {
"X402_GATEWAY_URL": "https://msb-solana-enterprise-payment-gateway.duckdns.org",
"X402_CHANNEL_ID": "chan_smoke_test_001"
}
}
}
}VS Code Cline
Add the server to cline_mcp_settings.json:
{
"mcpServers": {
"solana-x402-compliance": {
"command": "npx",
"args": ["-y", "@msantagiulianab/x402-mcp-server"],
"env": {
"X402_GATEWAY_URL": "https://msb-solana-enterprise-payment-gateway.duckdns.org",
"X402_CHANNEL_ID": "chan_smoke_test_001"
}
}
}
}Verified Dual Compliance Screening Outcomes
The screen_solana_address tool returns one of two verified outcomes:
Counterparty | Risk score | Verdict | Flags |
Clear counterparty |
|
| none |
Malicious / sanctioned counterparty |
|
|
|
Related MCP server: solana-forensics
Model Context Protocol (MCP) Server
This repository contains the official open-source MCP server implementation located at /agent-tools/mcp-server.
Implementation: TypeScript (
agent-tools/mcp-server/src)NPM Package:
@msantagiulianab/x402-mcp-serverExecutable:
npx -y @msantagiulianab/x402-mcp-server
Table of Contents
1. Executive Architecture & Core Thesis
Problem
Base-layer blockchain latency and per-transaction fees are fundamentally incompatible with high-throughput, micro-metered HTTP APIs. Use cases such as:
AI inference billed per token or per call,
RWA (real-world asset) valuation feeds billed per oracle read,
Compliance / sanctions screening billed per address,
…each issue millions of sub-cent requests per day. Settling every call directly on Solana would impose block-confirmation latency (hundreds of milliseconds to seconds) and a per-transaction fee that dwarfs the price of a single metered call. The economics do not close, and the user experience collapses.
Solution
The gateway decouples the metering decision from the settlement transaction using the x402 HTTP challenge-and-response protocol:
Monotonic unidirectional off-chain state channels. A client presents a signed voucher whose
cumulativeAmountAtomiconly ever increases for a channel. The gateway trusts the voucher only insofar as (a) it is signed by the channel owner, (b) its nonce is strictly monotonic, and (c) its cumulative spend does not exceed the verified on-chain escrow deposit.RFC 9110
402 Payment Requiredfilter. A SpringOncePerRequestFilterissues aPAYMENT-REQUIREDchallenge to unauthenticated callers and accepts aPAYMENT-SIGNATUREvoucher on retry, attaching aPAYMENT-RESPONSEreceipt on success.< 5ms hot path. Voucher verification is pure in-memory Ed25519 crypto plus a short-TTL cache of the escrow balance. No synchronous Solana RPC call is ever made on the HTTP request path.
Batched on-chain settlement. An administrative endpoint sweeps a channel's highest-nonce verified cumulative amount to the treasury in a single signed transaction, recorded atomically in the audit ledger.
The result is deterministic, single-digit-millisecond payment gating with fail-closed rejection, a complete audit trail, and on-chain finality only where it matters: at settlement.
Zero-Dependency Philosophy
The gateway deliberately avoids all generic Web3 SDK baggage:
Concern | Implementation | Dependency |
Ed25519 signing/verification |
|
|
Base58 codec | Hand-rolled | zero |
| Hand-rolled | zero |
Canonical account sorting |
| zero |
Transaction serialization & signing |
| zero |
Solana RPC | JDK | zero |
There is no @solana/web3.js, no solana4j/p4j, and no runtime
invocation of an external process. Wire transactions are serialized byte-by-byte
from first principles, and the JVM-native crypto stack is the only third-party
cryptographic primitive.
2. System Components
The gateway follows a strict layered separation:
HTTP Request
│
▼
X402PaymentFilter (OncePerRequestFilter) ── 402 challenge / 403 reject / pass-through
│
├──▶ ChannelVoucherVerifier (service) ── in-memory nonce watermark + Ed25519 + ceiling check
│ └──▶ EscrowBalanceProvider ── SolanaEscrowVerifier (short-TTL cache)
│
├──▶ PaymentAuditService (service) ── append VERIFIED record
│ └──▶ PaymentAuditRepository (JPA) ── append-only PostgreSQL ledger
│
└──▶ downstream @RestController ── compliance screening, (your) metered APIs
Settlement (admin, off hot path):
POST /api/v1/settlement/channels/{id}/sweep
│
▼
ChannelSettlementService (service)
├──▶ SolanaRpcClient ── getLatestBlockhash / sendTransaction (JSON-RPC 2.0)
├──▶ SolanaWireTransactionBuilder ── serialize + sign (in-process)
└──▶ PaymentAuditRepository ── VERIFIED → SETTLED (markSettled)Component | Package | Responsibility |
|
| 402 challenge, header decode, 403 fail-closed, receipt attach |
|
| in-memory anti-replay + signature + escrow-ceiling checks |
|
| on-chain escrow balance with short-TTL cache ( |
|
| append-only audit persistence |
|
| on-chain sweep + |
|
| read/insert only (no update/delete declared) |
|
| fail-closed JSON-RPC 2.0 ( |
|
| legacy tx wire format, account sorting, SOL/SPL instructions |
|
| BouncyCastle Ed25519 verify |
|
| keypair derivation + in-process signing |
|
| zero-dependency codecs & validation |
3. Complete Protocol Sequence Diagram
The full x402 challenge-and-response lifecycle across the client, the gateway (filter / verifier / ledger / settlement), and the Solana RPC node:
Client Gateway (Spring Boot) Solana RPC PostgreSQL
│ │ │ │
│ 1. POST /api/v1/... │ │ │
│ (no payment headers) │ │ │
│ ─────────────────────────▶│ │ │
│ │ X402PaymentFilter.shouldNotFilter() │ │
│ │ · /api/v1/* → protected │ │
│ │ · /api/v1/settlement/* → skip (admin) │ │
│ │ │ │
│ 2. HTTP 402 + PAYMENT-REQUIRED (Base64 challenge JSON) │ │
│ ◀─────────────────────────│ │ │
│ │ │ │
│ 3. POST /api/v1/... │ │ │
│ + PAYMENT-SIGNATURE │ │ │
│ (Base64 voucher JSON) │ │ │
│ ─────────────────────────▶│ │ │
│ │ 4. Base64-decode → PaymentVoucher record │ │
│ │ 5. ChannelVoucherVerifier.verifyVoucher() │ │
│ │ a. Base58 payer key valid? │ │
│ │ b. nonce > lastSeenNonce[channel]? │ │
│ │ c. cumulative ≤ escrow ceiling? │ │
│ │ (short-TTL cache; NO RPC on hot path) │ │
│ │ d. Ed25519 verify(canonical bytes) │ │
│ │ ── any failure → 403 Forbidden (fail- │ │
│ │ closed) and audit log │ │
│ │ 6. PaymentAuditService.recordVerifiedVoucher │ │
│ │ ─────────────────────────────────────────────────────────────────▶ append
│ │ │ VERIFIED row
│ 7. HTTP 200 + PAYMENT-RESPONSE (Base64 receipt JSON) │ │
│ ◀─────────────────────────│ │ │
│ │ │ │
│ [later] POST /api/v1/settlement/channels/{id}/sweep │ │
│ ─────────────────────────▶│ │ │
│ │ 8. ChannelSettlementService.settleChannel() │ │
│ │ a. load latest VERIFIED record │ │
│ │ b. getLatestBlockhash() ──────────────────▶│ │
│ │ ◀─────────────────────────────── blockhash │ │
│ │ c. SolanaWireTransactionBuilder │ │
│ │ .serializeAndSign() (in-process) │ │
│ │ d. sendTransaction(wireTx) ───────────────▶│ │
│ │ ◀─────────────────────────────── txSignature │ │
│ │ 9. record.markSettled(txSignature) │ │
│ │ ─────────────────────────────────────────────────────────────────▶ SETTLED row
│ 10. HTTP 200 { txSignature, settledAmountAtomic, ... } │ │
│ ◀─────────────────────────│ │ │Key invariant: steps 4–6 never touch the network. The only RPC interactions are the off-path settlement sweep (step 8), keeping the request hot path deterministic and sub-5ms.
4. Cryptographic & Voucher Wire Specification
Canonical byte layout
A voucher is signed over a deterministic, domain-separated byte string (see
PaymentVoucher#getCanonicalPayload()):
"X402_CHANNEL_V1:" || u16le(channelId.length) || channelId || i64le(cumulativeAmountAtomic) || i64le(nonce)Offset | Size (bytes) | Field | Encoding |
| 16 | domain tag | ASCII |
| 2 |
| unsigned 16-bit little-endian ( |
|
|
| raw UTF-8 bytes |
| 8 |
| signed 64-bit little-endian ( |
| 8 |
| signed 64-bit little-endian ( |
The domain tag provides cross-protocol disambiguation (a signature over these
bytes can never be replayed against another message scheme). Both integer fields
are little-endian to match the JVM ByteOrder.LITTLE_ENDIAN buffer and the
JavaScript reference signer in smoke-test.sh, which mirrors the layout exactly
(writeUInt16LE + writeBigInt64LE).
Ed25519 over Base58 public keys
The payer public key is a Base58-encoded 32-byte Ed25519 public key.
The signature is a Base58-encoded 64-byte Ed25519 signature.
Verification is
org.bouncycastle.crypto.signers.Ed25519Signerinitialized inverifymode withEd25519PublicKeyParameters(pubkeyBytes, 0).SolanaAddressValidator.isValid()rejects anything that does not decode to exactly 32 bytes — a structural guard that runs before the crypto check.
Monotonic nonce guarantees & anti-replay mechanics
Strict monotonicity (in-memory).
ChannelVoucherVerifiermaintains aConcurrentHashMap<String, Long>of the highest nonce seen per channel. Any voucher whosenonce <= lastSeenNonce[channel]is rejected before any cryptographic work, returning403 Forbidden.Constraint-level replay defense (database). The audit ledger enforces a
UNIQUE (channel_id, nonce)index (uk_payment_audit_ledger_channel_nonce), so a replayed nonce can never be persisted even if the in-memory watermark is bypassed (e.g. after a restart with an empty map).Monotonic cumulative amount. Because
cumulativeAmountAtomiconly grows across a channel's voucher sequence, the gateway never needs per-call payment state — it trusts the latest cumulative value as the running spend ceiling.Fail-closed ceiling. If the cumulative amount exceeds the verified escrow deposit ceiling (
EscrowBalanceProvider), the voucher is rejected. A missing, depleted, or unreadable escrow account yields a ceiling of0, never a positive balance.
Voucher JSON payload
{
"channelId": "chan_demo_solana_001",
"payerPubkey": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"cumulativeAmountAtomic": 5000,
"nonce": 10,
"signature": "<Base58 64-byte Ed25519 signature>"
}5. HTTP Wire Headers & JSON Schemas
All x402 headers are Base64-encoded JSON. The gateway accepts and emits both
a canonical and an X--prefixed alias for forward compatibility.
Direction | Canonical header | Alias | Meaning |
Server → Client |
|
| 402 challenge ( |
Client → Server |
|
| signed voucher ( |
Server → Client |
|
| settlement receipt ( |
Challenge (PAYMENT-REQUIRED)
{
"x402Version": 2,
"scheme": "channel",
"network": "solana:devnet",
"escrowAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"asset": "USDC",
"priceAtomicUnits": 5000,
"unit": "per-call",
"message": "Payment required via Solana payment channel or gasless voucher"
}Receipt (PAYMENT-RESPONSE)
{
"channelId": "chan_demo_solana_001",
"settledAmountAtomic": 5000,
"nonce": 10,
"timestamp": 1750000000000,
"status": "VERIFIED"
}Example curl flows
# 0) Machine-readable x402 discovery document (unauthenticated)
curl -i http://localhost:8080/.well-known/x402.json
# → HTTP/1.1 200 { "x402Version": 2, "name": "...", "services": [ ... ] }
# 1) Unauthenticated request → 402 challenge
curl -i -X POST http://localhost:8080/api/v1/compliance/screen-address \
-H 'Content-Type: application/json' \
-d '{"address":"4Nd1mBQtrMJVYVfKf2PJy9NZGibCcTRxpETqdrBHu19Y"}'
# → HTTP/1.1 402, PAYMENT-REQUIRED: <Base64 challenge JSON>
# 2) Replay with a signed voucher header → 200 + receipt
curl -i -X POST http://localhost:8080/api/v1/compliance/screen-address \
-H 'Content-Type: application/json' \
-H "PAYMENT-SIGNATURE: <Base64 voucher JSON>" \
-d '{"address":"4Nd1mBQtrMJVYVfKf2PJy9NZGibCcTRxpETqdrBHu19Y"}'
# → HTTP/1.1 200, PAYMENT-RESPONSE: <Base64 receipt JSON>
# 3) Administrative on-chain settlement sweep
curl -i -X POST http://localhost:8080/api/v1/settlement/channels/chan_smoke_test_001/sweep
# → HTTP/1.1 200 { "channelId": "...", "settledAmountAtomic": 5000, "txSignature": "...", ... }6. Database Schema & State Transitions
The schema is owned exclusively by versioned Flyway migrations
(src/main/resources/db/migration). Production runs with
spring.jpa.hibernate.ddl-auto: validate, so Hibernate never emits DDL — it only
verifies that the JPA entity maps onto the migrated schema.
payment_audit_ledger (V1 → V2)
Column | Type | Constraints | Notes |
|
|
| monotonically increasing append order |
|
|
| x402 channel identifier |
|
|
| Base58 32-byte payer public key |
|
|
| running spend ceiling in atomic units |
|
|
| anti-replay monotonic counter |
|
|
| Base58 Ed25519 voucher signature |
|
|
|
|
|
|
| on-chain sweep transaction signature |
|
|
| immutable append timestamp |
Indexes
Index | Columns | Purpose |
|
| primary key |
|
| constraint-level replay defense |
|
| payer lookups |
|
| channel audit reads |
V1 —
V1__init_payment_audit_ledger.sqlcreates the table, the unique anti-replay index, and the two lookup indexes.V2 —
V2__add_settlement_tx_signature.sqladds the nullabletx_signaturecolumn so previously-appendedVERIFIEDrows remain valid until swept.
Append-only contract
The table is append-only by contract: the application never issues UPDATE
or DELETE against it, the PaymentAuditRepository declares no mutation methods
beyond save (insert), and the JPA entity exposes no mutators other than the
single legal state transition below.
Channel state lifecycle
voucher verified & appended
┌──────────────────────────────────▶ VERIFIED
│ │
│ │ ChannelSettlementService.settleChannel()
│ │ → record.markSettled(txSignature)
│ ▼
└──────────────────────────────────── SETTLEDPaymentAuditRecord.markSettled(txSignature) enforces the only legal transition:
VERIFIED → SETTLED— allowed only once a non-blanktxSignatureis supplied.Any other transition (e.g.
SETTLED → SETTLED, ornull → SETTLED) throwsIllegalStateException.
The ChannelSettlementService sweeps the highest-nonce VERIFIED record for
a channel (findTopByChannelIdOrderByNonceDesc) — because cumulative amounts are
monotonic, the latest record already represents the full outstanding balance, and
settling it atomically closes the channel.
7. Quickstart & Verification
Prerequisites
Docker + Docker Compose (for the full stack)
JDK 25 (host toolchain; the project compiles to Java 21 bytecode)
Maven Wrapper (
./mvnw) — no system Maven requiredFor the smoke test only:
curl,node(≥ 12), and optionallyjq
1. Launch the full stack
docker compose up -d --buildThis builds the multi-stage Dockerfile (Temurin 25 builder → Temurin 25 JRE
runtime, non-root appuser) and starts:
Service | Image | Port | Role |
|
|
| append-only audit ledger |
| built from |
| Spring Boot gateway |
The application waits for the database healthcheck, then applies the Flyway
migrations (V1, V2) on startup.
2. Automated end-to-end smoke test
./smoke-test.shThe script exercises the complete protocol in five stages and exits non-zero on any failure:
Stage | Action | Assertion |
1 — Challenge |
|
|
2 — Voucher | Replay with a deterministically signed |
|
3 — Ledger (VERIFIED) | Query | ≥ 1 |
4 — Sweep |
|
|
5 — Ledger (SETTLED) | Re-query the ledger | ≥ 1 |
Fresh stack only. The voucher uses the fixed nonce
1and the ledger is append-only, so a second run against the same app process fails closed (403/ already-settled). Reset cleanly withdocker compose down -v && docker compose up -d --build.
3. Local development & testing
./mvnw clean testRuns the full 47-test JUnit 5 suite against an in-memory H2 database in
PostgreSQL mode (src/test/resources/application-test.yml) with Flyway applying
the same V1/V2 migrations. RPC mock mode is enabled so the suite is
deterministic and never dials an external Solana node.
./mvnw spring-boot:run # run locally (expects localhost PostgreSQL)8. Configuration Parameters
All values live in src/main/resources/application.yml. Spring Boot's relaxed
binding maps uppercase/underscore environment variables onto these keys; explicit
${ENV:default} placeholders are listed where they exist.
Property | Default | Environment override | Purpose |
|
|
| HTTP listen port |
|
|
| JDBC URL |
|
|
| DB user |
|
|
| DB password |
|
| — | JDBC driver |
|
|
| HikariCP pool ceiling |
|
| — | reject schema drift (Flyway owns DDL) |
|
| — | disable OSIV anti-pattern |
|
| — | enable migrations |
|
| — | migration search path |
|
| — | baseline non-empty schemas |
|
| — | baseline version |
|
| — | challenge |
|
| — | settlement escrow address |
|
| — | challenge |
|
| — | per-call price (atomic units) |
|
| — | challenge |
|
|
| JSON-RPC 2.0 endpoint |
|
|
| offline deterministic RPC (tests/local) |
|
| — | escrow balance cache TTL |
|
|
| settlement sweep destination |
|
|
| deterministic sweep signer (mock only) |
Security.
solana.gateway.mock-private-keyis a deterministic mock seed used only for offline/local settlement. In production, replace it with a secret-manager-backed key (e.g. KMS/HSM or an environment-injected secret) and never commit a funded key.
9. Production Extension & Customization Guide
This gateway is a template middleware, not a fixed product. The sections below show how to adapt it to any metered API domain — new endpoints, dynamic pricing, token settlement, and custom escrow programs.
(a) Securing New Endpoints
The X402PaymentFilter protects everything under /api/v1/ except the
administrative settlement paths. To place a new metered resource behind x402,
simply map a controller under /api/v1/:
@RestController
@RequestMapping("/api/v1/ai")
public class InferenceController {
@PostMapping("/infer")
public Map<String, Object> infer(@RequestBody InferenceRequest request) {
return inferenceEngine.run(request);
}
}
@RestController
@RequestMapping("/api/v1/rwa")
public class OracleController {
@GetMapping("/oracle/{asset}")
public Map<String, Object> valuation(@PathVariable String asset) {
return valuationFeed.price(asset);
}
}Both /api/v1/ai/infer and /api/v1/rwa/oracle are now automatically gated: a
request without a PAYMENT-SIGNATURE header receives 402, and a valid voucher
is required to reach the controller.
To tune which paths are protected or excluded, override
X402PaymentFilter#shouldNotFilter(HttpServletRequest):
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
if (path.startsWith("/api/v1/settlement/")) return true; // admin, unpaid
if (path.startsWith("/api/v1/public/")) return true; // free endpoints
return !path.startsWith("/api/v1/");
}(b) Custom Pricing & Metering
By default the filter charges a single static price
(x402.price-atomic-units: 5000) for every call. For request-shape or user-tier
pricing, replace the @Value-injected unitPriceAtomic with a pricing service:
@Service
public class RequestPricingService {
public long priceFor(HttpServletRequest request) {
return switch (request.getRequestURI()) {
case "/api/v1/ai/infer" -> 5000; // 0.005 USDC per inference
case "/api/v1/rwa/oracle" -> 10000; // 0.010 USDC per oracle read
default -> 5000; // fallback per-call price
};
}
}Then inject RequestPricingService into X402PaymentFilter and compute the
per-request price at the top of doFilterInternal, passing it into both the
402 challenge and the verifier call:
long price = pricingService.priceFor(request);
boolean authorized = voucherVerifier.verifyVoucher(voucher, price);For tiered billing (free tier, standard, enterprise), look up the payer's tier
from voucher.payerPubkey() (or an internal customer registry) and multiply the
base price accordingly. The voucher itself remains monotonic and cumulative — the
client simply signs cumulativeAmountAtomic += price on each call.
(c) Token Adaptation (SOL → SPL / Token-2022)
SolanaWireTransactionBuilder ships with two instruction factories. Native SOL
settlement uses the System program (0x02 discriminator):
byte[] source = payer.getPublicKeyBytes();
byte[] treasury = Base58.decode(treasuryPubkey);
SolanaInstruction transfer = SolanaWireTransactionBuilder.systemTransfer(source, treasury, amount);To settle in SPL Tokens (e.g. USDC) instead of native SOL, switch the
instruction in ChannelSettlementService.settleChannel() to the legacy SPL Token
transfer (0x03 discriminator, program id
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA):
// USDC is a 6-decimal SPL token, so scale the raw amount by 10^6.
long amountRaw = amountAtomic * 1_000_000L;
SolanaInstruction transfer = SolanaWireTransactionBuilder.tokenTransfer(
sourceTokenAccount, // gateway's associated token account
treasuryTokenAccount, // treasury's associated token account
authority, // token authority (signer)
amountRaw);Notes:
Associated Token Accounts (ATAs) must be resolved (or derived via
getAssociatedTokenAddress) before settling; the builder itself only serializes the instruction.Token-2022 uses the program id
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEband, for mint/transfer-fee tokens, thetransferCheckedinstruction (which carries the mint + decimals). Add a new factory alongsidetokenTransferrather than reusing the legacy0x03discriminator.Update
x402.asset/x402.price-atomic-unitsand the challenge to advertise the correct asset and decimal-scaled price.
(d) Custom Escrow Verification
Escrow balance resolution is behind the EscrowBalanceProvider interface, so the
verifier never depends on a concrete address or account layout:
public interface EscrowBalanceProvider {
long getVerifiedDepositCeiling(String channelId);
}The stock SolanaEscrowVerifier resolves a single settlement escrow
(x402.escrow-pubkey) and caches the lamport balance for a short TTL. To wire a
custom smart contract, PDA program, or per-channel escrow, either:
Override address resolution. Replace
SolanaEscrowVerifier#resolveEscrowAddresswith PDA derivation (e.g.findProgramAddress(seeds, programId)) so each channel resolves to its own escrow account; orProvide your own bean. Implement
EscrowBalanceProvider, parse the account'sdatafield for a custom balance layout (not justlamports), and expose it as the primary@Component. The filter/verifier chain is unchanged.
The critical contract — fail closed — must be preserved: a missing, depleted,
or unreadable escrow must yield a ceiling of 0, never a positive balance.
@Component
public class CustomProgramEscrowVerifier implements EscrowBalanceProvider {
private final SolanaRpcClient rpc;
public CustomProgramEscrowVerifier(SolanaRpcClient rpc) { this.rpc = rpc; }
@Override
public long getVerifiedDepositCeiling(String channelId) {
String escrow = deriveEscrowPda(channelId); // program-owned PDA
return rpc.getAccountInfo(escrow)
.map(AccountInfoResponse::value)
.map(AccountInfoValue::data)
.map(data -> decodeDepositCeiling(data)) // custom layout
.orElse(0L); // fail closed
}
}10. Project Layout
.
├── pom.xml # Java 21 target, Spring Boot 3.4.3, BouncyCastle 1.78.1
├── Dockerfile # multi-stage: Temurin 25 builder → 25-jre-alpine runtime
├── docker-compose.yml # PostgreSQL 16 + gateway app
├── smoke-test.sh # 5-stage end-to-end x402 verification
├── mvnw / mvnw.cmd / .mvn/wrapper/ # Maven wrapper (no system Maven needed)
└── src
├── main
│ ├── java/com/msb/solana/gateway
│ │ ├── SolanaPaymentGatewayApplication.java
│ │ ├── compliance/ AddressRiskEvaluator, ThreatIntelligenceRegistry, ScreeningVerdict, ScreeningResult, ScreeningFlag
│ │ ├── config/ SolanaRpcConfig.java (JDK HttpClient bean)
│ │ ├── controller/ ComplianceScreeningController, SettlementController, X402DiscoveryController
│ │ ├── entity/ PaymentAuditRecord, PaymentAuditStatus
│ │ ├── filter/ X402PaymentFilter.java
│ │ ├── model/ PaymentRequiredChallenge, PaymentVoucher,
│ │ │ PaymentSettlementReceipt, SettlementResult, X402DiscoveryResponse
│ │ ├── repository/ PaymentAuditRepository.java
│ │ ├── rpc/ SolanaRpcClient.java (+ model/* JSON-RPC DTOs)
│ │ ├── serialization/ Base58, CompactU16, AccountMeta, SolanaInstruction,
│ │ │ SolanaKeypair, SolanaKeypairService,
│ │ │ SolanaWireTransactionBuilder, Ed25519SignatureVerifier,
│ │ │ SolanaAddressValidator
│ │ └── service/ ChannelVoucherVerifier, ChannelSettlementService,
│ │ PaymentAuditService, SolanaEscrowVerifier,
│ │ EscrowBalanceProvider
│ └── resources/
│ ├── application.yml
│ └── db/migration/ V1__init_payment_audit_ledger.sql,
│ V2__add_settlement_tx_signature.sql
└── test
├── java/... 11 test classes (47 tests)
└── resources/application-test.yml # H2 (PostgreSQL mode) + mock RPC11. Testing
The suite runs 47 tests across 11 classes with JUnit 5, Mockito, and MockMvc:
Test class | Focus |
| OFAC / drainer blocklist, |
|
|
| MockMvc: 402 challenge, valid voucher → 200, replay → 403, tampered signature → 403 |
| sweep endpoint → |
| sweep transaction build, blockhash, sign, broadcast |
| nonce watermark, ceiling, signature checks |
| mock-mode ceiling, cache TTL, fail-closed empty balance |
| append-only record creation |
| unique |
| JSON-RPC request/response, mock signatures, fail-closed |
| Base58, compact-u16, Ed25519 round-trips |
| account sorting, header, discriminator, wire bytes |
The MockMvc integration tests verify the five mandated protocol outcomes:
Unauthenticated request →
402with a correct Base64PAYMENT-REQUIREDheader.Valid voucher →
200+ downstream payload +PAYMENT-RESPONSEheader.Tampered signature →
403 Forbidden(fail-closed).Replayed/stale nonce →
403 Forbiddenwith audit logging.Insufficient channel balance → fail-closed rejection.
The project targets >95% coverage for the cryptographic (serialization)
and filter components.
12. Security & Compliance Posture
Fail-closed by default. Missing header →
402; tampered signature, replayed nonce, or exceeded ceiling →403. Malformed header →400. No code path grants access on an unreadable RPC node or escrow.Append-only audit trail. Every verification is persisted exactly once; the schema and repository forbid mutation, and the
UNIQUE(channel_id, nonce)index makes replay impossible at the database layer.No private keys persisted or logged.
SolanaKeypairholds the signing key in-memory only; the public-key accessor returns a defensive copy. Logs record channel IDs, nonces, amounts, and tx signatures — never key material.Non-root container. The runtime image runs as an unprivileged
appuserwith-XX:+ExitOnOutOfMemoryErrorand container-aware heap sizing.Schema drift rejection.
hibernate.ddl-auto: validateensures the JPA model can never silently diverge from the versioned Flyway schema.Deterministic mock mode.
solana.rpc.mock-mode: true(tests and the local compose stack) produces stable, replayable signatures without funded accounts; production sets it tofalseto exercise live devnet/mainnet settlement.
Available Tools
4 toolsgenerate_compliance_reportA
Generate an immutable audit summary report for a Solana wallet address or transaction signature, checked against sanctions and threat intelligence logs.
Unlike screen_solana_address (a live, paid network screening), this tool is deterministic and side-effect-free: it performs no network call, settles no payment, and returns a stable audit record with a reproducible reportId. It references the current session's in-memory screening ledger: if the identifier has not been live-screened this session, it returns complianceStatus 'UNSCREENED' with a null riskScore and an empty checkedLists array.
Returns a JSON audit payload: { identifier, complianceStatus ('PASSED' | 'FLAGGED' | 'UNSCREENED'), riskScore (number or null), flags (string[]), sanctionsMatch (boolean), checkedLists ([] when unscreened, otherwise ['OFAC','EU_SANCTIONS','CHAIN_REPUTATION']), lastEvaluated, evaluationTimestamp, timestamp (ISO-8601), reportId ('rpt_' + 32 hex chars, SHA-256 derived) }. A blank or malformed identifier is rejected with a validation error.
| Name | Required | Description | Default |
|---|---|---|---|
| identifier | Yes | Solana identifier to report on: either a Base58 wallet address (32-byte public key) or a Base58 transaction signature (64 bytes). Example address: '4Nd1mBQtrMJVYVfKf2PJy9NZGibCcTRxpETqdrBHu19Y'. Example signature: '3zKcze3Q9DDRui2YCMeTPsBs3mxymxMN3oCvNDVKkrbgtZsR6CVfrLaMPd1kVHjoiAYEqTLNYxAzeABmdBMWSWhm'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure, and it does so thoroughly. It states there is no network call, no payment settled, a reproducible reportId, dependence on the session's in-memory screening ledger, exact behavior for unscreened identifiers, and validation errors for blank or malformed input. This is exceptionally transparent for a tool with no annotation support.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence earns its place: main purpose, differentiation from the sibling, behavioral guarantees, session dependency, unscreened behavior, and a concrete return payload shape. It is front-loaded with the core purpose and then provides structured detail. There is no filler or tautology.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with no output schema and no annotations, the description is remarkably complete. It fully defines the returned JSON fields, possible complianceStatus values, riskScore behavior, flags, checkedLists contents, and reportId format. It also covers error conditions and the prerequisite screening state, so an agent has everything needed to invoke it correctly and interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3 because the schema already documents the identifier parameter with examples. The description adds meaningful context beyond the schema: it explains that the identifier is checked against the current session's screening ledger and that an unscreened identifier yields a specific complianceStatus. This extra semantic dependency is valuable and not present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Generate an immutable audit summary report for a Solana wallet address or transaction signature, checked against sanctions and threat intelligence logs.' It clearly distinguishes itself from screen_solana_address by contrasting deterministic, side-effect-free reporting with live, paid network screening. An agent can immediately tell what this tool does and how it differs from its closest sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Unlike screen_solana_address (a live, paid network screening), this tool is deterministic and side-effect-free.' It also explains the precondition for meaningful results: the identifier must have been live-screened this session, otherwise it returns 'UNSCREENED'. This gives clear when-to-use and when-not-to-use guidance beyond any structured fields.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_channel_statusA
Retrieve the current operational status, limits, and settlement state of an x402 payment channel, identified by its channel_id.
Use this as the first step of the channel lifecycle before screening an address or verifying a payment: confirm the channel is ACTIVE and has capacity so a downstream x402 micro-payment can settle.
Returns a JSON object: { channelId, status ('ACTIVE'), currency ('USDC'), network ('solana-devnet'), capacity ('10000.00'), lastSettlementBlock }. A blank or malformed channel_id is rejected with a validation error.
| Name | Required | Description | Default |
|---|---|---|---|
| channel_id | Yes | x402 payment channel identifier (1-128 chars; alphanumeric plus '.', '-', '_'). Example: 'chan_smoke_test_001'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses the exact return shape, including fields and example values, and mentions validation error behavior for blank or malformed channel_id. It does not explicitly state read-only semantics or auth/rate-limit behavior, but 'Retrieve' and the status-check context make the read-only nature reasonably clear.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: purpose, usage context, and return shape are each covered in a single sentence with no filler. Every sentence contributes actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter read tool with no output schema, the description provides the necessary return shape, usage context, and validation behavior. It is nearly complete, though it does not describe behavior for unknown channel IDs or non-ACTIVE statuses, and the literal-looking status ('ACTIVE') is slightly ambiguous.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema already documents channel_id with format constraints and an example. The description adds little beyond saying the channel is 'identified by its channel_id,' so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Retrieve') and names the exact resource ('x402 payment channel') and the aspects returned (operational status, limits, settlement state). It also distinguishes this tool from siblings by positioning it as the first lifecycle step before screening or payment verification.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to use the tool: 'Use this as the first step of the channel lifecycle before screening an address or verifying a payment.' However, it does not explicitly name sibling tools or state when not to use it, so it falls just short of full alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
screen_solana_addressA
Screen a Solana wallet address in real time against sanctions and threat intelligence databases. This is the live, network-bound compliance check: it automatically negotiates and settles an x402 micro-payment, then returns the gateway's screening verdict, risk score, and any matching flags.
Use this when you need an authoritative, up-to-date screening decision for an address before transacting with it. For a deterministic, offline audit record instead, use generate_compliance_report; to validate a payment itself, use verify_x402_payment.
Returns a JSON object: { address, verdict ('CLEAR_TO_TRANSACT' | 'BLOCKED'), riskScore (0-100), flags[], sanctionsMatch (boolean), lastEvaluated, evaluationTimestamp }. On gateway or payment failure it returns isError with the underlying message.
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | Base58-encoded Solana wallet address (32-byte public key, 32-44 characters). Example: '4Nd1mBQtrMJVYVfKf2PJy9NZGibCcTRxpETqdrBHu19Y'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden. It discloses that the tool is live/network-bound, automatically negotiates and settles an x402 micro-payment (a side effect), returns a specific JSON structure, and handles failures by returning isError. This is comprehensive and non-contradictory.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: purpose first, then usage guidance, then return format, then error handling. Every sentence adds value; there is no filler or repetition. It's appropriately sized for the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with a single parameter and no output schema, the description fully specifies the return object with exact fields and possible values, and explains failure behavior. It also clarifies the tool's place among siblings, making it complete for an agent to call correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already describes the address parameter with format, length, and example. The description does not add extra meaning beyond the schema, so baseline 3 is appropriate. It doesn't need to compensate for gaps.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('screen') with a clear resource ('Solana wallet address') and distinguishes it from siblings by naming the offline alternative (generate_compliance_report) and payment validation (verify_x402_payment). The agent can immediately know what this tool does and how it differs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says 'Use this when you need an authoritative, up-to-date screening decision...' and provides alternatives with conditions ('For a deterministic, offline audit record instead, use generate_compliance_report; to validate a payment itself, use verify_x402_payment'). This gives clear when-to-use and when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
verify_x402_paymentA
Verify cryptographic validity and on-chain settlement state of an x402 payment proof or transaction signature.
Use this to confirm that a payment proof is well-formed and whether it has been settled against a channel. Prefer screen_solana_address for sanctions/threat screening of an address; use this tool for payment proof validation.
Returns a JSON object: { status ('valid' | 'settled' | 'invalid'), payerPublicKey (Base58), amountAtomicUnits (number), amount ('0.005000'), currency ('USDC'), timestamp (ISO-8601) }. status semantics: 'valid' = proof well-formed but not yet settled (no channelId); 'settled' = proof valid and settled on the supplied channelId; 'invalid' = proof is not a valid Base58 Solana address or transaction signature.
| Name | Required | Description | Default |
|---|---|---|---|
| channelId | No | Optional x402 channel identifier (alphanumeric plus '.', '-', '_'). When supplied and valid, the proof is reported as 'settled'; omit it to report 'valid'. Example: 'chan_smoke_test_001'. | |
| paymentProof | Yes | x402 payment proof to verify: a Base58 Solana transaction signature (64 bytes) or wallet address (32 bytes). Example: '3zKcze3Q9DDRui2YCMeTPsBs3mxymxMN3oCvNDVKkrbgtZsR6CVfrLaMPd1kVHjoiAYEqTLNYxAzeABmdBMWSWhm'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It provides a detailed return contract, including status semantics ('valid', 'settled', 'invalid') and explains how omitting or supplying channelId changes the outcome. It does not explicitly state that the tool is read-only or disclose side effects, but the verify-oriented description and deterministic status rules provide substantial transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized into purpose, usage, and return/status semantics, with no filler. It is longer than minimal, but the length is justified because there is no output schema and the return contract must be spelled out. A small amount of redundancy exists between the opening summary and the status definitions, but it does not hurt clarity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no annotations, no output schema, and only two parameters, the description is largely complete: it explains what the tool does, when to use it, the returned JSON shape, and the meaning of each status. Minor gaps remain, such as explicit guidance on when to use get_channel_status instead and any error/rate-limit behavior, but the essential information for correct invocation is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description goes beyond the schema by explaining how channelId affects the status result: supplying it yields 'settled', omitting it yields 'valid'. It also clarifies what makes a proof 'invalid' in terms of Base58 format, adding valuable parameter-level meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Verify cryptographic validity and on-chain settlement state of an x402 payment proof or transaction signature.' It also explicitly distinguishes itself from the sibling screen_solana_address by noting that the screen tool is for sanctions/threat screening while this tool is for payment proof validation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly states when to use the tool: to confirm a payment proof is well-formed and whether it has settled against a channel. It also gives an explicit alternative by saying to prefer screen_solana_address for sanctions/threat screening. However, it does not address when to prefer get_channel_status or generate_compliance_report, leaving some sibling differentiation incomplete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v1.0.13- Changed
generate_compliance_report1 field changed- added
Input schema / properties / identifier / descriptionAdded value: +"Solana identifier to report on: either a Base58 wallet address (32-byte public key) or a Base58 transaction signature (64 bytes). Example address: '4Nd1mBQtrMJVYVfKf2PJy9NZGibCcTRxpETqdrBHu19Y'. Example signature: '3zKcze3Q9DDRui2YCMeTPsBs3mxymxMN3oCvNDVKkrbgtZsR6CVfrLaMPd1kVHjoiAYEqTLNYxAzeABmdBMWSWhm'."
- Changed
get_channel_status1 field changed- added
Input schema / properties / channel_id / descriptionAdded value: +"x402 payment channel identifier (1-128 chars; alphanumeric plus '.', '-', '_'). Example: 'chan_smoke_test_001'."
- Changed
screen_solana_address1 field changed- added
Input schema / properties / address / descriptionAdded value: +"Base58-encoded Solana wallet address (32-byte public key, 32-44 characters). Example: '4Nd1mBQtrMJVYVfKf2PJy9NZGibCcTRxpETqdrBHu19Y'."
- Changed
verify_x402_payment2 fields changed- added
Input schema / properties / channelId / descriptionAdded value: +"Optional x402 channel identifier (alphanumeric plus '.', '-', '_'). When supplied and valid, the proof is reported as 'settled'; omit it to report 'valid'. Example: 'chan_smoke_test_001'." - added
Input schema / properties / paymentProof / descriptionAdded value: +"x402 payment proof to verify: a Base58 Solana transaction signature (64 bytes) or wallet address (32 bytes). Example: '3zKcze3Q9DDRui2YCMeTPsBs3mxymxMN3oCvNDVKkrbgtZsR6CVfrLaMPd1kVHjoiAYEqTLNYxAzeABmdBMWSWhm'."
1 tool update
v1.0.10- Added
verify_x402_payment
2 tool updates
v1.0.5- Added
generate_compliance_report - Added
get_channel_status
TDQS
Scored across 4 tools
Each tool targets a distinct action: payment proof validation, live address screening, channel status retrieval, and offline audit report generation. The descriptions explicitly cross-reference each other to prevent confusion, especially between screen_solana_address and generate_compliance_report.
All tool names follow a consistent verb_noun snake_case pattern: verify_, screen_, get_, generate_. The verbs clearly map to each tool's purpose and the resource names are descriptive.
Four tools is well-scoped for a compliance-focused x402 server. Each tool covers a distinct part of the workflow without redundancy or bloat.
The set covers the core compliance workflow: checking channel capacity, screening an address, verifying a payment, and generating an audit report. The only minor gap is lack of explicit channel creation or management tools, but that appears outside the server's compliance scope.
Maintenance
Related MCP Connectors
High-frequency OFAC wallet screening and recurring signed x402 checks for autonomous agents.
Entity verification, sanctions screening, and trust scoring for AI agents via x402 micropayments.
x402 paid API tools for AI agents on Solana: crypto safety, market data, KYB/AML verification.
Compliance MCP for AI agents: sanctions & KYT screening on 50+ chains, stablecoin-freeze, oracle.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables AI agents to make micropayments using USDC on Solana via the x402 protocol, supporting payment requests, on-chain verification, and revenue tracking.MIT
- FlicenseAqualityDmaintenanceOn-chain investigation and analysis tools for Solana blockchain, enabling detection of wash trading, funding source tracing, holder concentration analysis, and MEV/bundle activity identification.43-
- FlicenseNot gradedqualityBmaintenanceKeyless, pay-per-call compliance & regulated-data tools for AI agents: OFAC wallet + sanctions/PEP + KYB screening, SEC filings, FRED economics, FDA recalls, federal awards, and continuous monitoring (watch a wallet/company/brand for status changes). USDC via x402 on Base/Solana, no API key, no signup.-
- AlicenseAqualityBmaintenanceEnables AI agents to discover, inspect, and pay for paid HTTP and MCP services using USDC on Solana with a self-custodial wallet.439 npm7Apache 2.0