>_ WDK / HOW-TO GUIDE
MIGRATION
GUIDE.
Move from a USDC-only EIP-3009 integration to WDK + USDT0 routing with zero downtime. New fields are additive — existing clients continue to work unchanged throughout the migration.
Backwards compatibility guarantee
- ✓All existing /api/v1/facilitator/settle requests with USDC continue to work — no changes required.
- ✓The new quoteId, routeId, and client fields are optional in the settle endpoint for existing callers.
- ✓You can migrate incrementally — add USDT0 support while keeping USDC as a fallback.
What Changes
The migration adds two things to your settlement flow: a quote step before settlement, and WDK signer support for USDT0 tokens. Here is the before/after comparison.
POST /api/v1/facilitator/settle
{
"paymentPayload": {
"x402Version": 2,
"scheme": "exact",
"network": "eip155:8453",
"payload": {
"signature": "0x...",
"authorization": {
"from": "0x...",
"to": "0xTreasury",
"value": "1000000",
"validAfter": "...",
"validBefore": "...",
"nonce": "0x..."
}
}
},
"paymentRequirements": { ... }
}// Step 1: Quote (NEW)
POST /api/v1/liquidity/quote
{ "sourceAssets": ["USDT0", "USDC"], ... }
→ { "quoteId": "q_123", "routes": [...] }
// Step 2: Settle (EXTENDED)
POST /api/v1/router/settle
{
"quoteId": "q_123", // NEW
"routeId": "r_fast", // NEW
"client": { // NEW
"type": "wdk",
"version": "1.0.0"
},
"authType": "eip3009",
"amount": "1.00",
"asset": "USDT0", // NEW (was USDC)
"payment": {
"scheme": "exact",
"authorization": { ... },
"signature": "0x..."
}
}Baseline Audit
Before changing any code, inventory what you have. This tells you the scope of work and gives you a baseline to measure against after migration.
Checklist
- Find all callers using /api/v1/facilitator/settle — these are your migration targets.
- Check whether any caller hardcodes "asset": "USDC" or USDC decimal math (6 decimals).
- Note current success rate, average latency, and failure modes for the existing settle flow.
- Identify which chains your users have wallets on (Base, Arbitrum, Ethereum).
- Confirm you have a WDK-compatible wallet for USDT0 signing, or a plan to get one.
Add the Quote Step
Add the quote call before your settle call. This is additive — your existing settle call does not change yet. At this stage, just pass USDC in sourceAssets and use the returned quoteId and routeId in your settle request.
// BEFORE (no quote step):
async function settle(amount: string, authorization: Authorization, signature: string) {
return fetch('/api/v1/facilitator/settle', {
method: 'POST',
body: JSON.stringify({ paymentPayload: { ... } }),
});
}
// AFTER PHASE 1 (add quote, keep USDC):
async function settle(amount: string, authorization: Authorization, signature: string) {
// New: get a quote first
const quote = await fetch('/api/v1/liquidity/quote', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` },
body: JSON.stringify({
invoiceId: crypto.randomUUID(),
walletAddress: authorization.from,
sourceAssets: ['USDC'], // Still USDC only for now
}),
}).then(r => r.json());
const route = quote.routes[0];
// Extended: include quoteId + routeId, keep existing authorization
return fetch('/api/v1/router/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` },
body: JSON.stringify({
quoteId: quote.quoteId, // NEW
routeId: route.routeId, // NEW
client: { type: 'direct', version: '1.0.0' }, // NEW
authType: 'eip3009',
amount,
asset: 'USDC', // Still USDC
payment: { scheme: 'exact', authorization, signature },
}),
});
}Deploy this, monitor your success rate, and confirm nothing regressed. The route selection for USDC should return results immediately — no new failures expected.
Add WDK Signer + USDT0
Add the WDK signer adapter and expand sourceAssets to include USDT0. The quote response selects the best available token — if the user has USDT0 on Arbitrum, it'll be preferred over USDC on Base for most routes.
import { WdkSigner } from '@tether/wdk';
async function settle(amount: string, walletAddress: string, wdkSigner: WdkSigner) {
// Step 1: Quote with USDT0 preference
const quote = await fetch('/api/v1/liquidity/quote', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` },
body: JSON.stringify({
invoiceId: crypto.randomUUID(),
walletAddress,
sourceAssets: ['USDT0', 'USDC'], // USDT0 preferred, USDC fallback
}),
}).then(r => r.json());
const route = quote.routes[0];
const isWdk = route.sourceAsset === 'USDT0';
const now = Math.floor(Date.now() / 1000);
const nonce = '0x' + crypto.getRandomValues(new Uint8Array(32))
.reduce((hex, b) => hex + b.toString(16).padStart(2, '0'), '');
const authorization = {
from: walletAddress,
to: P402_TREASURY,
value: String(Math.round(parseFloat(amount) * 1_000_000)), // 6 decimals
validAfter: String(now - 60),
validBefore: String(now + 3600),
nonce,
};
// Sign: WDK for USDT0, standard EIP-712 for USDC
const signature = isWdk
? await wdkSigner.signTransferAuthorization({
token: USDT0_CONTRACT[route.sourceChain],
chainId: parseInt(route.sourceChain.split(':')[1]),
authorization,
})
: await standardEip712Sign(authorization); // Your existing signing logic
// Step 2: Settle
return fetch('/api/v1/router/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` },
body: JSON.stringify({
quoteId: quote.quoteId,
routeId: route.routeId,
client: { type: isWdk ? 'wdk' : 'direct', version: '1.0.0' },
authType: 'eip3009',
amount,
asset: route.sourceAsset,
payment: { scheme: 'exact', authorization, signature },
}),
}).then(r => r.json());
}Update Your UI
If you have a payment UI, update it to show the selected route and token. The quote response contains everything you need: selected asset, chain, fee, and estimated latency.
UI changes to make
- 1.Replace a token-only selector (e.g. "Pay with USDC") with a route card showing token + chain + estimated fee.
- 2.Show the selected route from the quote before asking the user to sign.
- 3.Display the receipt txHash and receipt_id after settlement for user reference.
- 4.Handle P402_QUOTE_EXPIRED gracefully: re-fetch the quote and present the new options.
Testing Your Migration
After each phase, verify these signals before moving to the next phase:
| Check | How |
|---|---|
| Settlement success rate unchanged | Compare 7-day success rate before and after deploy. |
| No double-spend incidents | Monitor for P402_REPLAY_DETECTED errors — any replay means a nonce bug. |
| Quote responses include routes | Assert quote.routes.length > 0 before attempting settlement. |
| Signature verification passes | P402_AUTH_INVALID errors indicate a signing mismatch — check chainId and contract address. |
| Receipt_id returned on success | Log receipt_id for each settlement; absence means the response schema changed. |