Skip to main content

Offramp Integration

The ZKP2P Client SDK is a TypeScript SDK for liquidity providers who want to offer fiat off-ramp services on Base. Use it to create and manage USDC deposits, configure payment methods and currencies, and query on-chain state with RPC-first reads.

Who is this for?​

This SDK is designed for liquidity providers (peers) who want to:

  • Create and manage USDC deposits that accept fiat payments
  • Configure payment methods, currencies, and conversion rates
  • Monitor deposit utilization and manage liquidity
  • Earn fees by providing off-ramp services

Installation​

npm install @zkp2p/sdk viem
# or
yarn add @zkp2p/sdk viem
# or
pnpm add @zkp2p/sdk viem

Quick start​

Initialize the client​

import { Zkp2pClient } from "@zkp2p/sdk";
import { createWalletClient, custom } from "viem";
import { base } from "viem/chains";

const walletClient = createWalletClient({
chain: base,
transport: custom(window.ethereum),
});

const client = new Zkp2pClient({
walletClient,
chainId: base.id,
});

Core operations​

Create a deposit​

import { Currency } from "@zkp2p/sdk";

const { hash } = await client.createDeposit({
token: "0xUSDC_ADDRESS",
amount: 10000000000n, // 10,000 USDC (6 decimals)
intentAmountRange: { min: 100000n, max: 1000000000n },
processorNames: ["wise", "revolut"],
payeeData: [
{ offchainId: "maker" }, // Wise payee details
{ offchainId: "maker" }, // Revolut payee details
],
conversionRates: [
[{ currency: Currency.USD, conversionRate: "1020000000000000000" }], // 1.02 (18 decimals)
[{ currency: Currency.EUR, conversionRate: "950000000000000000" }], // 0.95 (18 decimals)
],
});

console.log("Deposit created:", hash);

Manage deposit settings​

await client.setAcceptingIntents({ depositId: 1n, accepting: true });

await client.setIntentRange({ depositId: 1n, min: 50000n, max: 5000000n });

await client.setCurrencyMinRate({
depositId: 1n,
paymentMethod: "0x...",
fiatCurrency: "0x...",
minConversionRate: 1020000n,
});

Rate fields at a glance​

You will see several rate fields across the contracts, indexer, and curator APIs. They are related but not interchangeable:

FieldWhere it livesMeaning
minConversionRateOn-chain (EscrowV2)The depositor's fixed rate floor per (payment method, currency) pair. Set at deposit creation or via setCurrencyMinRate().
conversionRate / grossRateIndexer, orderbookThe manager/oracle-adjusted gross rate: max(managerRate, escrowFloor), where escrowFloor = max(minConversionRate, oracle spread rate). Equals minConversionRate only when the deposit has neither a vault manager nor an oracle config — an oracle-configured deposit surfaces the escrow floor even without a vault.
effectiveConversionRateCurator quotes and orderbookThe taker-facing rate after manager fees, computed by curator. Quote filtering and sorting use this.

A configured stale or invalid oracle halts new intents even with a fixed floor. A zero escrow floor also halts the pair. With a usable floor, a manager returning zero disables the pair; a manager call reverting falls back to the floor. The max() expressions describe the positive, usable-rate case.

For the full on-chain rate resolution and fee math, see Run a Vault.

Fund management​

await client.addFunds({ depositId: 1n, amount: 5000000n });
await client.removeFunds({ depositId: 1n, amount: 1000000n });
await client.withdrawDeposit({ depositId: 1n });

Querying on-chain data (RPC-first)​

const deposits = await client.getDeposits();
const ownerDeposits = await client.getAccountDeposits("0xOwnerAddress");
const deposit = await client.getDeposit(42n);
const batch = await client.getDepositsById([1n, 2n, 3n]);

const intents = await client.getIntents();
const ownerIntents = await client.getAccountIntents("0xOwnerAddress");
const intent = await client.getIntent("0xIntentHash...");

Indexer queries​

const deposits = await client.indexer.getDeposits(
{ status: "ACTIVE", minLiquidity: "1000000", depositor: "0xYourAddress" },
{ limit: 50, orderBy: "remainingDeposits", orderDirection: "desc" },
);

const depositsWithRelations = await client.indexer.getDepositsWithRelations(
{ status: "ACTIVE" },
{ limit: 50 },
{ includeIntents: true, intentStatuses: ["SIGNALED"] },
);

const fulfillments = await client.indexer.getFulfilledIntentEvents(["0x..."]);

Payment methods​

Each item in payeeData must line up by index with processorNames. If you pass payeeDetailsHashes to createDeposit(), the SDK uses those hashes directly and skips the curator call. Otherwise the SDK forwards each payeeData object to curator's public /v2/makers/create endpoint to obtain the hashes. Either path works without an API key. You can also call registerPayeeDetails() standalone to get the hashes ahead of time.

For optional Venmo receipt linking, use the Cash 0.6.0 hosted flow. The selected handle is registered before opening the popup; Google consent is separate from deposit creation and never required by cashout() or prepare().

The canonical payeeData shape is { offchainId, telegramUsername?, metadata? }. Put the platform-specific identifier in offchainId.

PlatformKeypayeeData exampleNotes
Wisewise{ offchainId: 'your-wisetag' }Pass the Wisetag without @. Register it in Peer extension 0.6.3 or newer before calling curator.
Venmovenmo{ offchainId: 'YourVenmoUsername' }Do not include @. Curator validates the exact Venmo username casing.
Revolutrevolut{ offchainId: 'your-revtag' }Do not include @.
Cash Appcashapp{ offchainId: 'yourcashtag' }Do not include $.
PayPalpaypal{ offchainId: 'yourpaypalmeusername' }Use the PayPal.me username without the paypal.me/ prefix. Requires Peer extension 0.6.3 or newer.
Zellezelle{ offchainId: 'maker@example.com' }Curator expects a lowercase email address.
Monzomonzo{ offchainId: 'your-monzo-me-name' }Use the Monzo.me username only.
Mercado Pagomercadopago{ offchainId: '0000003100064367123868' }CVU must be a valid 22-digit Mercado Pago / bank CVU.
Chimechime{ offchainId: '$yourchimesign' }Include the leading $. Curator expects the value in lowercase.
note

luxon and n26 still appear in the SDK's client-side PAYMENT_PLATFORMS list, but neither is in the production payment-method catalog. The SDK rejects them before submitting a deposit transaction. Don't build against them.

import {
getPaymentMethodsCatalog,
PLATFORM_METADATA,
PAYMENT_PLATFORMS,
} from "@zkp2p/sdk";

console.log(PAYMENT_PLATFORMS);

const methods = getPaymentMethodsCatalog(8453, "production");
const wiseHash = methods["wise"].paymentMethodHash;

const wiseInfo = PLATFORM_METADATA["wise"];
console.log(wiseInfo.displayName);

Currency utilities​

import {
Currency,
currencyInfo,
getCurrencyInfoFromHash,
resolveFiatCurrencyBytes32,
} from "@zkp2p/sdk";

const usd = Currency.USD;
const info = currencyInfo[Currency.USD];
const usdBytes = resolveFiatCurrencyBytes32("USD");

Contract helpers​

import { getContracts, getPaymentMethodsCatalog } from "@zkp2p/sdk";

const { addresses, abis } = getContracts(8453, "production");
const catalog = getPaymentMethodsCatalog(8453, "production");

Supported networks​

NetworkChain IDEnvironment
Base Mainnet8453production
Base Mainnet8453preproduction
Base Mainnet8453staging

Token allowance management​

import { getContracts } from "@zkp2p/sdk";

const { addresses } = getContracts(8453, "production");

const result = await client.ensureAllowance({
token: "0xUSDC_ADDRESS",
amount: 10000000000n,
spender: addresses.escrow,
maxApprove: false,
});

if (result.hadAllowance) {
console.log("Already had sufficient allowance");
} else {
console.log("Approval transaction:", result.hash);
}

Error handling​

import { ValidationError, NetworkError, ContractError } from "@zkp2p/sdk";

try {
await client.createDeposit({
/* ... */
});
} catch (error) {
if (error instanceof ValidationError) {
console.error("Invalid parameters:", error.message);
} else if (error instanceof NetworkError) {
console.error("Network issue:", error.message);
} else if (error instanceof ContractError) {
console.error("Contract error:", error.message);
}
}

Logging​

import { setLogLevel } from "@zkp2p/sdk";

setLogLevel("debug"); // 'debug' | 'info' | 'error'

React hooks​

import {
useCreateDeposit,
useAddFunds,
useRemoveFunds,
useWithdrawDeposit,
useSetAcceptingIntents,
useSetIntentRange,
useSetCurrencyMinRate,
} from "@zkp2p/sdk/react";

function DepositManager({ client }) {
const { createDeposit, isLoading, error } = useCreateDeposit({ client });

const handleCreate = async () => {
const result = await createDeposit({
token: "0xUSDC_ADDRESS",
amount: 10000000000n,
intentAmountRange: { min: 100000n, max: 1000000000n },
processorNames: ["wise"],
payeeData: [{ offchainId: "maker@example.com" }],
conversionRates: [
[{ currency: "USD", conversionRate: "1020000000000000000" }],
],
});
console.log("Created deposit:", result.hash);
};

return (
<div>
<button disabled={isLoading} onClick={handleCreate}>
{isLoading ? "Creating..." : "Create Deposit"}
</button>
{error && <p>Error: {error.message}</p>}
</div>
);
}