USDCtoFiat Cash-Out

Add non-custodial Base USDC-to-fiat cash-out with @usdctofiat/offramp cashout({ mode: "fast" | "best" }). Use when an agent must sell USDC from a real viem WalletClient into Venmo, Cash App, Revolut,

SKILL.md

GitHub

USDCtoFiat Cash-Out

Use @usdctofiat/offramp to call cashout({ mode: "fast" | "best" }) with a real viem WalletClient on Base (chain_id: 8453). Require the user to choose a mode.

Payment-policy boundary: SDK support proves technical compatibility, not permission from a payment provider. Wise currently prohibits receiving P2P crypto-sale payments. PayPal may require preapproval for cryptocurrency-related payments. Read the provider's current first-party policy before offering a route.

Fast or Best

Mode Fee Pricing
fast 0% spread / 0 bps Live oracle route (TOFIAT)
best Delegate manager fee 10 bps on USDC Delegate rate-manager path
import { cashout } from "@usdctofiat/offramp";
import type { WalletClient } from "viem";

export async function sellUsdc(signer: WalletClient) {
  return cashout({
    mode: "fast",
    signer,
    amount: "100",
    currency: "EUR",
    platform: "revolut",
    payee: "alice",
  });
}
const fast = await cashout({ ...input, mode: "fast" });
const best = await cashout({ ...input, mode: "best" });

Strings and numbers are human USDC amounts. A bigint is exact six-decimal base units. The helper is production-only and requires an explicit mode.

Persist depositId immediately. Fast depositId is the composite resume key for createOfframp().watch(). Best depositId is the numeric EscrowV2 id for deposits() / close().

Amounts

Both floors ship from the package root. Fast rejects an amount under the hard floor before submission; the practical floor is advice the SDK does not enforce.

Bound Base units USDC Meaning
MIN_CASHOUT_AMOUNT 10000n 0.01 Hard floor; below one cent a deposit is dust and can never fill
RECOMMENDED_MIN_CASHOUT_AMOUNT 1000000n 1 Practical floor; a sub-1-USDC deposit forces min==max fills and starves matching

Ask for a larger amount rather than creating a deposit under the practical floor. Nothing fails loudly, so the deposit is created and then sits unfilled.

Per-order fill range

A deposit is drained by orders, and each mode writes its own per-order range when it creates the deposit. Best caps one order at 1,500 USDC, so a Best cash-out above that cannot clear in a single fill and needs one buyer per 1,500 USDC slice. Fast writes the whole amount as its ceiling.

Mode Per-order min Per-order max
fast min(amount, 1) amount
best 1 min(amount, 1500)

Cells are USDC. Neither ceiling rejects a large amount, so nothing fails: it only changes how many fills the deposit waits for. Prefer Fast above 1,500 USDC, or tell the user Best will fill in pieces.

Rails

Every rail in PLATFORMS. The Offer column is this skill's routing guidance, not an SDK field: a no rail is still a real PLATFORMS entry, so read the reason before assuming the SDK will reject it.

Rail Offer Why
Venmo yes
Revolut yes
PayPal yes May require provider preapproval for cryptocurrency payments
Zelle yes
Monzo yes
Chime yes
Mercado Pago yes
Wise no Published P2P crypto-sale prohibition; the SDK still allows it
Cash App no Held in the SDK's own OFFRAMP_DISABLED_PAYMENT_PLATFORMS

A yes rail is offerable, not preapproved. The payment-policy boundary above still applies, and currencies and payee identifier rules come from the SDK, not from this table.

Disabled rails

OFFRAMP_DISABLED_PAYMENT_PLATFORMS is the SDK's own rail kill-switch and currently holds cashapp. A disabled rail is dropped from capability discovery and rejected before any deposit transaction, whatever the host passes, so do not offer Cash App. Test with isPaymentPlatformDisabled and surface DISABLED_PAYMENT_PLATFORM_MESSAGE instead of inventing copy.

A host adds its own rollout gate with disabledPlatforms, on createOfframp() or per call. Display names and separator variants canonicalize, so Cash App, cash-app and cash_app all hit the same gate.

// hostRollout.disabledRails is the host's own list, not an SDK default.
const order = await cashout({ ...input, mode: "fast", disabledPlatforms: hostRollout.disabledRails });

createOfframp()

Attribution is configured by @usdctofiat/offramp.

import { createOfframp } from "@usdctofiat/offramp";

export async function watchCashout(depositId: string) {
  const client = createOfframp();
  for await (const order of client.watch(depositId)) {
    if (!order.isInFlight) return order;
  }
}

Installation

No source-backed install or usage instructions could be extracted automatically. Review the upstream project before running this skill in a sensitive workflow.

Errors

Managed-path errors extend OfframpError with a typed code:

Code Meaning Recovery
VALIDATION Bad input Fix the input
APPROVAL_FAILED USDC approve reverted or wallet rejected Retry or top up gas
REGISTRATION_FAILED Curator rejected the maker Surface the cause
EXTENSION_REGISTRATION_REQUIRED Route needs an extension handshake usePeerExtensionRegistration(platform) or peerExtensionSdk
DEPOSIT_FAILED Escrow create reverted Check USDC balance, nonce, chain
CONFIRMATION_FAILED Post-deposit confirmation missed Call cashout() again; it resumes
DELEGATION_FAILED setRateManager failed Call again; Best resumes from delegation
USER_CANCELLED Wallet rejected a prompt Do not retry automatically
UNSUPPORTED Non-Base chain or missing client Switch network / pass a Base WalletClient

usePeerExtensionRegistration ships from the @usdctofiat/offramp/react subpath, not the package root. Non-React callers use the root peerExtensionSdk.

Progress callback via onProgress: (p) => void with step values: approving, registering, depositing, confirming, protecting, delegating, restricting, resuming, done.

Rules

  • Call cashout({ mode: "fast" | "best" }) on a real Base wallet and require an explicit mode.
  • Never log walletClient or private keys.
  • Do not invent a sandbox. Production Base only.
  • Read platform identifier rules from PLATFORMS; do not copy a stale list.
  • Check the amount against RECOMMENDED_MIN_CASHOUT_AMOUNT before creating a deposit; a smaller one is accepted and then starves matching.
  • A Best cash-out over 1,500 USDC cannot clear in one order. Route it through Fast or say it fills in pieces.
  • Do not offer Wise while its published P2P crypto-sale prohibition applies.
  • Never pass otcTaker on a fresh cash-out. The protocol cannot create a deposit paused and private in one transaction, so 9.0.0 rejects it with UNSUPPORTED.
  • Restrict an already-confirmed undelegated deposit with enableOtc, and lift it with disableOtc.