@zkp2p/cash 0.1.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +131 -0
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/chunk-4DRZRWWS.js +176 -0
- package/dist/chunk-4DRZRWWS.js.map +1 -0
- package/dist/createCashClient-DpAx9A1A.d.cts +449 -0
- package/dist/createCashClient-DpAx9A1A.d.ts +449 -0
- package/dist/index.cjs +1572 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1367 -0
- package/dist/index.d.ts +1367 -0
- package/dist/index.js +1322 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +292 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +97 -0
- package/dist/react.d.ts +97 -0
- package/dist/react.js +258 -0
- package/dist/react.js.map +1 -0
- package/dist/tools.cjs +148 -0
- package/dist/tools.cjs.map +1 -0
- package/dist/tools.d.cts +31 -0
- package/dist/tools.d.ts +31 -0
- package/dist/tools.js +145 -0
- package/dist/tools.js.map +1 -0
- package/package.json +89 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @zkp2p/cash - agent integration manual
|
|
2
|
+
|
|
3
|
+
You are integrating Peer Cash: an offramp that converts Base USDC to fiat
|
|
4
|
+
(Venmo, Revolut, Wise, Zelle, ...) at the live Chainlink market rate. The user
|
|
5
|
+
whose USDC you manage is the **maker**; a buyer pays them fiat and proves it
|
|
6
|
+
with TEE-TLS; the protocol releases the USDC. Funds are held by the protocol,
|
|
7
|
+
and only the maker can withdraw an unmatched deposit.
|
|
8
|
+
|
|
9
|
+
## Decision tree: pick your entry point
|
|
10
|
+
|
|
11
|
+
1. **You control a signer in-process** (viem `WalletClient`, e.g. a local
|
|
12
|
+
key or embedded wallet) → use `cashout()` / `topUp()` / `withdraw()`
|
|
13
|
+
directly.
|
|
14
|
+
2. **Signing happens elsewhere** (AA bundler, policy engine, custody service,
|
|
15
|
+
human approval step) → use `prepare()` / `prepareTopUp()` /
|
|
16
|
+
`prepareWithdraw()`. Each returns unsigned `txs[]`
|
|
17
|
+
(`{ to, data, value, chainId }`) plus same-index `steps[]` labels; inspect
|
|
18
|
+
the plan, submit the transactions in order, and wait for each receipt.
|
|
19
|
+
3. **You are a tool-use host** (MCP server, CLI) → import the manifest from
|
|
20
|
+
`@zkp2p/cash/tools` and map the tool names to the verbs above. Mutating
|
|
21
|
+
tools return unsigned transactions; keep signing host-side.
|
|
22
|
+
|
|
23
|
+
Every transaction (including approves) carries ERC-8021 attribution:
|
|
24
|
+
`peer-cash`, then any `referrer` codes from `createCashClient` options, then
|
|
25
|
+
the Base builder code.
|
|
26
|
+
|
|
27
|
+
**Two platform caveats, both surfaced in `capabilities()`:**
|
|
28
|
+
|
|
29
|
+
- **Wise and PayPal** carry `requiresIdentityAttestation: true`. Their curator
|
|
30
|
+
registration needs a signed maker identity attestation this SDK cannot mint
|
|
31
|
+
(it comes from the ZKP2P app/extension). A bare-handle `cashout()` to these
|
|
32
|
+
fails fast with `PAYEE_VERIFICATION_REQUIRED` before any transaction.
|
|
33
|
+
- **Venmo, Revolut, Cash App, Monzo** validate the handle against the live
|
|
34
|
+
platform at registration - the account must exist. The rest (Zelle, Chime,
|
|
35
|
+
etc.) are format-checked only. Match handles to the `payeeHint`.
|
|
36
|
+
|
|
37
|
+
## The loop
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createCashClient, usdc, isCashError } from '@zkp2p/cash';
|
|
41
|
+
|
|
42
|
+
const cash = createCashClient({ environment: 'production' });
|
|
43
|
+
|
|
44
|
+
// 1. Discover - static and side-effect free, do this once.
|
|
45
|
+
const caps = cash.capabilities();
|
|
46
|
+
|
|
47
|
+
// 2. Estimate - idempotent, cacheable, no side effects.
|
|
48
|
+
const est = await cash.estimate({ amount: usdc(500), currency: 'EUR' });
|
|
49
|
+
|
|
50
|
+
// 3. Execute.
|
|
51
|
+
const { depositId } = await cash.cashout(
|
|
52
|
+
{
|
|
53
|
+
amount: usdc(500),
|
|
54
|
+
receive: { platform: 'revolut', currency: 'EUR', payee: { offchainId: 'revtag' } },
|
|
55
|
+
},
|
|
56
|
+
{ signer },
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// 4. Persist depositId ↔ your user. That row is the entire integration state.
|
|
60
|
+
|
|
61
|
+
// 5. Drive the lifecycle from nextActions - no heuristics.
|
|
62
|
+
const order = await cash.order(depositId);
|
|
63
|
+
if (order.nextActions.includes('withdraw') && shouldUnwind) {
|
|
64
|
+
await cash.withdraw(depositId, { signer });
|
|
65
|
+
} else if (order.nextActions.includes('wait')) {
|
|
66
|
+
// poll again later, or `for await (const o of cash.watch(depositId))`
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Rules that prevent wrong behavior
|
|
71
|
+
|
|
72
|
+
- **Never promise a rate.** `estimate()` is `kind: 'oracle-estimate'`; the
|
|
73
|
+
binding rate resolves at the oracle when a buyer fills. Do not display or
|
|
74
|
+
log it as a locked price.
|
|
75
|
+
- **Never invent an ETA.** Use `order.explain()` - one honest sentence from
|
|
76
|
+
live data. Buyer arrival time is not knowable.
|
|
77
|
+
- **`ORDER_NOT_FOUND` seconds after `cashout()` is indexer lag**, not a lost
|
|
78
|
+
deposit. The tx receipt you hold is the truth. Retry; `watch()` absorbs
|
|
79
|
+
this automatically.
|
|
80
|
+
- **Unwind with `withdraw()` only.** It is state-aware (prunes expired
|
|
81
|
+
intents first). Do not call escrow functions directly.
|
|
82
|
+
- **Read fills as receipts.** `fiatOwed` is the buyer's obligation at the
|
|
83
|
+
locked rate; after the proof, `fiatPaid`/`paymentId`/`releasedAmount` are
|
|
84
|
+
the verified outcome. Reconcile against those, not your own math.
|
|
85
|
+
- **Check the buyer during `matched`.** `buyer(address)` (tool: `cash_buyer`)
|
|
86
|
+
returns their fulfilled/pruned history and success rate - surface it
|
|
87
|
+
instead of a raw address.
|
|
88
|
+
- **Serialize with the codecs.** `orderToJson`/`orderFromJson` etc. round-trip
|
|
89
|
+
bigints losslessly and re-attach `explain()`. Plain `JSON.stringify` on a
|
|
90
|
+
live object throws on bigints.
|
|
91
|
+
|
|
92
|
+
## Error → remediation table
|
|
93
|
+
|
|
94
|
+
Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
|
|
95
|
+
|
|
96
|
+
| Code | Retryable | Agent action |
|
|
97
|
+
| --------------------------------- | --------- | ----------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `ORACLE_UNSUPPORTED_CURRENCY` | no | Re-pick currency from `capabilities()` |
|
|
99
|
+
| `UNSUPPORTED_PLATFORM` | no | Re-pick platform from `capabilities()` |
|
|
100
|
+
| `AMOUNT_BELOW_MINIMUM` | no | Raise amount (hard floor $0.01, recommended ≥ 1 USDC) |
|
|
101
|
+
| `PAYEE_VERIFICATION_REQUIRED` | no | Wise/PayPal need a signed identity attestation - register the payee via the ZKP2P app first |
|
|
102
|
+
| `PAYEE_REGISTRATION_FAILED` | yes | Validate handle against `payeeHint`, retry with backoff (curator caps at 20 registrations/min) |
|
|
103
|
+
| `ALLOWANCE_NOT_VISIBLE` | yes | Approve mined but a stale RPC replica hid it; retry the same call in a few seconds |
|
|
104
|
+
| `TRANSACTION_FAILED` | no | The on-chain call reverted or was mapped from a raw error; surface to operator; funds unchanged |
|
|
105
|
+
| `DEPOSIT_RESOLUTION_FAILED` | no | Extract depositId from the `DepositReceived` log in the receipt |
|
|
106
|
+
| `ORDER_NOT_FOUND` | yes | Retry (indexer lag) unless the id is provably wrong |
|
|
107
|
+
| `INDEXER_LAG` | yes | Retry after a few seconds |
|
|
108
|
+
| `ACTIVE_INTENT_BLOCKS_WITHDRAWAL` | yes | Wait; retry full `withdraw()` after intent expiry (or withdraw the unlocked part with `amount`) |
|
|
109
|
+
| `INSUFFICIENT_AVAILABLE_FUNDS` | yes | Partial amount exceeds the unlocked balance; lower it or close fully later |
|
|
110
|
+
| `NOTHING_TO_WITHDRAW` | no | Order is terminal; reconcile your records |
|
|
111
|
+
| `ORDER_NOT_ACTIVE` | no | Top-up target is closed; start a new `cashout()` instead |
|
|
112
|
+
| `SIGNER_REQUIRED` | no | Provide `{ signer }` or switch to the prepare path |
|
|
113
|
+
| `WATCH_TIMEOUT` | yes | Resume `watch(depositId)` whenever convenient |
|
|
114
|
+
| `ESCROW_PAUSED` | yes | Back off; existing funds remain withdrawable |
|
|
115
|
+
|
|
116
|
+
`isCashError(err)` narrows unknown errors; `err.toJSON()` is safe for logs
|
|
117
|
+
and tool results.
|
|
118
|
+
|
|
119
|
+
## Verification checklist (staging, maker-side only)
|
|
120
|
+
|
|
121
|
+
Prove your integration against `environment: 'staging'` with a funded test
|
|
122
|
+
wallet. Never wait on a buyer - buyer-side is out of your scope:
|
|
123
|
+
|
|
124
|
+
1. `cashout()` a small amount (1–2 USDC) → capture `depositId`.
|
|
125
|
+
2. `order(depositId)` shows `awaiting-buyer` (retry through indexer lag).
|
|
126
|
+
3. `orders(owner)` includes the deposit.
|
|
127
|
+
4. `withdraw(depositId)` → transaction succeeds.
|
|
128
|
+
5. `order(depositId)` shows `returned`; wallet balance is restored minus gas.
|
|
129
|
+
|
|
130
|
+
If step 4 ever fails with funds stuck, stop and escalate - do not retry
|
|
131
|
+
blindly.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZKP2P
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @zkp2p/cash
|
|
2
|
+
|
|
3
|
+
Cash out Base USDC to fiat on Venmo, Revolut, Wise, Zelle, and more at the
|
|
4
|
+
live Chainlink market rate, with zero spread and no centralized off-ramp
|
|
5
|
+
provider.
|
|
6
|
+
|
|
7
|
+
Peer Cash is an **offramp-only** SDK for the [ZKP2P](https://peer.xyz)
|
|
8
|
+
protocol. The cashing-out user is the maker: their USDC becomes a
|
|
9
|
+
protocol-held deposit, ZKP2P handles the buyer side, and the SDK gives the
|
|
10
|
+
integrator a small set of typed verbs plus readable order state. No hosted
|
|
11
|
+
widget, no provider custody, no quote engine to maintain.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createCashClient, usdc } from '@zkp2p/cash';
|
|
15
|
+
|
|
16
|
+
const cash = createCashClient({ environment: 'production' });
|
|
17
|
+
|
|
18
|
+
const est = await cash.estimate({ amount: usdc(1000), currency: 'USD' });
|
|
19
|
+
// { rate: 1, receiveAmount: 1000, kind: 'oracle-estimate' } - "≈", never a locked quote
|
|
20
|
+
|
|
21
|
+
const { depositId } = await cash.cashout(
|
|
22
|
+
{
|
|
23
|
+
amount: usdc(1000),
|
|
24
|
+
receive: { platform: 'venmo', currency: 'USD', payee: { offchainId: '@you' } },
|
|
25
|
+
},
|
|
26
|
+
{ signer }, // any viem WalletClient on Base
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
for await (const order of cash.watch(depositId)) {
|
|
30
|
+
console.log(order.state, order.explain());
|
|
31
|
+
if (order.state === 'delivered') break;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## The eight verbs
|
|
36
|
+
|
|
37
|
+
| Verb | What it does |
|
|
38
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `capabilities()` | Sync discovery: platforms × currencies × payee format hints × amount bounds |
|
|
40
|
+
| `estimate({ amount, currency })` | Live oracle rate - no payee, no side effects, idempotent |
|
|
41
|
+
| `cashout(input, { signer })` | Registers your payee, creates the protocol-held order, returns the `depositId` |
|
|
42
|
+
| `prepare(input)` | Same as cashout but returns unsigned `txs[]` + readable `steps[]` - agent wallets, AA, server keys |
|
|
43
|
+
| `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
|
|
44
|
+
| `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
|
|
45
|
+
| `withdraw(depositId, { signer, amount? })` | The ONE unwind verb - partial with an `amount` (live intents don't block it), full close without (prunes expired intents first) |
|
|
46
|
+
| `topUp(depositId, amount, { signer })` | Add USDC to a live order - same payee, same market rate |
|
|
47
|
+
| `buyer(address)` | A buyer's track record from their intent history - who just matched your order? |
|
|
48
|
+
|
|
49
|
+
Every mutating verb has an unsigned counterpart (`prepare`, `prepareWithdraw`,
|
|
50
|
+
`prepareTopUp`). The unsigned path returns raw `txs[]` plus a same-index
|
|
51
|
+
`steps[]` plan such as `approve`, `createDeposit`, or `withdrawDeposit`, so
|
|
52
|
+
wallets, AA systems, and agents can show what each transaction does before
|
|
53
|
+
signing. Every transaction - including approves - carries ERC-8021
|
|
54
|
+
attribution: `peer-cash` first, your own `referrer` code(s) after it, so
|
|
55
|
+
onchain analytics can segment cash flow end to end.
|
|
56
|
+
|
|
57
|
+
`capabilities()` tells you which platforms need a verified identity to
|
|
58
|
+
register a payee (`requiresIdentityAttestation` - Wise and PayPal today); a
|
|
59
|
+
bare-handle `cashout()` to those fails fast with `PAYEE_VERIFICATION_REQUIRED`
|
|
60
|
+
rather than reverting on-chain.
|
|
61
|
+
|
|
62
|
+
## Lifecycle
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
buyer signals fiat proven
|
|
66
|
+
awaiting-buyer ──────────► matched ──────────► delivered
|
|
67
|
+
│ │ (partial fills pass through "delivering")
|
|
68
|
+
│ withdraw() │ buyer never pays → intent expires
|
|
69
|
+
▼ ▼ withdraw() prunes + returns funds
|
|
70
|
+
returned ◄─────────────────┘
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **You are the maker.** Your deposit is priced by the live Chainlink oracle
|
|
74
|
+
with `spreadBps: 0`, making it the best offer on the book by construction.
|
|
75
|
+
- **There is no quote.** The binding rate resolves at the oracle when a buyer
|
|
76
|
+
fills. `estimate()` says "approximately"; nothing in this API pretends to
|
|
77
|
+
lock a price.
|
|
78
|
+
- **Everything is resumable.** An order is reconstructed from the chain by
|
|
79
|
+
`depositId` alone. Close the tab, switch devices, crash the process - then
|
|
80
|
+
call `order(depositId)`.
|
|
81
|
+
- **Unwind is one verb.** Buyer never paid? Their intent expires; `withdraw()`
|
|
82
|
+
prunes it and returns your USDC. You never choose between cancel and recover.
|
|
83
|
+
|
|
84
|
+
Deep dive: [docs/lifecycle-and-recovery.md](docs/lifecycle-and-recovery.md).
|
|
85
|
+
|
|
86
|
+
## For agents
|
|
87
|
+
|
|
88
|
+
- `cashout`/`withdraw`/`topUp` have unsigned counterparts (`prepare`,
|
|
89
|
+
`prepareWithdraw`, `prepareTopUp`) - inspect readable `steps[]` and calldata
|
|
90
|
+
before signing, then submit the matching `txs[]` in order.
|
|
91
|
+
- Mutating tool calls return unsigned transactions by default; signing stays
|
|
92
|
+
with the host that owns custody, policy, and user approval.
|
|
93
|
+
- Every error carries `code`, `retryable`, and a `remediation` sentence.
|
|
94
|
+
- Every order carries `nextActions: ('wait' | 'withdraw')[]` - no heuristics.
|
|
95
|
+
- Every wire type has a zod schema + JSON codec - state crosses process
|
|
96
|
+
boundaries losslessly.
|
|
97
|
+
- Everything arrives decoded: platform ids and currency codes instead of
|
|
98
|
+
bytes32 hashes, plain-number rates instead of 1e18 bigints.
|
|
99
|
+
- Fills are receipts: the locked rate and fiat owed at signal, then the
|
|
100
|
+
verified fiat paid, currency, platform payment id, released USDC, and
|
|
101
|
+
fill latency once the proof lands.
|
|
102
|
+
- `@zkp2p/cash/tools` exports a JSON-schema tool manifest of the verbs.
|
|
103
|
+
|
|
104
|
+
Start at [AGENTS.md](AGENTS.md), or load the
|
|
105
|
+
[`peer-cash-integration` skill](skills/peer-cash-integration/SKILL.md).
|
|
106
|
+
|
|
107
|
+
## React
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { useEstimate, useCashout, useOrder, useOrders } from '@zkp2p/cash/react';
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
React is an optional peer dependency - the root entry never imports it.
|
|
114
|
+
|
|
115
|
+
## Environments
|
|
116
|
+
|
|
117
|
+
`production` | `preproduction` | `staging` - selects contracts, curator, and
|
|
118
|
+
indexer. Indexer and curator URLs are overridable via `createCashClient`
|
|
119
|
+
options. v1 is same-chain only: Base USDC in.
|
|
120
|
+
|
|
121
|
+
## Install
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
npm install @zkp2p/cash@dev viem
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Trust model, honestly
|
|
128
|
+
|
|
129
|
+
This SDK is open source, so the code that constructs the parameters moving
|
|
130
|
+
your USDC into protocol-held funds is auditable. It depends on the published
|
|
131
|
+
`@zkp2p/sdk` for protocol internals, which currently ships from private source.
|
|
132
|
+
The Peer Cash facade is verifiable here; the dependency is not yet fully open.
|
|
133
|
+
Onchain custody is still enforced by the protocol: only the contract holds
|
|
134
|
+
funds, and only you can withdraw an unmatched deposit.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// src/engine/constants.ts
|
|
2
|
+
var BASE_CHAIN_ID = 8453;
|
|
3
|
+
var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
4
|
+
var USDC_DECIMALS = 6;
|
|
5
|
+
var MARKET_SPREAD_BPS = 0;
|
|
6
|
+
var ORACLE_MIN_CONVERSION_RATE_SENTINEL = 1n;
|
|
7
|
+
var CASH_ORDER_STATUSES = [
|
|
8
|
+
"SIGNALED",
|
|
9
|
+
"FULFILLED",
|
|
10
|
+
"PRUNED",
|
|
11
|
+
"MANUALLY_RELEASED"
|
|
12
|
+
];
|
|
13
|
+
var CASH_ORDER_POLL_INTERVAL_MS = 5e3;
|
|
14
|
+
var CASH_RETAIN_ON_EMPTY = false;
|
|
15
|
+
|
|
16
|
+
// src/client/errors.ts
|
|
17
|
+
var CashError = class extends Error {
|
|
18
|
+
code;
|
|
19
|
+
retryable;
|
|
20
|
+
remediation;
|
|
21
|
+
constructor(shape, options) {
|
|
22
|
+
super(shape.message, options);
|
|
23
|
+
this.name = "CashError";
|
|
24
|
+
this.code = shape.code;
|
|
25
|
+
this.retryable = shape.retryable;
|
|
26
|
+
this.remediation = shape.remediation;
|
|
27
|
+
}
|
|
28
|
+
/** Serializable view (for tool results and logs). */
|
|
29
|
+
toJSON() {
|
|
30
|
+
return {
|
|
31
|
+
code: this.code,
|
|
32
|
+
message: this.message,
|
|
33
|
+
retryable: this.retryable,
|
|
34
|
+
remediation: this.remediation
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function isCashError(value) {
|
|
39
|
+
return value instanceof CashError;
|
|
40
|
+
}
|
|
41
|
+
var errors = {
|
|
42
|
+
oracleUnsupportedCurrency: (currency) => new CashError({
|
|
43
|
+
code: "ORACLE_UNSUPPORTED_CURRENCY",
|
|
44
|
+
message: `${currency} has no live Chainlink oracle feed; Peer Cash is market-rate only.`,
|
|
45
|
+
retryable: false,
|
|
46
|
+
remediation: `Pick a currency listed in capabilities() - each one is priced by a live oracle feed.`
|
|
47
|
+
}),
|
|
48
|
+
unsupportedPlatform: (platform) => new CashError({
|
|
49
|
+
code: "UNSUPPORTED_PLATFORM",
|
|
50
|
+
message: `'${platform}' is not a supported payout platform in this environment.`,
|
|
51
|
+
retryable: false,
|
|
52
|
+
remediation: `Pick a platform listed in capabilities().`
|
|
53
|
+
}),
|
|
54
|
+
amountBelowMinimum: (amount, min) => new CashError({
|
|
55
|
+
code: "AMOUNT_BELOW_MINIMUM",
|
|
56
|
+
message: `Amount ${amount} is below the minimum cash-out of ${min} USDC base units.`,
|
|
57
|
+
retryable: false,
|
|
58
|
+
remediation: `Increase the amount to at least ${min} base units (${Number(min) / 1e6} USDC).`
|
|
59
|
+
}),
|
|
60
|
+
activeIntentBlocksWithdrawal: (depositId) => new CashError({
|
|
61
|
+
code: "ACTIVE_INTENT_BLOCKS_WITHDRAWAL",
|
|
62
|
+
message: `Order ${depositId} has a live buyer intent; escrow blocks withdrawal while a buyer may still deliver.`,
|
|
63
|
+
retryable: true,
|
|
64
|
+
remediation: `Wait for the buyer to complete or for their intent to expire, then call withdraw() again - it prunes expired intents automatically.`
|
|
65
|
+
}),
|
|
66
|
+
insufficientAvailableFunds: (depositId, requested, available) => new CashError({
|
|
67
|
+
code: "INSUFFICIENT_AVAILABLE_FUNDS",
|
|
68
|
+
message: `Order ${depositId} has ${available} base units available; ${requested} requested.`,
|
|
69
|
+
retryable: true,
|
|
70
|
+
remediation: `Withdraw at most the available (unlocked) amount, or omit the amount to close the order fully once no buyer intent is live.`
|
|
71
|
+
}),
|
|
72
|
+
orderNotActive: (depositId) => new CashError({
|
|
73
|
+
code: "ORDER_NOT_ACTIVE",
|
|
74
|
+
message: `Order ${depositId} is closed (delivered or returned); it cannot be topped up.`,
|
|
75
|
+
retryable: false,
|
|
76
|
+
remediation: `Start a new cash-out with cashout() instead.`
|
|
77
|
+
}),
|
|
78
|
+
nothingToWithdraw: (depositId) => new CashError({
|
|
79
|
+
code: "NOTHING_TO_WITHDRAW",
|
|
80
|
+
message: `Order ${depositId} holds no withdrawable funds (already delivered or returned).`,
|
|
81
|
+
retryable: false,
|
|
82
|
+
remediation: `Check order(depositId).state - this order is terminal.`
|
|
83
|
+
}),
|
|
84
|
+
indexerLag: (depositId) => new CashError({
|
|
85
|
+
code: "INDEXER_LAG",
|
|
86
|
+
message: `Order ${depositId} is not indexed yet (the deposit may be seconds old).`,
|
|
87
|
+
retryable: true,
|
|
88
|
+
remediation: `Retry in a few seconds; on-chain state is ahead of the indexer right after a transaction.`
|
|
89
|
+
}),
|
|
90
|
+
orderNotFound: (depositId) => new CashError({
|
|
91
|
+
code: "ORDER_NOT_FOUND",
|
|
92
|
+
message: `No deposit found for id ${depositId}.`,
|
|
93
|
+
retryable: true,
|
|
94
|
+
remediation: `Verify the composite depositId (escrow_onchainId). If the deposit was created seconds ago this is indexer lag - retry shortly.`
|
|
95
|
+
}),
|
|
96
|
+
payeeRegistrationFailed: (cause) => new CashError(
|
|
97
|
+
{
|
|
98
|
+
code: "PAYEE_REGISTRATION_FAILED",
|
|
99
|
+
message: `Registering payee details with the curator failed.`,
|
|
100
|
+
retryable: true,
|
|
101
|
+
remediation: `Check the payee handle format for the platform (see capabilities() hints) and retry.`
|
|
102
|
+
},
|
|
103
|
+
{ cause }
|
|
104
|
+
),
|
|
105
|
+
payeeVerificationRequired: (platform, cause) => new CashError(
|
|
106
|
+
{
|
|
107
|
+
code: "PAYEE_VERIFICATION_REQUIRED",
|
|
108
|
+
message: `${platform} requires a verified maker identity attestation to register a payee; a bare handle is not accepted.`,
|
|
109
|
+
retryable: false,
|
|
110
|
+
remediation: `Register this ${platform} payee through the ZKP2P app / extension (which produces the signed identity attestation) before cashing out. capabilities() flags such platforms with requiresIdentityAttestation: true.`
|
|
111
|
+
},
|
|
112
|
+
{ cause }
|
|
113
|
+
),
|
|
114
|
+
allowanceNotVisible: (amount) => new CashError({
|
|
115
|
+
code: "ALLOWANCE_NOT_VISIBLE",
|
|
116
|
+
message: `USDC approval for ${amount} base units did not become visible on the read path in time.`,
|
|
117
|
+
retryable: true,
|
|
118
|
+
remediation: `The approve transaction mined but a load-balanced RPC is serving stale state. Retry the same call in a few seconds.`
|
|
119
|
+
}),
|
|
120
|
+
depositResolutionFailed: (txHash) => new CashError({
|
|
121
|
+
code: "DEPOSIT_RESOLUTION_FAILED",
|
|
122
|
+
message: `Deposit transaction ${txHash} succeeded but no DepositReceived event was found in the receipt.`,
|
|
123
|
+
retryable: false,
|
|
124
|
+
remediation: `Inspect the transaction on Basescan; recover the depositId from the DepositReceived log manually, then resume with order(depositId).`
|
|
125
|
+
}),
|
|
126
|
+
signerRequired: (verb) => new CashError({
|
|
127
|
+
code: "SIGNER_REQUIRED",
|
|
128
|
+
message: `${verb}() mutates on-chain state and needs a signer.`,
|
|
129
|
+
retryable: false,
|
|
130
|
+
remediation: `Pass { signer } (a viem WalletClient with an account), or use prepare() and submit the returned txs with your own signing infrastructure.`
|
|
131
|
+
}),
|
|
132
|
+
watchTimeout: (depositId, timeoutMs) => new CashError({
|
|
133
|
+
code: "WATCH_TIMEOUT",
|
|
134
|
+
message: `watch(${depositId}) exceeded ${timeoutMs}ms without reaching a terminal state.`,
|
|
135
|
+
retryable: true,
|
|
136
|
+
remediation: `The order is still live - resume any time with watch(depositId) or order(depositId).`
|
|
137
|
+
}),
|
|
138
|
+
transactionFailed: (txHash, cause) => new CashError(
|
|
139
|
+
{
|
|
140
|
+
code: "TRANSACTION_FAILED",
|
|
141
|
+
message: `Transaction ${txHash} reverted.`,
|
|
142
|
+
retryable: false,
|
|
143
|
+
remediation: `Inspect the transaction on Basescan; the deposit state is unchanged if the revert happened before escrow accepted funds.`
|
|
144
|
+
},
|
|
145
|
+
{ cause }
|
|
146
|
+
),
|
|
147
|
+
escrowPaused: () => new CashError({
|
|
148
|
+
code: "ESCROW_PAUSED",
|
|
149
|
+
message: `The escrow contract is paused; deposits are temporarily disabled.`,
|
|
150
|
+
retryable: true,
|
|
151
|
+
remediation: `Wait for the protocol to unpause and retry. Existing funds remain withdrawable.`
|
|
152
|
+
}),
|
|
153
|
+
/** Generic fallback for an on-chain call that failed for an unrecognized reason. */
|
|
154
|
+
chainCallFailed: (verb, cause) => new CashError(
|
|
155
|
+
{
|
|
156
|
+
code: "TRANSACTION_FAILED",
|
|
157
|
+
message: `The on-chain ${verb} call failed.`,
|
|
158
|
+
retryable: false,
|
|
159
|
+
remediation: `Inspect the error cause and the wallet on Basescan. Deposit state is unchanged if the call reverted before escrow accepted funds.`
|
|
160
|
+
},
|
|
161
|
+
{ cause }
|
|
162
|
+
)
|
|
163
|
+
};
|
|
164
|
+
function mapChainError(verb, err) {
|
|
165
|
+
if (isCashError(err)) return err;
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
if (/\bpaused\b/i.test(message)) return errors.escrowPaused();
|
|
168
|
+
if (/exceeds allowance|insufficient allowance|transfer amount exceeds/i.test(message)) {
|
|
169
|
+
return errors.allowanceNotVisible(0n);
|
|
170
|
+
}
|
|
171
|
+
return errors.chainCallFailed(verb, err);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { BASE_CHAIN_ID, BASE_USDC_ADDRESS, CASH_ORDER_POLL_INTERVAL_MS, CASH_ORDER_STATUSES, CASH_RETAIN_ON_EMPTY, CashError, MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, USDC_DECIMALS, errors, isCashError, mapChainError };
|
|
175
|
+
//# sourceMappingURL=chunk-4DRZRWWS.js.map
|
|
176
|
+
//# sourceMappingURL=chunk-4DRZRWWS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/engine/constants.ts","../src/client/errors.ts"],"names":[],"mappings":"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","file":"chunk-4DRZRWWS.js","sourcesContent":["/**\n * Peer Cash - engine constants.\n *\n * Peer Cash is an async crypto→fiat offramp built on the maker/deposit side of\n * the protocol: the cashing-out user IS the maker. They create a deposit at the\n * live oracle/market rate (0% spread); a buyer (a standard taker) signals an\n * intent, pays fiat, and proves it via the standard TEE-TLS flow, releasing the\n * user's crypto. The protocol is reused in its existing direction - no proof\n * inversion, no sell-side quote.\n */\nimport type { IntentStatus } from '../sdk-types';\n\n/** Base chain id - Peer Cash settles in Base USDC. */\nexport const BASE_CHAIN_ID = 8453;\n\n/** Canonical USDC on Base (6 decimals). The deposit asset for every cash-out. */\nexport const BASE_USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' as const;\n\n/** USDC has 6 decimals. */\nexport const USDC_DECIMALS = 6;\n\n/**\n * Market rate = the live Chainlink oracle with **zero spread**. The user sets no\n * rate; selling at market is the fast-fill incentive (the deposit is the best\n * deal on the book, so buyers have reason to take it quickly).\n */\nexport const MARKET_SPREAD_BPS = 0;\n\n/**\n * EscrowV2 rejects a zero `minConversionRate` even when an oracle-backed rate\n * config is attached. Use the smallest non-zero sentinel so the oracle rate\n * still fully determines pricing while satisfying the on-chain invariant.\n */\nexport const ORACLE_MIN_CONVERSION_RATE_SENTINEL = 1n;\n\n/**\n * The full intent-status set a cash-out order can pass through. The indexer's\n * `getIntentsForDeposits` defaults to `['SIGNALED']` only - passing this\n * explicit set is REQUIRED, otherwise `delivered`/`returned` states are\n * silently filtered out.\n */\nexport const CASH_ORDER_STATUSES: IntentStatus[] = [\n 'SIGNALED',\n 'FULFILLED',\n 'PRUNED',\n 'MANUALLY_RELEASED',\n];\n\n/** Default polling cadence for an in-flight order (ms). Matches the protocol's active-intent polling. */\nexport const CASH_ORDER_POLL_INTERVAL_MS = 5_000;\n\n/**\n * Default deposit config for every Peer Cash deposit: a one-shot cash-out\n * cleans up when fully filled rather than lingering empty.\n */\nexport const CASH_RETAIN_ON_EMPTY = false;\n","/**\n * Typed errors - every failure carries a `code`, whether it is `retryable`,\n * and a `remediation` sentence so agents can self-drive recovery.\n */\nexport type CashErrorCode =\n | 'ORACLE_UNSUPPORTED_CURRENCY'\n | 'UNSUPPORTED_PLATFORM'\n | 'AMOUNT_BELOW_MINIMUM'\n | 'ACTIVE_INTENT_BLOCKS_WITHDRAWAL'\n | 'NOTHING_TO_WITHDRAW'\n | 'INSUFFICIENT_AVAILABLE_FUNDS'\n | 'ORDER_NOT_ACTIVE'\n | 'ESCROW_PAUSED'\n | 'INDEXER_LAG'\n | 'ORDER_NOT_FOUND'\n | 'PAYEE_REGISTRATION_FAILED'\n | 'PAYEE_VERIFICATION_REQUIRED'\n | 'DEPOSIT_RESOLUTION_FAILED'\n | 'ALLOWANCE_NOT_VISIBLE'\n | 'SIGNER_REQUIRED'\n | 'WATCH_TIMEOUT'\n | 'TRANSACTION_FAILED';\n\nexport interface CashErrorShape {\n code: CashErrorCode;\n message: string;\n retryable: boolean;\n remediation: string;\n}\n\nexport class CashError extends Error implements CashErrorShape {\n readonly code: CashErrorCode;\n readonly retryable: boolean;\n readonly remediation: string;\n\n constructor(shape: CashErrorShape, options?: { cause?: unknown }) {\n super(shape.message, options);\n this.name = 'CashError';\n this.code = shape.code;\n this.retryable = shape.retryable;\n this.remediation = shape.remediation;\n }\n\n /** Serializable view (for tool results and logs). */\n toJSON(): CashErrorShape {\n return {\n code: this.code,\n message: this.message,\n retryable: this.retryable,\n remediation: this.remediation,\n };\n }\n}\n\nexport function isCashError(value: unknown): value is CashError {\n return value instanceof CashError;\n}\n\n/** Factory helpers keep call sites one-liners and remediation copy consistent. */\nexport const errors = {\n oracleUnsupportedCurrency: (currency: string) =>\n new CashError({\n code: 'ORACLE_UNSUPPORTED_CURRENCY',\n message: `${currency} has no live Chainlink oracle feed; Peer Cash is market-rate only.`,\n retryable: false,\n remediation: `Pick a currency listed in capabilities() - each one is priced by a live oracle feed.`,\n }),\n unsupportedPlatform: (platform: string) =>\n new CashError({\n code: 'UNSUPPORTED_PLATFORM',\n message: `'${platform}' is not a supported payout platform in this environment.`,\n retryable: false,\n remediation: `Pick a platform listed in capabilities().`,\n }),\n amountBelowMinimum: (amount: bigint, min: bigint) =>\n new CashError({\n code: 'AMOUNT_BELOW_MINIMUM',\n message: `Amount ${amount} is below the minimum cash-out of ${min} USDC base units.`,\n retryable: false,\n remediation: `Increase the amount to at least ${min} base units (${Number(min) / 1e6} USDC).`,\n }),\n activeIntentBlocksWithdrawal: (depositId: string) =>\n new CashError({\n code: 'ACTIVE_INTENT_BLOCKS_WITHDRAWAL',\n message: `Order ${depositId} has a live buyer intent; escrow blocks withdrawal while a buyer may still deliver.`,\n retryable: true,\n remediation: `Wait for the buyer to complete or for their intent to expire, then call withdraw() again - it prunes expired intents automatically.`,\n }),\n insufficientAvailableFunds: (depositId: string, requested: bigint, available: bigint) =>\n new CashError({\n code: 'INSUFFICIENT_AVAILABLE_FUNDS',\n message: `Order ${depositId} has ${available} base units available; ${requested} requested.`,\n retryable: true,\n remediation: `Withdraw at most the available (unlocked) amount, or omit the amount to close the order fully once no buyer intent is live.`,\n }),\n orderNotActive: (depositId: string) =>\n new CashError({\n code: 'ORDER_NOT_ACTIVE',\n message: `Order ${depositId} is closed (delivered or returned); it cannot be topped up.`,\n retryable: false,\n remediation: `Start a new cash-out with cashout() instead.`,\n }),\n nothingToWithdraw: (depositId: string) =>\n new CashError({\n code: 'NOTHING_TO_WITHDRAW',\n message: `Order ${depositId} holds no withdrawable funds (already delivered or returned).`,\n retryable: false,\n remediation: `Check order(depositId).state - this order is terminal.`,\n }),\n indexerLag: (depositId: string) =>\n new CashError({\n code: 'INDEXER_LAG',\n message: `Order ${depositId} is not indexed yet (the deposit may be seconds old).`,\n retryable: true,\n remediation: `Retry in a few seconds; on-chain state is ahead of the indexer right after a transaction.`,\n }),\n orderNotFound: (depositId: string) =>\n new CashError({\n code: 'ORDER_NOT_FOUND',\n message: `No deposit found for id ${depositId}.`,\n retryable: true,\n remediation: `Verify the composite depositId (escrow_onchainId). If the deposit was created seconds ago this is indexer lag - retry shortly.`,\n }),\n payeeRegistrationFailed: (cause: unknown) =>\n new CashError(\n {\n code: 'PAYEE_REGISTRATION_FAILED',\n message: `Registering payee details with the curator failed.`,\n retryable: true,\n remediation: `Check the payee handle format for the platform (see capabilities() hints) and retry.`,\n },\n { cause },\n ),\n payeeVerificationRequired: (platform: string, cause?: unknown) =>\n new CashError(\n {\n code: 'PAYEE_VERIFICATION_REQUIRED',\n message: `${platform} requires a verified maker identity attestation to register a payee; a bare handle is not accepted.`,\n retryable: false,\n remediation: `Register this ${platform} payee through the ZKP2P app / extension (which produces the signed identity attestation) before cashing out. capabilities() flags such platforms with requiresIdentityAttestation: true.`,\n },\n { cause },\n ),\n allowanceNotVisible: (amount: bigint) =>\n new CashError({\n code: 'ALLOWANCE_NOT_VISIBLE',\n message: `USDC approval for ${amount} base units did not become visible on the read path in time.`,\n retryable: true,\n remediation: `The approve transaction mined but a load-balanced RPC is serving stale state. Retry the same call in a few seconds.`,\n }),\n depositResolutionFailed: (txHash: string) =>\n new CashError({\n code: 'DEPOSIT_RESOLUTION_FAILED',\n message: `Deposit transaction ${txHash} succeeded but no DepositReceived event was found in the receipt.`,\n retryable: false,\n remediation: `Inspect the transaction on Basescan; recover the depositId from the DepositReceived log manually, then resume with order(depositId).`,\n }),\n signerRequired: (verb: string) =>\n new CashError({\n code: 'SIGNER_REQUIRED',\n message: `${verb}() mutates on-chain state and needs a signer.`,\n retryable: false,\n remediation: `Pass { signer } (a viem WalletClient with an account), or use prepare() and submit the returned txs with your own signing infrastructure.`,\n }),\n watchTimeout: (depositId: string, timeoutMs: number) =>\n new CashError({\n code: 'WATCH_TIMEOUT',\n message: `watch(${depositId}) exceeded ${timeoutMs}ms without reaching a terminal state.`,\n retryable: true,\n remediation: `The order is still live - resume any time with watch(depositId) or order(depositId).`,\n }),\n transactionFailed: (txHash: string, cause?: unknown) =>\n new CashError(\n {\n code: 'TRANSACTION_FAILED',\n message: `Transaction ${txHash} reverted.`,\n retryable: false,\n remediation: `Inspect the transaction on Basescan; the deposit state is unchanged if the revert happened before escrow accepted funds.`,\n },\n { cause },\n ),\n escrowPaused: () =>\n new CashError({\n code: 'ESCROW_PAUSED',\n message: `The escrow contract is paused; deposits are temporarily disabled.`,\n retryable: true,\n remediation: `Wait for the protocol to unpause and retry. Existing funds remain withdrawable.`,\n }),\n /** Generic fallback for an on-chain call that failed for an unrecognized reason. */\n chainCallFailed: (verb: string, cause?: unknown) =>\n new CashError(\n {\n code: 'TRANSACTION_FAILED',\n message: `The on-chain ${verb} call failed.`,\n retryable: false,\n remediation: `Inspect the error cause and the wallet on Basescan. Deposit state is unchanged if the call reverted before escrow accepted funds.`,\n },\n { cause },\n ),\n};\n\n/**\n * Map a raw SDK/RPC/viem error from a mutating on-chain call to a typed\n * `CashError`, so the package's error contract holds even when the underlying\n * call reverts. Recognized reverts get specific codes; everything else falls\n * back to a wrapped `TRANSACTION_FAILED` (never a raw error to the consumer).\n */\nexport function mapChainError(verb: string, err: unknown): CashError {\n if (isCashError(err)) return err;\n const message = err instanceof Error ? err.message : String(err);\n if (/\\bpaused\\b/i.test(message)) return errors.escrowPaused();\n if (/exceeds allowance|insufficient allowance|transfer amount exceeds/i.test(message)) {\n return errors.allowanceNotVisible(0n);\n }\n return errors.chainCallFailed(verb, err);\n}\n"]}
|