Skip to main content

Offramp Integration

As a liquidity provider, you sell USDC on Base for fiat. Use @zkp2p/sdk to create and fund USDC deposits, choose the payment platforms, currencies and rates you accept, and read deposit and intent state. Buyers pay you through those platforms and receive your USDC at your rate.

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,
});

The protocol runs on Base mainnet (chain ID 8453), and the client targets production by default.

Core operations​

Approve USDC​

Approve the escrow before you create or fund a deposit:

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

const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base
const { addresses } = getContracts(8453, "production");

const result = await client.ensureAllowance({
token: USDC,
amount: 10_000_000000n, // 10,000 USDC (6 decimals)
spender: addresses.escrow,
maxApprove: false,
});

if (!result.hadAllowance) {
console.log("Approval transaction:", result.hash);
}

Create a deposit​

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

const { hash } = await client.createDeposit({
token: USDC,
amount: 10_000_000000n, // 10,000 USDC
intentAmountRange: { min: 100000n, max: 1_000_000000n }, // 0.10 to 1,000 USDC per order
processorNames: ["revolut", "chime"],
payeeData: [
{ offchainId: "your-revtag" }, // Revolut
{ offchainId: "$yourchimesign" }, // Chime
],
conversionRates: [
[{ currency: Currency.EUR, conversionRate: "950000000000000000" }], // 0.95 EUR per USDC
[{ currency: Currency.USD, conversionRate: "1020000000000000000" }], // 1.02 USD per USDC
],
});

processorNames, payeeData and conversionRates line up by index. Rates are fiat per USDC with 18 decimals.

Venmo and PayPal deposits on SDK 0.14

If your @zkp2p/sdk version accepts enableTrustedGroupBypass (0.14.x), pass enableTrustedGroupBypass: false whenever a deposit includes Venmo or PayPal. Otherwise createDeposit() also adds the retired Peer Pay merchant group, whose members skip staking on those methods.

Manage deposit settings​

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

const methods = getPaymentMethodsCatalog(8453, "production");

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

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

await client.setCurrencyMinRate({
depositId: 1n,
paymentMethod: methods.revolut.paymentMethodHash,
fiatCurrency: resolveFiatCurrencyBytes32("EUR"),
minConversionRate: 960000000000000000n, // 0.96 (18 decimals)
});

Rate fields at a glance​

Contracts, the indexer and Peer's API expose related rate fields. They are not interchangeable:

FieldWhere it livesMeaning
minConversionRateOn-chain (EscrowV2)The depositor's fixed rate floor per payment method and currency. Set at deposit creation or with setCurrencyMinRate().
conversionRate / grossRateIndexer, orderbookThe gross rate max(managerRate, escrowFloor), where escrowFloor = max(minConversionRate, oracle spread rate). It equals minConversionRate only when the deposit has neither a vault manager nor an oracle configuration.
effectiveConversionRatePeer's API quotes and orderbookThe taker-facing rate after manager fees. Quotes are filtered and sorted by this rate.

A configured oracle that is stale or invalid halts new intents even with a fixed floor, and a zero escrow floor halts the pair. With a usable floor, a manager rate of zero disables the pair, and a reverting manager call falls back to the floor. The max() expressions describe the 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: 5_000000n });
await client.removeFunds({ depositId: 1n, amount: 1_000000n });
await client.withdrawDeposit({ depositId: 1n });

removeFunds() withdraws part of the unlocked balance. withdrawDeposit() returns all unlocked funds and stops the deposit from accepting intents.

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..."]);

Indexer results can lag a transaction. Confirm writes from the receipt and on-chain reads.

Payment methods​

Each payeeData entry has the shape { offchainId, telegramUsername?, metadata?, identityAttestation? }. Put the platform identifier in offchainId.

createDeposit() registers each payee with Peer's API (POST /v2/makers/create) to get its payee hash. To register ahead of time, call registerPayeeDetails() and pass the returned hashes as payeeDetailsHashes; createDeposit() then skips registration. Neither path needs an API key.

PlatformKeypayeeData exampleNotes
Wisewise{ offchainId: 'your-wisetag' }Wisetag without @. A new Wisetag needs an identity attestation
Venmovenmo{ offchainId: 'YourVenmoUsername' }Username without @
Revolutrevolut{ offchainId: 'your-revtag' }Revtag without @
Cash Appcashapp{ offchainId: 'yourcashtag' }Cashtag without $
PayPalpaypal{ offchainId: 'yourpaypalmeusername' }PayPal.me username without paypal.me/. A new username needs an identity attestation
Zellezelle{ offchainId: 'maker@example.com' }Lowercase email address
Monzomonzo{ offchainId: 'your-monzo-me-name' }Monzo.me username
Mercado Pagomercadopago{ offchainId: '0000003100064367123868' }22-digit CVU or CBU
Chimechime{ offchainId: '$yourchimesign' }Lowercase $ChimeSign, including the $
X Moneyxmoney{ offchainId: 'yourxusername' }Lowercase X username without @
UPIupi{ offchainId: 'name@bank' }Lowercase UPI ID

Request identity attestations through the attestation helpers in the Client Reference and pass the result as payeeData[].identityAttestation.

To verify and release incoming payments automatically, connect Seller Autopilot.

note

PAYMENT_PLATFORMS still lists luxon and n26, but neither is in the production payment-method catalog, and the SDK rejects them before sending a deposit transaction.

Catalogs and helpers​

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

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

const usdInfo = currencyInfo[Currency.USD];
const usdBytes32 = resolveFiatCurrencyBytes32("USD");
const fromHash = getCurrencyInfoFromHash(usdBytes32);

getContracts(8453, "production") returns { addresses, abis } for the deployed contracts.

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);
}
}

Set SDK log verbosity with setLogLevel("debug" | "info" | "error").

React hooks​

@zkp2p/sdk/react wraps each deposit operation in a hook: useCreateDeposit, useAddFunds, useRemoveFunds, useWithdrawDeposit, useSetAcceptingIntents, useSetIntentRange and useSetCurrencyMinRate, among others.

import { useCreateDeposit } from "@zkp2p/sdk/react";

function CreateDepositButton({ client, params }) {
const { createDeposit, isLoading, error, txHash } = useCreateDeposit({
client,
});

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

createDeposit() from the hook resolves to null on failure and sets error. See React Hooks for every hook.