TL;DR
- Dexter is the fourth x402 facilitator on the War-Tracker gate, joining xpay (Base), Coinbase Developer Platform (Base + Polygon), and PayAI (Base + Polygon + Avalanche).
- Every paid route's
accepts[]array now carries one extra offer per chain Dexter covers — currently Base mainnet only, with Polygon / Arbitrum / Optimism / Avalanche on an env knob for opt-in when we want broader coverage. - The wire-level difference: Dexter's offers carry
extra.assetTransferMethod: "permit2". The x402 Python SDK dispatches on that field, so buyers sign a Uniswap Permit2 typed-data structure instead of the EIP-3009transferWithAuthorizationthe other three facilitators use. Same EIP-712 family; different domain and message shape. - No auth, no JWT, no API key. Dexter behaves like xpay and
PayAI from the seller's perspective — a
payToaddress and a base URL is all the gate needs to advertise it. - One install gotcha for buyers: the x402 buyer SDK only
enables the extensions validator when installed with the
[extensions]extra (pip install 'x402[extensions]'). Without it, Dexter offers fail validation withPaymentError: Extensions validation requires jsonschema.
Why a fourth facilitator
Each facilitator we light up adds two things:
- Buyer choice. Buyers with a wallet on a chain we don't
already advertise can pay us natively without bridging or
swapping. Dexter today only ships Base, but their
/supportedadvertises Polygon / Arbitrum / Optimism / Avalanche / OP / Linea / Scroll / Mode / zkSync Era — a one-env-knob expansion when we choose. - Resilience. Any single facilitator going down (rate-limit,
incident, key-rotation) used to mean some buyers couldn't
pay. With four facilitators advertising overlapping chain
coverage, a buyer SDK that retries down the
accepts[]array on facilitator failure will almost always find a working route on the buyer's home chain.
The seller-side mental model stays the same: each facilitator
has a distinct payTo address that we control; the buyer's
EIP-712 signature (whether Permit2 or EIP-3009) commits to that
address; when the signed payload comes back, the
(network, payTo) pair uniquely identifies which facilitator's
/verify and /settle to hit.
Permit2 vs EIP-3009 in the offer
The shape that changes is the EVM offer's extra block. Before
Dexter, every EVM offer looked like this (illustration is the
Base-mainnet xpay offer for /api/v1/events):
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "5000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x…xpay…",
"maxTimeoutSeconds": 60,
"extra": { "name": "USD Coin", "version": "2" }
}
The Dexter offer for the same route is identical except for the
new assetTransferMethod hint:
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "5000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x…dexter…",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USD Coin",
"version": "2",
"assetTransferMethod": "permit2"
}
}
Pre-Permit2 buyer SDKs that don't read extra.assetTransferMethod
will try to sign an EIP-3009 transferWithAuthorization for a
Dexter offer and the signature won't validate at /verify —
they'll fall through to a sibling offer (xpay/CDP/PayAI) that
they CAN sign. Modern SDKs read the hint, build a Permit2
PermitSingle + SignatureTransferDetails instead, and submit
through Dexter's flow.
The seller side is Optional[str] on the Facilitator
descriptor — one field, defaults to None to keep
EIP-3009 the default for the other three. From
telegram-worker/x402_gate.py:
@dataclass(frozen=True)
class Facilitator:
id: str
label: str
url: str
pay_to: str
networks: Tuple[str, ...]
fee_micro_usdc: int
# ...
asset_transfer_method_evm: Optional[str] = None
And the Dexter registration:
dexter = Facilitator(
id="dexter",
label="Dexter",
url="https://x402.dexter.cash",
pay_to=X402_PAY_TO_EVM_DEXTER,
networks=dexter_networks, # Base by default; env-extensible
fee_micro_usdc=0, # gas-sponsored on Base today
priority=3,
requires_auth=False,
asset_transfer_method_evm="permit2",
)
That's the entire diff against the previous three facilitators — one field, no special-case dispatch in the gate's hot path.
What buyers have to install
If you're building against Dexter offers, two things matter:
- x402 Python SDK 0.5.x or later for
assetTransferMethoddispatch on the buyer side. - The
extensionsextra so the SDK can validate theextensions.bazaarblock we attach to every offer:
bash
pip install 'x402[extensions]'
If you skip the extra, the SDK rejects Dexter offers with
PaymentError: Extensions validation requires jsonschema.
This bit us during integration testing — the failure mode
looks like a Dexter-specific bug but is actually a generic
"your SDK can't validate any offer with extensions" error.
The other three facilitators' offers also carry
extensions.bazaar (it's part of how their discovery rolls
into x402scan / agentic.market catalogs), so the
[extensions] install is a good idea regardless of which
facilitator you settle through.
If you're a TypeScript / Go / Rust buyer: same deal. Check that
your SDK reads extra.assetTransferMethod and has a Permit2
codepath. The x402 reference TypeScript SDK does. If yours
doesn't, the Dexter offer will be invisible to you and you'll
settle through one of the EIP-3009 facilitators — slightly less
chain coverage on /supported but everything else works the
same.
Why Permit2
Permit2 (the Uniswap-deployed canonical permit contract on every major EVM chain) gives a facilitator two things that EIP-3009 doesn't:
- Single-signature multi-token approvals. A buyer can sign
one Permit2 message that authorises the facilitator to pull
N different tokens up to a per-token budget, valid until
validUntil. EIP-3009 is per-token, per-amount, per-nonce. For repeat-business with a single facilitator this is a real UX win — sign once, settle many times within budget. - No token-side opt-in required. EIP-3009 only works for tokens that implement the EIP-3009 interface (USDC, EURC, some others). Permit2 works for any ERC-20 because Permit2 is a separate canonical contract the user pre-approves once. That matters less for x402 today (everyone settles in USDC, which DOES implement EIP-3009) but expands the design space — a future "pay in USDT" or "pay in DAI" offer is trivially advertisable through Dexter.
For a single-shot $0.001 micropayment to read one /api/v1/vessels row, neither of those wins materially over EIP-3009 — the buyer-side UX is identical (one signature, one HTTP retry). But the plumbing is now in place for repeat-business flows that amortise the signature across many calls.
Network expansion
Dexter's /supported on 2026-05-17 advertises:
- EVM: Base, Polygon, Arbitrum, Optimism, Avalanche, Linea, Scroll, Mode, zkSync Era (9 chains)
- Non-EVM: Solana mainnet + devnet
We default to Base mainnet only on first deploy because that matched the existing buyer-side smoke-test surface (we already proved Base settlement works end-to-end across xpay / CDP / PayAI before adding Dexter). The other chains are one env knob away:
# Light up Polygon + Arbitrum + Optimism on Dexter
X402_FACILITATOR_NETWORKS_DEXTER="eip155:8453,eip155:137,eip155:42161,eip155:10"
Solana settlement on Dexter is on the same TODO as the rest of
the facilitator-Solana rollout — both CDP and PayAI also list
Solana on their /supported today, so when we work out the
Solana-native signing flow (it's a different transaction
structure entirely — not EIP-712, not Permit2), we'll light all
three Solana-capable facilitators up at once.
Economics
The economics work the same as the rest of the multi-facilitator mix (see the prior post for the full breakdown):
- Base price is the seller-retained portion and is the same
across facilitators per route — e.g. $0.001 for
/api/v1/vessels/{imo}/position. - Buyer-side amount =
base_price + facilitator.fee_micro_usdc. Dexter currently runs at $0 per-tx fee on Base (gas-sponsored like xpay), so the Dexter offer'samountmatches the xpay offer'samountfor the same route. - Per-facilitator revenue is tracked in Redis
(
x402:paid:fac:dexter,x402:revenue_micro:fac:dexter) and surfaced on the/admin/ai-trafficdashboard so we can see which facilitator is actually carrying volume.
Operational notes
- Disabling Dexter is a one-knob op: clear
X402_PAY_TO_EVM_DEXTERin env (or dropdexterfromX402_FACILITATORS_ORDER) and redeploy. The gate quietly stops advertising Dexter offers; buyers that used Dexter fall through to the next-priority facilitator offering their chain. - Adding a fifth facilitator is the same shape: one
Facilitator(...)row in_build_facilitator_registry(), oneX402_PAY_TO_EVM_*env, and (if it uses Permit2 or some other non-EIP-3009 method) oneasset_transfer_method_evmfield. No changes to the offer-routing or settlement code. - Distinct
payToper facilitator is load-bearing. The gate routes incoming signed payloads back to the correct facilitator by matching(network, payTo)against the registry. Reusing one address across facilitators would collapse the routing — DO NOT do it.
What this means for buyers
If you already settle on Base via xpay / CDP / PayAI: nothing
changes. Your existing SDK keeps picking the offer that matches
your signing capability, and you get one extra accepts[] entry
per request that your SDK can ignore if it doesn't speak
Permit2.
If your SDK speaks Permit2 (modern x402 Python with
[extensions], modern x402 TypeScript): the Dexter offer is
slightly cheaper on Base (no facilitator fee) and you'll start
hitting it preferentially when the buyer SDK picks the
lowest-amount offer for your chain.
If you're integrating fresh: install x402[extensions] from
day one. The 200 KB extra dependency (jsonschema) is the
difference between "your SDK validates every offer the gate
emits" and "your SDK silently drops every Dexter offer and a
chunk of legacy CDP / PayAI offers carrying extensions.bazaar".
Links
- Free policy explainer:
/x402 - Machine-readable policy:
/x402.json - Live offers (no payment required, just
curl):curl -A "" https://war-tracker.com/api/v1/events | jq .accepts - Per-facilitator revenue dashboard:
/admin/ai-traffic(token-gated) - Prior multi-facilitator post: Multi-facilitator, multi-chain x402
- Dexter facilitator: https://x402.dexter.cash
- Uniswap Permit2 reference: https://docs.uniswap.org/contracts/permit2/overview