@zkp2p/cash 0.4.7 → 0.4.9

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # @zkp2p/cash - agent integration manual
2
2
 
3
+ > This file is the shipped manual for agents **using** the package. To
4
+ > contribute to the SDK itself (layout, ground rules, CI, releasing), start at
5
+ > [CLAUDE.md](https://github.com/zkp2p/peer-cash/blob/main/CLAUDE.md).
6
+
3
7
  You are integrating Peer Cash: an offramp that routes any Relay-supported EVM
4
8
  source asset into Base USDC, then converts Base USDC to fiat (Venmo, Revolut,
5
9
  Wise, Zelle, ...) at the live Chainlink market rate. The user whose USDC you
@@ -9,16 +13,18 @@ can withdraw an unmatched deposit.
9
13
 
10
14
  ## Decision tree: pick your entry point
11
15
 
12
- 1. **You control a signer in-process** (viem `WalletClient`, e.g. a local
13
- key or embedded wallet) → use `cashout()` / `topUp()` / `withdraw()`
14
- directly.
16
+ 1. **You control a signer in-process** (any viem `WalletClient`, including a
17
+ local or externally connected EOA) → use `cashout()` / `topUp()` /
18
+ `withdraw()` directly. No Privy wallet is required.
15
19
  2. **Signing happens elsewhere** (AA bundler, policy engine, custody service,
16
20
  human approval step) → use `prepare()` / `prepareTopUp()` /
17
21
  `prepareWithdraw()`. Each returns unsigned `txs[]`
18
22
  (`{ to, data, value, chainId }`) plus same-index `steps[]` labels; inspect
19
23
  the plan, submit the transactions in order, and wait for each receipt. After
20
24
  `createDeposit` confirms, pass its receipt to `finalizePreparedCashout()` and
21
- persist the returned `depositId`.
25
+ persist the returned `depositId`. If the original plan set
26
+ `accessPolicyRequired: true`, submit and confirm
27
+ `prepareAccessPolicy(depositId)` with the depositor next.
22
28
  3. **You are a tool-use host** (MCP server, CLI) → import the manifest from
23
29
  `@zkp2p/cash/tools` and map the tool names to the verbs above. Base-USDC
24
30
  mutating tools return unsigned transactions. `cash_source_quote` is a quote,
@@ -32,19 +38,23 @@ Every transaction (including approves) carries ERC-8021 attribution:
32
38
  code. The namespaced referral marker gives its Peer Privy wallet the direct
33
39
  deposit-level integration share instead of applying maker L1/L2.
34
40
 
35
- **Platform caveats, all surfaced in `capabilities()`:**
41
+ **Platform caveats:**
36
42
 
37
- - **Venmo, Cash App, and PayPal** carry
38
- `requiresAtomicAccessPolicy: true`. Generic `cashout()` and `prepare()` reject
39
- them with `ATOMIC_ACCESS_POLICY_REQUIRED` before any side effect. Use Peer
40
- web's Curator-backed atomic flow or a host with the same atomic guarantee;
41
- never create the deposit and attach the group policy sequentially.
43
+ - **Venmo, Cash App, and PayPal restrict who can signal intents by default.**
44
+ If any payout leg uses one of these platforms, signed
45
+ `cashout()` confirms `createDeposit`, then submits and confirms the Plus, Pro,
46
+ Peer Makers, and Peer Pay policy using the same viem wallet. This is a
47
+ deliberate non-atomic follow-up with a brief unprotected interval. For
48
+ `prepare()`, check `accessPolicyRequired`; after `createDeposit` confirms,
49
+ finalize its receipt and submit `prepareAccessPolicy(depositId)` with the
50
+ depositor. Any viem EOA works; Privy is not required.
42
51
 
43
52
  - **Wise and PayPal** carry `requiresIdentityAttestation: true`. A new curator
44
53
  registration needs a signed maker identity attestation this SDK cannot mint
45
- (it comes from the Peer app/extension). An already-registered handle can be
46
- reused with bare payee data. A new handle without its attestation fails with
47
- `PAYEE_VERIFICATION_REQUIRED` before funds move on-chain.
54
+ (first-party Peer web obtains it through the Peer TEE browser extension).
55
+ An already-registered handle can be reused with bare payee data. A new handle
56
+ without its attestation fails with `PAYEE_VERIFICATION_REQUIRED` before funds
57
+ move on-chain.
48
58
  - **Venmo, Revolut, Cash App, Monzo** validate the handle against the live
49
59
  platform at registration - the account must exist. The rest (Zelle, Chime,
50
60
  etc.) are format-checked only. Match handles to the `payeeHint`.
@@ -178,6 +188,10 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
178
188
  broadcast; follow its recovery action and inspect wallet/protocol state.
179
189
  `TRANSACTION_STATUS_UNKNOWN` means the returned hash may already have
180
190
  succeeded. Inspect `err.recovery.transactionHash` first.
191
+ - **Never repeat a cash-out after an access-policy failure.** The deposit
192
+ already exists. If `recovery.transactionHash` is present, inspect that policy
193
+ transaction first; prepare another policy transaction only when the previous
194
+ one is absent or confirmed reverted.
181
195
  - **`ORDER_NOT_FOUND` seconds after `cashout()` is indexer lag**, not a lost
182
196
  deposit. The tx receipt you hold is the truth. Retry; `watch()` absorbs
183
197
  this automatically.
@@ -212,10 +226,10 @@ Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
212
226
  | `INVALID_INTENT_AMOUNT_RANGE` | no | Use a positive min, max at least min, and max no greater than amount |
213
227
  | `INVALID_PAYOUT_CURRENCIES` | no | Pass one or more unique currencies listed for the platform |
214
228
  | `INVALID_PAYOUT_PLATFORMS` | no | Pass one leg or an array of legs, using each platform at most once |
215
- | `PAYEE_VERIFICATION_REQUIRED` | no | Register a new Wise/PayPal payee through Peer; an existing registered handle can be reused |
229
+ | `PAYEE_VERIFICATION_REQUIRED` | no | Use Peer web + TEE extension for new Wise/PayPal; reuse registered handles |
216
230
  | `PAYEE_REGISTRATION_FAILED` | yes | Validate against `payeeHint`, then retry |
217
- | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Use Peer web or an equivalent atomic host for Venmo, Cash App, or PayPal; nothing moved |
218
- | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | Recover an existing `0.4.4` deposit from its policy recovery data; do not create it again |
231
+ | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Deprecated compatibility code; current SDK flows never emit it |
232
+ | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | Deposit exists; inspect policy tx first, attach only if needed; never repeat the cash-out. |
219
233
  | `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE` | no | Execute Relay with a signer first, then prepare a Base-USDC cashout |
220
234
  | `SOURCE_RECIPIENT_MISMATCH` | no | Route Base USDC to the cashout depositor |
221
235
  | `SOURCE_CAPABILITIES_FAILED` | yes | Retry discovery or fall back to Base USDC |
@@ -255,7 +269,8 @@ and tool results.
255
269
  Prove your integration against `environment: 'staging'` with a funded test
256
270
  wallet. Never wait on a buyer - buyer-side is out of your scope:
257
271
 
258
- 1. `cashout()` a small amount (1–2 USDC) → capture `depositId`.
272
+ 1. `cashout()` a small amount (1–2 USDC) → capture `depositId` and, for a
273
+ restricted payout, `accessPolicyTxHash`.
259
274
  2. `order(depositId)` shows `awaiting-buyer` (retry through indexer lag).
260
275
  3. `orders(owner)` includes the deposit.
261
276
  4. `withdraw(depositId)` → transaction succeeds.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @zkp2p/cash
2
2
 
3
- Route any Relay-supported EVM source asset into Base USDC, then cash out to fiat
4
- on Venmo, Revolut, Wise, Zelle, and more at the live Chainlink market rate,
5
- with zero spread and no centralized off-ramp provider.
3
+ Route any Relay-supported EVM source asset into Base USDC, then cash out to
4
+ fiat on Venmo, Revolut, Wise, Zelle, and more at the live Chainlink market
5
+ rate, with zero spread and no centralized off-ramp provider.
6
6
 
7
7
  Peer Cash is an **offramp-only** SDK for the [ZKP2P](https://peer.xyz)
8
8
  protocol. The cashing-out user is the maker: their USDC becomes a deposit in
@@ -12,18 +12,13 @@ No hosted widget, no provider custody, no quote engine to maintain.
12
12
 
13
13
  **[npm](https://www.npmjs.com/package/@zkp2p/cash)** · **[Lifecycle and recovery](docs/lifecycle-and-recovery.md)** · **[Agent integration manual](AGENTS.md)**
14
14
 
15
- ## Pick the right SDK
16
-
17
- Peer Cash and the general ZKP2P SDK serve different integration depths:
15
+ ## Install
18
16
 
19
- | Package | Use it when | Boundary |
20
- | ------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
- | `@zkp2p/cash` | Cash-out is the product | Offramp only. The user is always the maker, the destination is Base USDC, pricing is the live Chainlink rate at fill with zero spread, and the SDK owns the resumable order lifecycle. |
22
- | `@zkp2p/sdk` | You are composing directly with the Peer protocol | General maker and taker operations, deposits, intents, proofs, quotes, vaults, rate managers, referrals, hooks, and API helpers. Your application owns the workflow and protocol choices. |
17
+ ```sh
18
+ npm install @zkp2p/cash viem
19
+ ```
23
20
 
24
- Peer Cash is a narrow facade over `@zkp2p/sdk`, not a replacement for it. It
25
- cannot express custom spreads, buyer-side proof flows, vaults, disputes, or
26
- arbitrary protocol operations.
21
+ ## Quickstart
27
22
 
28
23
  ```ts
29
24
  import { createCashClient, usdc } from '@zkp2p/cash';
@@ -43,13 +38,15 @@ const fillStats = await cash.fillStats();
43
38
  const pairStats = fillStats['venmo:USD'];
44
39
  const multiCurrencyStats = fillStats['revolut:EUR+GBP+USD'];
45
40
 
46
- const { depositId } = await cash.cashout(
41
+ const { depositId, accessPolicyTxHash } = await cash.cashout(
47
42
  {
48
43
  amount: usdc(1000),
49
- receive: { platform: 'chime', currency: 'USD', payee: '$you' },
44
+ receive: { platform: 'venmo', currency: 'USD', payee: '@you' },
50
45
  },
51
- { signer }, // any viem WalletClient on Base
46
+ { signer }, // any viem WalletClient on Base, including an EOA
52
47
  );
48
+ // Venmo, Cash App, and PayPal return only after their access policy confirms.
49
+ console.log(depositId, accessPolicyTxHash);
53
50
 
54
51
  // One method can offer several currencies. The buyer chooses the fill
55
52
  // currency, and each option resolves at its own live oracle rate.
@@ -71,7 +68,7 @@ const widestReach = await cash.cashout(
71
68
  {
72
69
  amount: usdc(1000),
73
70
  receive: [
74
- { platform: 'chime', currency: 'USD', payee: '$you' },
71
+ { platform: 'venmo', currency: 'USD', payee: '@you' },
75
72
  { platform: 'revolut', currencies: ['EUR', 'GBP'], payee: { offchainId: 'revtag' } },
76
73
  ],
77
74
  },
@@ -84,52 +81,18 @@ for await (const order of cash.watch(depositId)) {
84
81
  }
85
82
  ```
86
83
 
87
- ## Earn the integration share
88
-
89
- Use the same six-character referral code shown in your Peer mobile or web app.
90
- No API key, registration transaction, or separate receiving address is needed:
91
- the referral code already belongs to your Peer Privy wallet.
92
-
93
- ```ts
94
- const cash = createCashClient({
95
- environment: 'production',
96
- referralCode: 'ABC123',
97
- });
98
- ```
99
-
100
- The SDK normalizes the value and stamps `peer-ref-ABC123` into ERC-8021
101
- attribution on the deposit transaction. When that liquidity is filled, Curator
102
- pays the code owner 50 bps, capped by the configured Peer service fee. This is
103
- the deposit-level integration path: it replaces the maker L1/L2 referral split
104
- for that deposit instead of enrolling the cashing-out user as your referee.
105
-
106
- The mapping is permanent. If you later customize your displayed Peer referral
107
- code, open deposits carrying the old code still pay the same wallet. Include at
108
- most one `peer-ref-XXXXXX` marker; an unknown or conflicting marker receives no
109
- integration share. The existing `referrer` option remains available for
110
- analytics-only ERC-8021 codes such as `acme-app`.
84
+ ## Pick the right SDK
111
85
 
112
- Source asset path:
86
+ Peer Cash and the general ZKP2P SDK serve different integration depths:
113
87
 
114
- ```ts
115
- const { depositId, source } = await cash.cashout(
116
- {
117
- amount: 10_000_000n, // exact input: 10 USDC in source-token base units
118
- source: {
119
- chainId: 1,
120
- currency: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
121
- tradeType: 'EXACT_INPUT',
122
- },
123
- receive: { platform: 'chime', currency: 'USD', payee: { offchainId: '$you' } },
124
- },
125
- { signer, sourceSigner },
126
- );
88
+ | Package | Use it when | Boundary |
89
+ | ------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
90
+ | `@zkp2p/cash` | Cash-out is the product | Offramp only. The user is always the maker, the destination is Base USDC, pricing is the live Chainlink rate at fill with zero spread, and the SDK owns the resumable order lifecycle. |
91
+ | `@zkp2p/sdk` | You are composing directly with the Peer protocol | General maker and taker operations, deposits, intents, proofs, quotes, vaults, rate managers, referrals, hooks, and API helpers. Your application owns the workflow and protocol choices. |
127
92
 
128
- // source.amount is Relay's guaranteed minimum Base USDC output and the exact
129
- // amount deposited into the cash-out order. It is not the route's actual output.
130
- console.log(source?.amount, source?.requestId);
131
- console.log(source?.transactions?.origin, source?.transactions?.destination);
132
- ```
93
+ Peer Cash is a narrow facade over `@zkp2p/sdk`, not a replacement for it. It
94
+ cannot express custom spreads, buyer-side proof flows, vaults, disputes, or
95
+ arbitrary protocol operations.
133
96
 
134
97
  ## The core verbs
135
98
 
@@ -141,9 +104,9 @@ console.log(source?.transactions?.origin, source?.transactions?.destination);
141
104
  | `quoteSource(input)` / `executeSourceQuote(quote, { signer })` | Relay SDK EVM source routing into Base USDC before cashout |
142
105
  | `relayStatus(requestId)` | Relay request status from the Relay SDK request path |
143
106
  | `estimate({ amount, currency }, { includeEta? })` | Base USDC oracle estimate; optionally skip the historical ETA for progressive rendering |
144
- | `cashout(input, { signer })` | Registers the payee and creates an unrestricted-rail order; protected rails fail before any side effect |
145
- | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare unrestricted-rail external signing, then resolve the confirmed createDeposit receipt into resumable state |
146
- | `prepareAccessPolicy(depositId)` | Recover a restricted-rail deposit created by the unsafe `0.4.4` sequential flow; never use it to create a new order |
107
+ | `cashout(input, { signer })` | Creates the order with any viem wallet; Venmo, Cash App, and PayPal then attach the canonical access groups |
108
+ | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare external signing, resolve the deposit, then check `accessPolicyRequired` for the follow-up |
109
+ | `prepareAccessPolicy(depositId)` | Prepare the post-deposit Plus, Pro, Peer Makers, and Peer Pay policy transaction |
147
110
  | `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
148
111
  | `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
149
112
  | `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) |
@@ -165,31 +128,55 @@ Peer Cash transaction, including approves, carries ERC-8021 attribution:
165
128
  `peer-cash` first, optional `peer-ref-XXXXXX` from `referralCode` next, and your
166
129
  analytics-only `referrer` code(s) after it.
167
130
 
168
- Venmo, Cash App, and PayPal must attach Plus, Pro, Peer Makers, and Peer Pay in
169
- the same atomic operation that creates the deposit. A create-then-configure
170
- sequence leaves a public interval in which an unapproved taker can signal an
171
- intent. The generic `cashout()` and `prepare()` paths therefore throw
172
- `ATOMIC_ACCESS_POLICY_REQUIRED` before wallet access, Relay quoting, payee
173
- registration, allowance, or deposit submission. Peer web supports these rails
174
- through its atomic Curator-backed flow. Other hosts must provide an equivalent
175
- atomic batch rather than composing the two public SDK calls sequentially.
176
- `capabilities()` marks these platforms with
177
- `requiresAtomicAccessPolicy: true`.
178
-
179
- `prepareAccessPolicy(depositId)` remains available only to recover a restricted
180
- deposit already created by `0.4.4`. Configure that existing deposit immediately;
181
- do not call `cashout()` again and do not use this recovery helper as a new-order
182
- flow.
131
+ Order reads fail closed against the same active catalog. If any method on an
132
+ indexed deposit is unsupported, `orders()` excludes the whole deposit and
133
+ `order()` returns `ORDER_NOT_FOUND`; Peer Cash never partially reclassifies a
134
+ mixed historical deposit.
135
+
136
+ ## Payout rails and access policies
137
+
138
+ | Payout rail | Access-policy behavior | New payee registration |
139
+ | --------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
140
+ | Venmo / Cash App | Four groups attach after deposit confirmation | Curator validates the live handle |
141
+ | PayPal | Same four-group follow-up | Requires a Peer TEE browser-extension identity attestation |
142
+ | Wise | No access-policy follow-up | Requires a Peer TEE browser-extension identity attestation |
143
+ | Other supported rails | No access-policy follow-up; use `capabilities()` for currencies and format | Follow the `payeeHint`; live-validation behavior is described in the integration guide |
144
+
145
+ No platform requires an atomic access-policy flow. `cashout()` and `prepare()`
146
+ work with any viem `WalletClient`, including a local or externally connected
147
+ EOA; no Privy wallet or signer API is required. The deprecated
148
+ `requiresAtomicAccessPolicy` capability remains for wire compatibility and is
149
+ always `false`.
150
+
151
+ Venmo, Cash App, and PayPal cash-outs restrict intent signaling to Plus, Pro,
152
+ Peer Makers, and Peer Pay groups by default. If any payout leg uses one of
153
+ those platforms, signed `cashout()` creates the deposit first, then uses the
154
+ same wallet to submit and confirm the policy transaction; this intentionally
155
+ leaves a brief non-atomic interval. Prepared integrations receive
156
+ `accessPolicyRequired: true` and, after confirming `createDeposit`, must call
157
+ `finalizePreparedCashout(receipt)` followed by
158
+ `prepareAccessPolicy(depositId)`. Other platforms do not need the follow-up.
159
+
160
+ If policy attachment fails, `ACCESS_POLICY_CONFIGURATION_FAILED.recovery`
161
+ identifies the existing deposit and any submitted policy transaction. Never
162
+ create another cash-out. When `recovery.transactionHash` is present, inspect
163
+ that transaction before resubmitting; otherwise prepare the policy again with
164
+ the same depositor wallet.
183
165
 
184
166
  `capabilities()` presents Zelle as one platform. A cashout with
185
167
  `receive.platform: 'zelle'` attaches only the generic Zelle payment method to
186
168
  the deposit. Bank-specific capture routing is outside this maker-side SDK and
187
169
  never changes the on-chain payment method.
188
170
 
189
- Order reads fail closed against the same active catalog. If any method on an
190
- indexed deposit is unsupported, `orders()` excludes the whole deposit and
191
- `order()` returns `ORDER_NOT_FOUND`; Peer Cash never partially reclassifies a
192
- mixed historical deposit.
171
+ `capabilities()` tells you which platforms need a verified identity for a new
172
+ payee registration (`requiresIdentityAttestation` - Wise and PayPal today).
173
+ The SDK accepts an `identityAttestation` in structured payee data but does not
174
+ mint one. First-party Peer web obtains it through the Peer TEE browser
175
+ extension. An already-registered Wise or PayPal handle can be reused with bare
176
+ payee data. A new handle without its signed attestation fails during curator
177
+ registration with `PAYEE_VERIFICATION_REQUIRED`, before funds move on-chain.
178
+
179
+ ## Source routing (any EVM asset in)
193
180
 
194
181
  The default/minimal flow is unchanged: pass Base USDC base units to
195
182
  `estimate()` and `cashout()`. For any other source asset, pass `source` to
@@ -201,6 +188,27 @@ The destination is always canonical Base USDC
201
188
  discovered and quoted by `@relayprotocol/relay-sdk`, not a static token
202
189
  allowlist.
203
190
 
191
+ ```ts
192
+ const { depositId, accessPolicyTxHash, source } = await cash.cashout(
193
+ {
194
+ amount: 10_000_000n, // exact input: 10 USDC in source-token base units
195
+ source: {
196
+ chainId: 1,
197
+ currency: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
198
+ tradeType: 'EXACT_INPUT',
199
+ },
200
+ receive: { platform: 'venmo', currency: 'USD', payee: { offchainId: '@you' } },
201
+ },
202
+ { signer, sourceSigner },
203
+ );
204
+
205
+ // source.amount is Relay's guaranteed minimum Base USDC output and the exact
206
+ // amount deposited into the cash-out order. It is not the route's actual output.
207
+ console.log(source?.amount, source?.requestId);
208
+ console.log(source?.transactions?.origin, source?.transactions?.destination);
209
+ console.log(accessPolicyTxHash); // present because this example uses Venmo
210
+ ```
211
+
204
212
  Routes that submit more than one source-chain transaction (approve, then
205
213
  route) require a nonce-managed source signer -
206
214
  `privateKeyToAccount(pk, { nonceManager })` from viem. Without one the SDK
@@ -208,12 +216,6 @@ refuses the route preflight with `SOURCE_NONCE_MANAGER_REQUIRED` instead of
208
216
  letting the route transaction reuse the approval's nonce and revert
209
217
  mid-route. Browser wallets are unaffected.
210
218
 
211
- `capabilities()` tells you which platforms need a verified identity for a new
212
- payee registration (`requiresIdentityAttestation` - Wise and PayPal today).
213
- An already-registered Wise or PayPal handle can be reused with bare payee data.
214
- A new handle without its signed attestation fails during curator registration
215
- with `PAYEE_VERIFICATION_REQUIRED`, before funds move on-chain.
216
-
217
219
  ## Source-route recovery
218
220
 
219
221
  Persist `depositId`, transaction hashes, and the Relay `requestId` as soon as
@@ -239,6 +241,11 @@ they are available. A source-routed result includes both a flat
239
241
  - `TRANSACTION_SUBMISSION_UNKNOWN`: a Base-only cashout or another mutation
240
242
  returned no hash. Treat it as potentially broadcast. Inspect recent Base
241
243
  wallet activity and the supplied recovery action before any retry.
244
+ - `ACCESS_POLICY_CONFIGURATION_FAILED`: the deposit exists, but its required
245
+ Venmo, Cash App, or PayPal policy was not confirmed. Do not cash out again;
246
+ inspect `recovery.transactionHash` when present, then retry
247
+ `prepareAccessPolicy(error.recovery.depositId)` only if the prior policy
248
+ transaction did not succeed.
242
249
 
243
250
  Wallet clients pinned to the wrong chain fail with `SIGNER_CHAIN_MISMATCH`
244
251
  before a quote or transaction is submitted. Chainless wallets are checked
@@ -285,6 +292,31 @@ awaiting-buyer ──────────► matched ───────
285
292
 
286
293
  Deep dive: [docs/lifecycle-and-recovery.md](docs/lifecycle-and-recovery.md).
287
294
 
295
+ ## Earn the integration share
296
+
297
+ Use the same six-character referral code shown in your Peer mobile or web app.
298
+ No API key, registration transaction, or separate receiving address is needed:
299
+ the referral code already belongs to your Peer Privy wallet.
300
+
301
+ ```ts
302
+ const cash = createCashClient({
303
+ environment: 'production',
304
+ referralCode: 'ABC123',
305
+ });
306
+ ```
307
+
308
+ The SDK normalizes the value and stamps `peer-ref-ABC123` into ERC-8021
309
+ attribution on the deposit transaction. When that liquidity is filled, Curator
310
+ pays the code owner 50 bps, capped by the configured Peer service fee. This is
311
+ the deposit-level integration path: it replaces the maker L1/L2 referral split
312
+ for that deposit instead of enrolling the cashing-out user as your referee.
313
+
314
+ The mapping is permanent. If you later customize your displayed Peer referral
315
+ code, open deposits carrying the old code still pay the same wallet. Include at
316
+ most one `peer-ref-XXXXXX` marker; an unknown or conflicting marker receives no
317
+ integration share. The existing `referrer` option remains available for
318
+ analytics-only ERC-8021 codes such as `acme-app`.
319
+
288
320
  ## For agents
289
321
 
290
322
  - `cashout`/`withdraw`/`topUp` have unsigned counterparts (`prepare`,
@@ -292,6 +324,9 @@ Deep dive: [docs/lifecycle-and-recovery.md](docs/lifecycle-and-recovery.md).
292
324
  before signing, then submit the matching `txs[]` in order.
293
325
  - Mutating tool calls return unsigned transactions by default; signing stays
294
326
  with the host that owns custody, policy, and user approval.
327
+ - After a prepared restricted cash-out confirms, the host adapter must call
328
+ `finalizePreparedCashout(receipt)` and `prepareAccessPolicy(depositId)`; these
329
+ receipt/signing operations are `CashClient` methods, not built-in tool calls.
295
330
  - Every error carries `code`, `retryable`, and a `remediation` sentence.
296
331
  - Every order carries `nextActions: ('wait' | 'withdraw')[]` - no heuristics.
297
332
  - Every wire type has a zod schema + JSON codec - state crosses process
@@ -322,12 +357,6 @@ defaults to `https://api-staging.zkp2p.xyz`. Indexer, curator, and Relay
322
357
  options remain overridable via `createCashClient` options. Base USDC on Base
323
358
  is the default source and the only destination asset for cashout orders.
324
359
 
325
- ## Install
326
-
327
- ```sh
328
- npm install @zkp2p/cash viem
329
- ```
330
-
331
360
  ## Examples
332
361
 
333
362
  Runnable first-party examples in [`examples/`](examples):
@@ -342,6 +371,13 @@ dependency currently ships from private source. Onchain custody is enforced by
342
371
  the protocol: only the contract holds funds, and only the maker can withdraw
343
372
  an unmatched deposit.
344
373
 
374
+ ## Contributing
375
+
376
+ [CLAUDE.md](https://github.com/zkp2p/peer-cash/blob/main/CLAUDE.md) is the
377
+ contributor guide: ground rules, repo layout, the `bun run ci` gate, and the
378
+ release/publish process. `AGENTS.md` is the shipped manual for agents using
379
+ the package, not the contributor entry point.
380
+
345
381
  ## License
346
382
 
347
383
  MIT
@@ -194,15 +194,16 @@ var errors = {
194
194
  code: "PAYEE_VERIFICATION_REQUIRED",
195
195
  message: `${platform} requires a verified maker identity attestation to register a payee; a bare handle is not accepted.`,
196
196
  retryable: false,
197
- 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.`
197
+ remediation: `Register this ${platform} payee through Peer web and its TEE browser extension, which produces the signed identity attestation, before cashing out. capabilities() flags such platforms with requiresIdentityAttestation: true.`
198
198
  },
199
199
  { cause }
200
200
  ),
201
+ /** @deprecated Cash-outs no longer require an atomic access-policy flow. */
201
202
  atomicAccessPolicyRequired: (platforms) => new CashError({
202
203
  code: "ATOMIC_ACCESS_POLICY_REQUIRED",
203
- message: `${platforms.join(", ")} cash-outs require atomic deposit creation and access-policy configuration.`,
204
+ message: `Atomic access-policy enforcement for ${platforms.join(", ")} is deprecated.`,
204
205
  retryable: false,
205
- remediation: `Use Peer web or a host that atomically batches guard-before, createDeposit, guard-after, and configureDeposit. Nothing was submitted by this call.`
206
+ remediation: `Upgrade @zkp2p/cash; current cash-out flows do not require atomic access-policy configuration.`
206
207
  }),
207
208
  sourceRouteUnsupportedInPrepare: () => new CashError({
208
209
  code: "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE",
@@ -405,20 +406,28 @@ var errors = {
405
406
  },
406
407
  { cause }
407
408
  ),
408
- accessPolicyConfigurationFailed: (depositId, groupIds, cause, transactionHash) => new CashError(
409
+ accessPolicyConfigurationFailed: (depositId, groupIds, context = {}) => new CashError(
409
410
  {
410
411
  code: "ACCESS_POLICY_CONFIGURATION_FAILED",
411
- message: `Cash-out deposit ${depositId} was created, but its required access policy was not confirmed.`,
412
+ message: `Cash-out deposit ${depositId} was created, but its access policy could not be confirmed.`,
412
413
  retryable: false,
413
- remediation: `Do not call cashout() again. Inspect the existing deposit and any access-policy transaction, then configure that deposit with recovery.groupIds through @zkp2p/sdk accessPolicy.`,
414
+ remediation: `Do not create another cash-out. If recovery.transactionHash is present, inspect that policy transaction first. Otherwise, or if it is confirmed reverted, submit and confirm prepareAccessPolicy(recovery.depositId) with the same depositor wallet.`,
414
415
  recovery: {
415
416
  kind: "configure-cashout-access-policy",
416
417
  depositId,
417
418
  groupIds: [...groupIds],
418
- ...transactionHash ? { transactionHash } : {}
419
+ ...context.transactionHash ? { transactionHash: context.transactionHash } : {},
420
+ ...context.source ? {
421
+ source: {
422
+ amount: context.source.amount.toString(),
423
+ ...context.source.requestId ? { requestId: context.source.requestId } : {},
424
+ txHashes: context.source.txHashes,
425
+ ...context.source.transactions ? { transactions: context.source.transactions } : {}
426
+ }
427
+ } : {}
419
428
  }
420
429
  },
421
- { cause }
430
+ { cause: context.cause }
422
431
  ),
423
432
  escrowPaused: () => new CashError({
424
433
  code: "ESCROW_PAUSED",
@@ -306,14 +306,15 @@ interface CashPlatformCapability {
306
306
  payeeHint: string;
307
307
  /**
308
308
  * When true, registering a payee for this platform requires a signed maker
309
- * identity attestation the SDK cannot produce - register the payee via the
310
- * ZKP2P app/extension first. A bare-handle `cashout()` throws
309
+ * identity attestation the SDK cannot produce. First-party Peer web obtains
310
+ * it through the Peer TEE browser extension. Existing registrations can be
311
+ * reused with bare payee data; a new bare handle throws
311
312
  * `PAYEE_VERIFICATION_REQUIRED`.
312
313
  */
313
314
  requiresIdentityAttestation: boolean;
314
315
  /**
315
- * When true, the platform needs deposit creation and access-policy setup in
316
- * one atomic host operation. Generic `cashout()` and `prepare()` fail closed.
316
+ * @deprecated Always false. This does not report the sequential restricted-
317
+ * platform policy; prepared hosts must inspect `PrepareResult.accessPolicyRequired`.
317
318
  */
318
319
  requiresAtomicAccessPolicy: boolean;
319
320
  }
@@ -549,7 +550,7 @@ interface CashoutInput {
549
550
  };
550
551
  }
551
552
  interface SignerOptions {
552
- /** A viem WalletClient with an account, on Base. */
553
+ /** Any viem WalletClient with a Base account, including a local or external EOA. */
553
554
  signer: WalletClient;
554
555
  }
555
556
  interface CashoutOptions extends SignerOptions {
@@ -587,7 +588,7 @@ interface CashoutResult {
587
588
  onchainDepositId: bigint;
588
589
  /** Optimistic snapshot (`awaiting-buyer`); poll `order(depositId)` for live state. */
589
590
  order: CashOrder;
590
- /** @deprecated Only present in recovery data produced by the unsafe 0.4.4 sequential flow. */
591
+ /** Confirmed access-policy transaction when any payout leg is Venmo, Cash App, or PayPal. */
591
592
  accessPolicyTxHash?: Hash;
592
593
  /** Present when `cashout()` first routed a source asset through Relay. */
593
594
  source?: {
@@ -615,7 +616,7 @@ interface PrepareResult {
615
616
  register: {
616
617
  hashedOnchainIds: string[];
617
618
  };
618
- /** @deprecated Always false for new plans; restricted rails require an atomic host flow. */
619
+ /** Whether the host must submit and confirm the policy after `createDeposit` confirms. */
619
620
  accessPolicyRequired: boolean;
620
621
  }
621
622
  /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
@@ -679,7 +680,7 @@ interface CashClient {
679
680
  prepare(input: CashoutInput): Promise<PrepareResult>;
680
681
  /** Resolve an externally executed createDeposit receipt into resumable cash-out state. */
681
682
  finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
682
- /** Recover an existing 0.4.4 restricted-rail deposit by preparing its four-group policy. */
683
+ /** Prepare the required four-group follow-up for a restricted cash-out. */
683
684
  prepareAccessPolicy(depositId: string): PreparedTransaction;
684
685
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
685
686
  order(depositId: string): Promise<CashOrder>;
@@ -306,14 +306,15 @@ interface CashPlatformCapability {
306
306
  payeeHint: string;
307
307
  /**
308
308
  * When true, registering a payee for this platform requires a signed maker
309
- * identity attestation the SDK cannot produce - register the payee via the
310
- * ZKP2P app/extension first. A bare-handle `cashout()` throws
309
+ * identity attestation the SDK cannot produce. First-party Peer web obtains
310
+ * it through the Peer TEE browser extension. Existing registrations can be
311
+ * reused with bare payee data; a new bare handle throws
311
312
  * `PAYEE_VERIFICATION_REQUIRED`.
312
313
  */
313
314
  requiresIdentityAttestation: boolean;
314
315
  /**
315
- * When true, the platform needs deposit creation and access-policy setup in
316
- * one atomic host operation. Generic `cashout()` and `prepare()` fail closed.
316
+ * @deprecated Always false. This does not report the sequential restricted-
317
+ * platform policy; prepared hosts must inspect `PrepareResult.accessPolicyRequired`.
317
318
  */
318
319
  requiresAtomicAccessPolicy: boolean;
319
320
  }
@@ -549,7 +550,7 @@ interface CashoutInput {
549
550
  };
550
551
  }
551
552
  interface SignerOptions {
552
- /** A viem WalletClient with an account, on Base. */
553
+ /** Any viem WalletClient with a Base account, including a local or external EOA. */
553
554
  signer: WalletClient;
554
555
  }
555
556
  interface CashoutOptions extends SignerOptions {
@@ -587,7 +588,7 @@ interface CashoutResult {
587
588
  onchainDepositId: bigint;
588
589
  /** Optimistic snapshot (`awaiting-buyer`); poll `order(depositId)` for live state. */
589
590
  order: CashOrder;
590
- /** @deprecated Only present in recovery data produced by the unsafe 0.4.4 sequential flow. */
591
+ /** Confirmed access-policy transaction when any payout leg is Venmo, Cash App, or PayPal. */
591
592
  accessPolicyTxHash?: Hash;
592
593
  /** Present when `cashout()` first routed a source asset through Relay. */
593
594
  source?: {
@@ -615,7 +616,7 @@ interface PrepareResult {
615
616
  register: {
616
617
  hashedOnchainIds: string[];
617
618
  };
618
- /** @deprecated Always false for new plans; restricted rails require an atomic host flow. */
619
+ /** Whether the host must submit and confirm the policy after `createDeposit` confirms. */
619
620
  accessPolicyRequired: boolean;
620
621
  }
621
622
  /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
@@ -679,7 +680,7 @@ interface CashClient {
679
680
  prepare(input: CashoutInput): Promise<PrepareResult>;
680
681
  /** Resolve an externally executed createDeposit receipt into resumable cash-out state. */
681
682
  finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
682
- /** Recover an existing 0.4.4 restricted-rail deposit by preparing its four-group policy. */
683
+ /** Prepare the required four-group follow-up for a restricted cash-out. */
683
684
  prepareAccessPolicy(depositId: string): PreparedTransaction;
684
685
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
685
686
  order(depositId: string): Promise<CashOrder>;