Off-ramp USDC to fiat via ZKP2P protocol. Pay humans in their local fiat currency by matching with LP liquidity. Use when the agent needs to pay a human freelancer, convert USDC to fiat, or send fiat payments to non-crypto users.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Off-ramp USDC to fiat via ZKP2P protocol. Pay humans in their local fiat currency by matching with LP liquidity. Use when the agent needs to pay a human freelancer, convert USDC to fiat, or send fiat payments to non-crypto users.
ZKP2P Off-Ramp (USDC to Fiat)
Agent holds USDC on Base and needs to pay a human in fiat (USD, EUR, GBP, etc.). The agent signals an intent to sell USDC, an LP sends fiat to the recipient, the LP proves the payment, and the escrowed USDC transfers to the LP.
Overview
The off-ramp flow is the mirror of the on-ramp:
1. FIND LP → Query deposits accepting the target currency/platform
2. SIGNAL INTENT → Agent locks its own USDC in escrow, specifying the fiat recipient
3. LP SENDS FIAT → LP sends fiat to the agent's specified recipient
4. LP PROVES → LP generates proof of fiat payment
5. LP FULFILLS → LP submits proof on-chain, receives the escrowed USDC
6. CONFIRMATION → Agent monitors intent status for fulfillment
Key difference from on-ramp: In the off-ramp, the agent is the one locking USDC (acting as the "maker"), and the LP is the one sending fiat and proving payment (acting as the "taker"). The agent does NOT need to generate proofs -- the LP handles that.
Current Status
Step
Status
Notes
Find LP
AVAILABLE
Query via indexer or getQuote()
Signal Intent
AVAILABLE
signalIntent() in @zkp2p/sdk
LP Sends Fiat
LP-SIDE
Agent waits; LP handles fiat transfer
LP Proves
LP-SIDE
LP generates proof via PeerAuth extension
LP Fulfills
LP-SIDE
LP calls fulfillIntent() on-chain
Confirmation
AVAILABLE
Monitor intent status via indexer or RPC
Bottom line: The agent-side operations (Steps 1-2, 6) work today via the SDK. Steps 3-5 are handled by the LP. A higher-level "pay human" API that abstracts the full flow is planned but not yet available.
Step 1: Find LP
Query for available LP deposits that accept the target payment platform and currency:
Lower conversionRate = better for agent (less fiat per USDC)
Available balance
Check deposit's unlocked USDC balance covers the amount
Intent range
Verify amount falls within deposit's [min, max] intent range
Active intents
Fewer active intents = faster fulfillment likelihood
LP history
Check LP's fulfillment rate via indexer
Step 2: Signal Intent
The agent locks its USDC in escrow and specifies the fiat payment recipient:
// First, ensure the agent has approved USDC to the Escrow contractawait client.ensureAllowance({
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDCamount: 200_000000n, // 200 USDC
});
// Signal intent to sell 200 USDCconst intentTx = await client.signalIntent({
depositId: quote.depositId,
amount: '200000000', // 200 USDCtoAddress: agentAddress, // Receives USDC back if intent cancelledprocessorName: 'wise',
payeeDetails: recipientPayeeHash, // Hashed fiat recipient details (see below)fiatCurrencyCode: 'EUR',
conversionRate: quote.conversionRate,
});
console.log(`Intent signaled: ${intentTx}`);
What Happens On-Chain
Agent's USDC is transferred to the Escrow contract
The Orchestrator creates an intent record with:
depositId: which LP deposit to match with
amount: USDC locked
payeeDetails: hashed fiat recipient info (the person the LP must pay)
conversionRate: agreed rate
paymentMethod: which platform the LP must use
The LP can now see this intent and knows exactly who to pay and how much
Payee Details — Hashing Recipient Payment Info
The payeeDetails field is a hash of the fiat payment recipient's platform-specific identifier. This preserves privacy while allowing the LP to verify they paid the correct person.
import { keccak256, toBytes, encodePacked } from'viem';
// For Venmo: hash the recipient's Venmo username or user IDconst venmoPayeeHash = keccak256(encodePacked(
['string'],
['venmo_username_here']
));
// For Wise: hash the recipient's email or account numberconst wisePayeeHash = keccak256(encodePacked(
['string'],
['recipient@email.com']
));
// For bank transfers (Zelle, etc.): hash the recipient's email or phoneconst zellePayeeHash = keccak256(encodePacked(
['string'],
['+1234567890']
));
Important: The LP needs the actual (unhashed) payee details to send the fiat payment. The ZKP2P API stores encrypted payee details and exposes them only to the matched LP. Use the apiPostDepositDetails() adapter to register payee details:
// Register payee details with ZKP2P API (encrypted, LP-only access)import { apiPostDepositDetails } from'@zkp2p/sdk';
const result = awaitapiPostDepositDetails(
{
depositId: quote.depositId.toString(),
paymentMethodHash: wiseHash,
payeeDetails: 'recipient@email.com', // Plaintext -- API encryptschainId: 8453,
escrowAddress: '0x2f121CDDCA6d652f35e8B3E560f9760898888888',
},
'https://api.zkp2p.xyz',
15000
);
// result.hashedOnchainId is the hash to use as payeeDetails in signalIntent
Step 3-5: LP Handles Fiat Payment and Proof
After the agent signals an intent, the LP-side flow is:
LP detects the intent (via indexer, ProtocolViewer, or event listener)
LP sends fiat to the specified recipient via the required payment platform
LP generates proof using the PeerAuth browser extension
LP calls fulfillIntent() with the proof, releasing USDC to the LP
The agent does NOT need to do anything during Steps 3-5 -- the LP handles everything.
Step 6: Monitor Intent Status
The agent should monitor the intent to confirm fulfillment:
// Poll intent statusasyncfunctionwaitForFulfillment(client: OfframpClient,
intentHash: `0x${string}`,
timeoutMs: number = 86400000// 24 hours): Promise<boolean> {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
const intent = await client.getIntent(intentHash);
if (intent.status === 'FULFILLED') {
console.log('Intent fulfilled! Fiat has been sent to recipient.');
returntrue;
}
if (intent.status === 'CANCELLED' || intent.status === 'EXPIRED') {
console.log(`Intent ${intent.status}. USDC returned to agent.`);
returnfalse;
}
// Wait 60 seconds before checking againawaitnewPromise(resolve =>setTimeout(resolve, 60000));
}
console.log('Timeout waiting for fulfillment.');
returnfalse;
}
Alternatively, the depositor (LP) can release funds back:
// LP releases funds back to the agent (depositor-initiated)const releaseTx = await client.releaseFundsToPayer({
intentHash: intentHash,
});
Limitations
No direct "pay human" API. Currently the agent must manually find an LP, signal an intent, and register payee details. A higher-level API (POST /v1/agent/checkout) that wraps these steps is planned.
LP fulfillment is not guaranteed. After the agent signals an intent and locks USDC, the LP may not fulfill. The agent's USDC is recoverable after intent expiration via cancelIntent().
Fiat amount depends on rate. The fiat amount the freelancer receives is usdcAmount * conversionRate / 1e18. At a 1.02 rate, 150 USDC results in ~$153 USD being sent. The agent pays the spread as a fee to the LP.
Payee details registration. The agent must register the fiat recipient's plaintext payment details with the ZKP2P API so the LP knows where to send fiat. This uses the apiPostDepositDetails() adapter.
Limited platform coverage. Not all payment platforms support all currencies. Check the LP's deposit configuration to ensure the (platform, currency) pair is active.
Future: Agent Off-Ramp Checkout API
A planned API will simplify the off-ramp to a single call:
// FUTURE — not yet availableconst checkout = awaitfetch('https://api.zkp2p.xyz/v1/agent/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
body: JSON.stringify({
amount: '150000000', // 150 USDCcurrency: 'USD',
platform: 'venmo',
recipient: '@freelancer-username',
senderAddress: agentAddress,
}),
});
// Returns: { intentHash, estimatedFiatAmount, lpDepositId, rate }// Agent signs a single transaction to lock USDC// API handles LP matching, payee registration, and monitoring