@zkp2p/cash 0.5.3 → 0.5.4-rc.2
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 +14 -5
- package/README.md +32 -23
- package/dist/{createCashClient-DimDWJU4.d.cts → createCashClient-ECL3HN5H.d.cts} +6 -2
- package/dist/{createCashClient-DimDWJU4.d.ts → createCashClient-ECL3HN5H.d.ts} +6 -2
- package/dist/index.cjs +70 -49
- package/dist/index.d.cts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +71 -49
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/tools.cjs +1 -1
- package/dist/tools.js +1 -1
- package/examples/onchain-demo/package.json +1 -1
- package/examples/upi-staging-cashout.ts +8 -3
- package/llms.txt +14 -4
- package/package.json +2 -2
- package/skills/peer-cash-integration/SKILL.md +25 -5
- package/skills/upi-preprod-qa/SKILL.md +100 -0
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,7 @@ You are integrating Peer Cash: an offramp that routes Relay-supported EVM
|
|
|
8
8
|
assets or NEAR Intents 1Click external deposits into Base USDC, then converts
|
|
9
9
|
Base USDC to fiat (Venmo, Revolut, Wise, Alipay, Zelle, ...) at a zero-spread
|
|
10
10
|
Chainlink market rate. Existing corridors bind at intent signal; Alipay/CNY
|
|
11
|
-
fixes a fresh Ethereum Chainlink snapshot during deposit preparation. The user whose USDC you
|
|
11
|
+
fixes a fresh Ethereum Chainlink snapshot during deposit preparation; opt-in UPI/INR uses Polygon Chainlink. The user whose USDC you
|
|
12
12
|
manage is the **maker**; a buyer pays them fiat and proves it with TEE-TLS; the
|
|
13
13
|
protocol releases the USDC. Funds are held by the protocol, and only the maker
|
|
14
14
|
can withdraw an unmatched deposit.
|
|
@@ -70,10 +70,10 @@ deposit-level integration share instead of applying maker L1/L2.
|
|
|
70
70
|
- **Venmo, Revolut, Cash App, Monzo** validate the handle against the live
|
|
71
71
|
platform at registration - the account must exist. The rest (Zelle, Chime,
|
|
72
72
|
etc.) are format-checked only. Match handles to the `payeeHint`.
|
|
73
|
-
- **UPI is staging-only and opt-in.** Construct the client with
|
|
73
|
+
- **UPI is staging/preproduction-only and opt-in.** Construct the client with
|
|
74
74
|
`features: { upi: true }`. Any valid UPI ID from any bank can receive; there
|
|
75
75
|
is no seller bank login, extension, identity attestation, or prior
|
|
76
|
-
registration flow. Buyers
|
|
76
|
+
registration flow. Buyers pay and verify through Amazon Pay using standard UPI.
|
|
77
77
|
|
|
78
78
|
## The loop
|
|
79
79
|
|
|
@@ -196,8 +196,8 @@ const route = await cash.nearIntentsStatus({
|
|
|
196
196
|
|
|
197
197
|
- **Respect the declared binding point.** `estimate()` is
|
|
198
198
|
`kind: 'oracle-estimate'`. Its `binding` is `intent-signal` for existing
|
|
199
|
-
on-chain oracle corridors and `deposit-creation` for Alipay/CNY. Do not call
|
|
200
|
-
an estimate locked before that point. Once
|
|
199
|
+
on-chain oracle corridors and `deposit-creation` for Alipay/CNY and UPI/INR. Do not call
|
|
200
|
+
an estimate locked before that point. Once a creation-time corridor is prepared, its fresh
|
|
201
201
|
Chainlink snapshot is the on-chain maker floor.
|
|
202
202
|
- **Do not invent an ETA.** Use `estimate().eta`: `{ seconds, label }` backed
|
|
203
203
|
by the same rolling 30-day, intent-attributed pair sample as `fillStats()`,
|
|
@@ -334,3 +334,12 @@ wallet. Never wait on a buyer - buyer-side is out of your scope:
|
|
|
334
334
|
|
|
335
335
|
If step 4 ever fails with funds stuck, stop and escalate - do not retry
|
|
336
336
|
blindly.
|
|
337
|
+
|
|
338
|
+
UPI/INR reads the live Chainlink Polygon mainnet proxy
|
|
339
|
+
`0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60` (chain 137), inverts
|
|
340
|
+
USD per INR, and rounds the creation-time maker floor up. Configure its
|
|
341
|
+
read-only RPC with `upiCreationRateRpcUrl` or `upiCreationRateTransport`.
|
|
342
|
+
Alipay/CNY retains the Ethereum registry and `creationRateRpcUrl` /
|
|
343
|
+
`creationRateTransport`. UPI rejects the wrong chain, invalid rounds, and
|
|
344
|
+
observations older than 24 hours; market closures do not bypass freshness.
|
|
345
|
+
UPI remains opt-in on staging/preproduction and disabled in production.
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Route Relay-supported EVM assets or NEAR Intents 1Click external deposits into
|
|
|
4
4
|
Base USDC, then cash out to fiat on Venmo, Revolut, Wise, Alipay, Zelle, and
|
|
5
5
|
more at a zero-spread Chainlink market rate with no centralized off-ramp
|
|
6
6
|
provider. Existing corridors bind the live oracle when a buyer signals;
|
|
7
|
-
Alipay/CNY
|
|
8
|
-
deposit.
|
|
7
|
+
Alipay/CNY and opt-in UPI/INR fix fresh Chainlink snapshots when the SDK prepares
|
|
8
|
+
the deposit (Ethereum for CNY; Polygon for INR).
|
|
9
9
|
|
|
10
10
|
Peer Cash is an **offramp-only** SDK for the [ZKP2P](https://peer.xyz)
|
|
11
11
|
protocol. The cashing-out user is the maker: their USDC becomes a deposit in
|
|
@@ -94,16 +94,18 @@ for await (const order of cash.watch(depositId)) {
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
### Staging UPI cash-out
|
|
97
|
+
### Staging and preproduction UPI cash-out
|
|
98
98
|
|
|
99
|
-
UPI
|
|
100
|
-
|
|
99
|
+
UPI requires the canonical UPI/INR catalog from `@zkp2p/sdk` 0.14.2-rc.1
|
|
100
|
+
or its approved successor; a missing catalog entry keeps the corridor disabled.
|
|
101
|
+
UPI is an opt-in staging and preproduction corridor. Production remains disabled
|
|
102
|
+
pending rollout review. Any valid UPI ID from any bank can receive a cash-out. The seller
|
|
101
103
|
does not connect a bank account, install an extension, or complete a separate
|
|
102
104
|
registration flow:
|
|
103
105
|
|
|
104
106
|
```ts
|
|
105
107
|
const cash = createCashClient({
|
|
106
|
-
environment: '
|
|
108
|
+
environment: 'preproduction',
|
|
107
109
|
features: { upi: true },
|
|
108
110
|
});
|
|
109
111
|
|
|
@@ -116,18 +118,25 @@ await cash.cashout(
|
|
|
116
118
|
);
|
|
117
119
|
```
|
|
118
120
|
|
|
119
|
-
Buyers
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
Buyers pay and verify through Amazon Pay using standard UPI. UPI Lite and
|
|
122
|
+
merchant payments are unsupported. The seller may receive at a valid UPI ID
|
|
123
|
+
from any bank.
|
|
124
|
+
|
|
125
|
+
INR pricing uses the [Chainlink Polygon INR/USD feed](https://data.chain.link/feeds/polygon/mainnet/inr-usd),
|
|
126
|
+
inverted into INR per USDC and fixed at deposit preparation. The SDK rejects
|
|
127
|
+
invalid rounds and readings older than 24 hours, including market-hour gaps.
|
|
128
|
+
Only oracle reads use Polygon; funds and transactions stay on Base. Override
|
|
129
|
+
the Polygon reader with `upiCreationRateTransport` or `upiCreationRateRpcUrl`.
|
|
130
|
+
The existing `creationRateTransport`/`creationRateRpcUrl` options remain Ethereum-only for CNY.
|
|
122
131
|
|
|
123
132
|
## Pick the right SDK
|
|
124
133
|
|
|
125
134
|
Peer Cash and the general ZKP2P SDK serve different integration depths:
|
|
126
135
|
|
|
127
|
-
| Package | Use it when | Boundary
|
|
128
|
-
| ------------- | ------------------------------------------------- |
|
|
129
|
-
| `@zkp2p/cash` | Cash-out is the product | Offramp only. The user is always the maker, the destination is Base USDC, pricing is zero-spread Chainlink (signal-time by default; creation-time for Alipay/CNY), and the SDK owns the resumable order lifecycle. |
|
|
130
|
-
| `@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.
|
|
136
|
+
| Package | Use it when | Boundary |
|
|
137
|
+
| ------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
138
|
+
| `@zkp2p/cash` | Cash-out is the product | Offramp only. The user is always the maker, the destination is Base USDC, pricing is zero-spread Chainlink (signal-time by default; creation-time for Alipay/CNY and UPI/INR), and the SDK owns the resumable order lifecycle. |
|
|
139
|
+
| `@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. |
|
|
131
140
|
|
|
132
141
|
Peer Cash is a narrow facade over `@zkp2p/sdk`, not a replacement for it. It
|
|
133
142
|
cannot express custom spreads, buyer-side proof flows, vaults, disputes, or
|
|
@@ -177,14 +186,14 @@ mixed historical deposit.
|
|
|
177
186
|
|
|
178
187
|
## Payout rails and access policies
|
|
179
188
|
|
|
180
|
-
| Payout rail
|
|
181
|
-
|
|
|
182
|
-
| Venmo
|
|
183
|
-
| PayPal
|
|
184
|
-
| Cash App
|
|
185
|
-
| Wise
|
|
186
|
-
| UPI (staging opt-in)
|
|
187
|
-
| Other supported rails
|
|
189
|
+
| Payout rail | Access-policy behavior | New payee registration |
|
|
190
|
+
| ---------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
191
|
+
| Venmo | Peer Pay merchant policy attaches for that payment method | Curator validates the live handle |
|
|
192
|
+
| PayPal | Same method-scoped Peer Pay follow-up | Requires a Peer TEE browser-extension identity attestation |
|
|
193
|
+
| Cash App | No access-policy follow-up; non-chargebackable and no stake required | Curator validates the live handle |
|
|
194
|
+
| Wise | No access-policy follow-up | Requires a Peer TEE browser-extension identity attestation |
|
|
195
|
+
| UPI (staging/preprod opt-in) | No access-policy follow-up | Any valid UPI ID; no account connection or identity attestation |
|
|
196
|
+
| 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 |
|
|
188
197
|
|
|
189
198
|
No platform requires an atomic access-policy flow. `cashout()` and `prepare()`
|
|
190
199
|
work with any viem `WalletClient`, including a local or externally connected
|
|
@@ -380,7 +389,7 @@ awaiting-buyer ──────────► matched ───────
|
|
|
380
389
|
resulting CNY-per-USDC maker floor when it prepares the deposit. A buyer may
|
|
381
390
|
signal at that floor or a better rate for the maker.
|
|
382
391
|
- **Read `binding`.** `estimate().binding` is `intent-signal` by default and
|
|
383
|
-
`deposit-creation` for Alipay/CNY. An estimate remains approximate until its
|
|
392
|
+
`deposit-creation` for Alipay/CNY and UPI/INR. An estimate remains approximate until its
|
|
384
393
|
stated binding point.
|
|
385
394
|
- **ETA is historical.** `estimate().eta` is just `{ seconds, label }`, backed
|
|
386
395
|
by the same rolling 30-day, intent-attributed pair sampler as `fillStats()`,
|
|
@@ -474,7 +483,7 @@ is the default source and the only destination asset for cashout orders.
|
|
|
474
483
|
Runnable first-party examples in [`examples/`](examples):
|
|
475
484
|
|
|
476
485
|
- [`node-cashout.ts`](examples/node-cashout.ts) - server-side cash-out with a private-key signer, plus order tracking.
|
|
477
|
-
- [`upi-staging-cashout.ts`](examples/upi-staging-cashout.ts) - opt-in UPI/INR cash-out to any valid UPI ID on staging.
|
|
486
|
+
- [`upi-staging-cashout.ts`](examples/upi-staging-cashout.ts) - opt-in UPI/INR cash-out to any valid UPI ID on staging or preproduction.
|
|
478
487
|
- [`agent-tool-use.ts`](examples/agent-tool-use.ts) - wiring the verbs into an agent tool-use loop with host-side signing.
|
|
479
488
|
- [`carpe-diem-provider-cashout`](examples/carpe-diem-provider-cashout) - cash out confirmed Carpe Diem provider DIEM revenue through the connected Base wallet.
|
|
480
489
|
- [`mpp-merchant-cashout`](examples/mpp-merchant-cashout) - turn confirmed MPP merchant revenue into an unsigned Peer Cash plan while the merchant keeps custody and signing.
|
|
@@ -585,8 +585,8 @@ interface CashFillEta {
|
|
|
585
585
|
* idempotent, cacheable.
|
|
586
586
|
*
|
|
587
587
|
* Existing corridors read the same Chainlink feed the protocol uses when an
|
|
588
|
-
* intent is signaled. Creation-rate corridors read Chainlink
|
|
589
|
-
*
|
|
588
|
+
* intent is signaled. Creation-rate corridors read Chainlink on Ethereum (CNY)
|
|
589
|
+
* or Polygon (INR), fixing the fresh snapshot when preparing the deposit.
|
|
590
590
|
*/
|
|
591
591
|
|
|
592
592
|
interface EstimateInput {
|
|
@@ -692,6 +692,10 @@ interface CashClientOptions {
|
|
|
692
692
|
creationRateTransport?: Transport;
|
|
693
693
|
/** Convenience alternative to `creationRateTransport`. */
|
|
694
694
|
creationRateRpcUrl?: string;
|
|
695
|
+
/** Polygon transport used only to snapshot UPI/INR's creation-time rate. */
|
|
696
|
+
upiCreationRateTransport?: Transport;
|
|
697
|
+
/** Convenience alternative to `upiCreationRateTransport`; requires Polygon mainnet. */
|
|
698
|
+
upiCreationRateRpcUrl?: string;
|
|
695
699
|
/** Relay API configuration for source assets outside Base USDC. */
|
|
696
700
|
relay?: RelayOptions;
|
|
697
701
|
/** NEAR Intents 1Click configuration for externally funded source routes. */
|
|
@@ -585,8 +585,8 @@ interface CashFillEta {
|
|
|
585
585
|
* idempotent, cacheable.
|
|
586
586
|
*
|
|
587
587
|
* Existing corridors read the same Chainlink feed the protocol uses when an
|
|
588
|
-
* intent is signaled. Creation-rate corridors read Chainlink
|
|
589
|
-
*
|
|
588
|
+
* intent is signaled. Creation-rate corridors read Chainlink on Ethereum (CNY)
|
|
589
|
+
* or Polygon (INR), fixing the fresh snapshot when preparing the deposit.
|
|
590
590
|
*/
|
|
591
591
|
|
|
592
592
|
interface EstimateInput {
|
|
@@ -692,6 +692,10 @@ interface CashClientOptions {
|
|
|
692
692
|
creationRateTransport?: Transport;
|
|
693
693
|
/** Convenience alternative to `creationRateTransport`. */
|
|
694
694
|
creationRateRpcUrl?: string;
|
|
695
|
+
/** Polygon transport used only to snapshot UPI/INR's creation-time rate. */
|
|
696
|
+
upiCreationRateTransport?: Transport;
|
|
697
|
+
/** Convenience alternative to `upiCreationRateTransport`; requires Polygon mainnet. */
|
|
698
|
+
upiCreationRateRpcUrl?: string;
|
|
695
699
|
/** Relay API configuration for source assets outside Base USDC. */
|
|
696
700
|
relay?: RelayOptions;
|
|
697
701
|
/** NEAR Intents 1Click configuration for externally funded source routes. */
|
package/dist/index.cjs
CHANGED
|
@@ -29,12 +29,14 @@ var CASH_ORDER_STATUSES = [
|
|
|
29
29
|
];
|
|
30
30
|
var CASH_ORDER_POLL_INTERVAL_MS = 5e3;
|
|
31
31
|
var CASH_RETAIN_ON_EMPTY = false;
|
|
32
|
-
|
|
33
|
-
// src/client/creationRate.ts
|
|
34
32
|
var CHAINLINK_FEED_REGISTRY = "0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf";
|
|
35
33
|
var CNY_DENOMINATION = "0x000000000000000000000000000000000000009c";
|
|
36
|
-
var INR_DENOMINATION = "0x0000000000000000000000000000000000000164";
|
|
37
34
|
var USD_DENOMINATION = "0x0000000000000000000000000000000000000348";
|
|
35
|
+
var POLYGON_INR_USD_FEED = "0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60";
|
|
36
|
+
var DIRECT_FEED_ABI = viem.parseAbi([
|
|
37
|
+
"function decimals() view returns (uint8)",
|
|
38
|
+
"function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)"
|
|
39
|
+
]);
|
|
38
40
|
var FEED_REGISTRY_ABI = [
|
|
39
41
|
{
|
|
40
42
|
name: "decimals",
|
|
@@ -73,74 +75,85 @@ function getCreationRateDenomination(platform, currency) {
|
|
|
73
75
|
if (platform.toLowerCase() === "alipay" && currency.toUpperCase() === "CNY") {
|
|
74
76
|
return CNY_DENOMINATION;
|
|
75
77
|
}
|
|
76
|
-
if (platform.toLowerCase() === "upi" && currency.toUpperCase() === "INR") {
|
|
77
|
-
return INR_DENOMINATION;
|
|
78
|
-
}
|
|
79
78
|
throw new Error(`No creation-time rate source for ${platform}/${currency}`);
|
|
80
79
|
}
|
|
81
80
|
function divideRoundUp(numerator, denominator) {
|
|
82
81
|
return (numerator + denominator - 1n) / denominator;
|
|
83
82
|
}
|
|
84
83
|
async function readCashCreationRate(publicClient, platform, currency, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
publicClient.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
const isUpi = platform.toLowerCase() === "upi" && currency.toUpperCase() === "INR";
|
|
85
|
+
const pair = `${currency.toUpperCase()}/USD`;
|
|
86
|
+
let decimals;
|
|
87
|
+
let round;
|
|
88
|
+
if (isUpi) {
|
|
89
|
+
if (await publicClient.getChainId() !== 137) {
|
|
90
|
+
throw new Error("Chainlink INR/USD requires Polygon mainnet (137)");
|
|
91
|
+
}
|
|
92
|
+
[decimals, round] = await Promise.all([
|
|
93
|
+
publicClient.readContract({
|
|
94
|
+
address: POLYGON_INR_USD_FEED,
|
|
95
|
+
abi: DIRECT_FEED_ABI,
|
|
96
|
+
functionName: "decimals"
|
|
97
|
+
}),
|
|
98
|
+
publicClient.readContract({
|
|
99
|
+
address: POLYGON_INR_USD_FEED,
|
|
100
|
+
abi: DIRECT_FEED_ABI,
|
|
101
|
+
functionName: "latestRoundData"
|
|
102
|
+
})
|
|
103
|
+
]);
|
|
104
|
+
} else {
|
|
105
|
+
const args = [
|
|
106
|
+
getCreationRateDenomination(platform, currency),
|
|
107
|
+
USD_DENOMINATION
|
|
108
|
+
];
|
|
109
|
+
[decimals, round] = await Promise.all([
|
|
110
|
+
publicClient.readContract({
|
|
111
|
+
address: CHAINLINK_FEED_REGISTRY,
|
|
112
|
+
abi: FEED_REGISTRY_ABI,
|
|
113
|
+
functionName: "decimals",
|
|
114
|
+
args
|
|
115
|
+
}),
|
|
116
|
+
publicClient.readContract({
|
|
117
|
+
address: CHAINLINK_FEED_REGISTRY,
|
|
118
|
+
abi: FEED_REGISTRY_ABI,
|
|
119
|
+
functionName: "latestRoundData",
|
|
120
|
+
args
|
|
121
|
+
})
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
103
124
|
const [roundId, answer, , updatedAtRaw, answeredInRound] = round;
|
|
104
125
|
if (answer <= 0n || updatedAtRaw <= 0n || answeredInRound < roundId) {
|
|
105
|
-
throw new Error(
|
|
126
|
+
throw new Error(`Chainlink ${pair} returned an invalid round`);
|
|
106
127
|
}
|
|
107
128
|
const updatedAt = Number(updatedAtRaw);
|
|
108
129
|
if (!Number.isSafeInteger(updatedAt) || updatedAt > nowSeconds) {
|
|
109
|
-
throw new Error(
|
|
130
|
+
throw new Error(`Chainlink ${pair} returned an invalid timestamp`);
|
|
110
131
|
}
|
|
111
132
|
if (nowSeconds - updatedAt > CREATION_RATE_MAX_STALENESS_SECONDS) {
|
|
112
|
-
throw new Error(
|
|
133
|
+
throw new Error(`Chainlink ${pair} rate is stale`);
|
|
113
134
|
}
|
|
114
135
|
const rate1e18 = divideRoundUp(10n ** (BigInt(decimals) + 18n), answer);
|
|
115
136
|
const rate = Number(rate1e18) / 1e18;
|
|
116
137
|
if (!Number.isFinite(rate) || rate <= 0) {
|
|
117
|
-
throw new Error(
|
|
138
|
+
throw new Error(`Chainlink ${pair} produced an invalid creation rate`);
|
|
118
139
|
}
|
|
119
140
|
return { rate1e18, rate, updatedAt };
|
|
120
141
|
}
|
|
121
142
|
async function readAlipayCnyCreationRate(publicClient, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
122
143
|
return readCashCreationRate(publicClient, "alipay", "CNY", nowSeconds);
|
|
123
144
|
}
|
|
124
|
-
var UPI_STAGING_PAYMENT_METHOD_HASH = "0xe99a5081226cbbff9440a63da5caa04fa30f210c12c4dd9976132ac075054cd9";
|
|
125
145
|
function getCashPaymentMethodsCatalog(environment, features = {}) {
|
|
126
146
|
const catalog = sdk.getPaymentMethodsCatalog(8453, environment);
|
|
127
|
-
if (environment
|
|
147
|
+
if ((environment === "staging" || environment === "preproduction") && features.upi === true) {
|
|
128
148
|
return catalog;
|
|
129
149
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
paymentMethodHash: UPI_STAGING_PAYMENT_METHOD_HASH,
|
|
134
|
-
currencies: [sdk.currencyInfo.INR.currencyCodeHash],
|
|
135
|
-
timestampBuffer: 30,
|
|
136
|
-
providerHashes: []
|
|
137
|
-
}
|
|
138
|
-
};
|
|
150
|
+
const enabledCatalog = { ...catalog };
|
|
151
|
+
delete enabledCatalog.upi;
|
|
152
|
+
return enabledCatalog;
|
|
139
153
|
}
|
|
140
154
|
function resolveCashPaymentMethodHash(processorName, catalog) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return catalog.upi.paymentMethodHash;
|
|
155
|
+
if (processorName.trim().toLowerCase() === "upi" && !catalog.upi) {
|
|
156
|
+
throw new Error("UPI is not enabled in this environment catalog");
|
|
144
157
|
}
|
|
145
158
|
return sdk.resolvePaymentMethodHashFromCatalog(processorName, catalog);
|
|
146
159
|
}
|
|
@@ -644,9 +657,7 @@ function buildCapabilities(environment, features = {}) {
|
|
|
644
657
|
requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform),
|
|
645
658
|
requiresAtomicAccessPolicy: false
|
|
646
659
|
};
|
|
647
|
-
}).filter(
|
|
648
|
-
(p) => p.currencies.length > 0 && (p.platform !== "upi" || environment === "staging" && features.upi === true)
|
|
649
|
-
).sort((a, b) => a.platform.localeCompare(b.platform));
|
|
660
|
+
}).filter((p) => p.currencies.length > 0).sort((a, b) => a.platform.localeCompare(b.platform));
|
|
650
661
|
const currencies = [...new Set(platforms.flatMap((p) => p.currencies))].sort();
|
|
651
662
|
const baseUsdc = { address: BASE_USDC_ADDRESS, symbol: "USDC", decimals: USDC_DECIMALS };
|
|
652
663
|
return {
|
|
@@ -1799,10 +1810,11 @@ async function readEstimate(publicClient, input, context = {}) {
|
|
|
1799
1810
|
let rate;
|
|
1800
1811
|
let oracleUpdatedAt;
|
|
1801
1812
|
if (usesCreationRate) {
|
|
1802
|
-
|
|
1813
|
+
const rateClient = creationRatePlatform.toLowerCase() === "upi" ? context.upiCreationRateClient : context.creationRateClient;
|
|
1814
|
+
if (!rateClient) throw errors.oracleUnsupportedCurrency(currency);
|
|
1803
1815
|
try {
|
|
1804
1816
|
const snapshot = await readCashCreationRate(
|
|
1805
|
-
|
|
1817
|
+
rateClient,
|
|
1806
1818
|
creationRatePlatform,
|
|
1807
1819
|
currency,
|
|
1808
1820
|
asOf
|
|
@@ -2830,7 +2842,7 @@ function createCashAttributionReader(options) {
|
|
|
2830
2842
|
// src/client/classify.ts
|
|
2831
2843
|
function isCashPayoutSet(payouts, attributedToCash = false) {
|
|
2832
2844
|
return payouts.length > 0 && payouts.every(
|
|
2833
|
-
(payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && payout.platform
|
|
2845
|
+
(payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && isCreationRateCorridor(payout.platform, payout.currency ?? "") && payout.pricing.fixedAtCreation === true && (payout.pricing.fixedRate ?? 0) > 0
|
|
2834
2846
|
);
|
|
2835
2847
|
}
|
|
2836
2848
|
|
|
@@ -2934,6 +2946,10 @@ function createCashClient(options) {
|
|
|
2934
2946
|
chain: chains.mainnet,
|
|
2935
2947
|
transport: creationRateTransport
|
|
2936
2948
|
});
|
|
2949
|
+
const upiCreationRateClient = viem.createPublicClient({
|
|
2950
|
+
chain: chains.polygon,
|
|
2951
|
+
transport: options.upiCreationRateTransport ?? viem.http(options.upiCreationRateRpcUrl)
|
|
2952
|
+
});
|
|
2937
2953
|
const readCashAttribution = createCashAttributionReader({
|
|
2938
2954
|
environment,
|
|
2939
2955
|
...options.indexerUrl ? { indexerUrl: options.indexerUrl } : {},
|
|
@@ -3068,7 +3084,11 @@ function createCashClient(options) {
|
|
|
3068
3084
|
throw new Error(`No creation-time rate reader for ${platform}/${currency}`);
|
|
3069
3085
|
}
|
|
3070
3086
|
try {
|
|
3071
|
-
return await readCashCreationRate(
|
|
3087
|
+
return await readCashCreationRate(
|
|
3088
|
+
platform.toLowerCase() === "upi" ? upiCreationRateClient : creationRateClient,
|
|
3089
|
+
platform,
|
|
3090
|
+
currency
|
|
3091
|
+
);
|
|
3072
3092
|
} catch (err) {
|
|
3073
3093
|
throw errors.oracleReadFailed(currency, err);
|
|
3074
3094
|
}
|
|
@@ -3446,6 +3466,7 @@ function createCashClient(options) {
|
|
|
3446
3466
|
...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
|
|
3447
3467
|
etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
|
|
3448
3468
|
creationRateClient,
|
|
3469
|
+
upiCreationRateClient,
|
|
3449
3470
|
...options.relay ? { relay: options.relay } : {}
|
|
3450
3471
|
});
|
|
3451
3472
|
},
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CashPayoutInfo, I as IntentEntity, a as CashBuyerProfile, b as CashDepositInput, c as CashCatalogFeatures, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, N as NearIntentsSourceCapabilities, k as NearIntentsDepositInput, l as NearIntentsQuote, m as NearIntentsQuoteInput, n as NearIntentsStatus, o as NearIntentsStatusInput, P as PrepareResult, p as CashPreparedStep, R as RelayExecutionResult, q as RelayQuote, r as RelayStatus, s as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-
|
|
2
|
-
export { B as BASE_CHAIN_ID, t as BASE_USDC_ADDRESS, u as CASH_ATTRIBUTION_CODE, v as CASH_ORDER_POLL_INTERVAL_MS, w as CASH_ORDER_STATUSES, x as CASH_REFERRAL_ATTRIBUTION_PREFIX, y as CASH_RETAIN_ON_EMPTY, z as CashAsset, A as CashChain, D as CashClient, E as CashClientOptions, F as CashCorridorPricing, G as CashFeatureFlags, H as CashFillEta, J as CashLeg, K as CashMultiCurrencyLeg, L as CashNextAction, M as CashOrderState, O as CashPairFillStats, Q as CashPayeeInput, S as CashPayout, U as CashPayoutPricing, V as CashPlatformCapability, X as CashPreparedStepKind, Y as CashReceiveLeg, Z as CashoutInput, _ as CashoutOptions, $ as CuratorPayeeDataInput, a0 as EstimateInput, a1 as EstimateOptions, a2 as MARKET_SPREAD_BPS, a3 as MIN_CASHOUT_AMOUNT, a4 as NEAR_INTENTS_API_URL, a5 as NEAR_INTENTS_BASE_USDC_ASSET_ID, a6 as NEAR_INTENTS_DEFAULT_SLIPPAGE_BPS, a7 as NEAR_INTENTS_STATUSES, a8 as NearIntentsClient, a9 as NearIntentsOptions, aa as NearIntentsQuoteRequest, ab as NearIntentsStatusCode, ac as NearIntentsToken, ad as NearIntentsTradeType, ae as NearIntentsTransaction, af as ORACLE_MIN_CONVERSION_RATE_SENTINEL, ag as OrdersOptions, ah as PreparedCashoutReceipt, ai as RECOMMENDED_MIN_CASHOUT_AMOUNT, aj as RelayOptions, ak as RelayQuoteInput, al as RelaySourceInput, am as RelayTransaction, an as SignerOptions, ao as USDC_DECIMALS, ap as WatchOptions, aq as WithdrawOptions, ar as buildCapabilities, as as createCashClient, at as createNearIntentsClient, au as normalizeCashPayee, av as quoteNearIntentsToBaseUsdc, aw as readNearIntentsSourceCapabilities, ax as readNearIntentsStatus, ay as submitNearIntentsDeposit, az as toCashReferralAttributionCode } from './createCashClient-
|
|
1
|
+
import { C as CashPayoutInfo, I as IntentEntity, a as CashBuyerProfile, b as CashDepositInput, c as CashCatalogFeatures, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, N as NearIntentsSourceCapabilities, k as NearIntentsDepositInput, l as NearIntentsQuote, m as NearIntentsQuoteInput, n as NearIntentsStatus, o as NearIntentsStatusInput, P as PrepareResult, p as CashPreparedStep, R as RelayExecutionResult, q as RelayQuote, r as RelayStatus, s as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-ECL3HN5H.cjs';
|
|
2
|
+
export { B as BASE_CHAIN_ID, t as BASE_USDC_ADDRESS, u as CASH_ATTRIBUTION_CODE, v as CASH_ORDER_POLL_INTERVAL_MS, w as CASH_ORDER_STATUSES, x as CASH_REFERRAL_ATTRIBUTION_PREFIX, y as CASH_RETAIN_ON_EMPTY, z as CashAsset, A as CashChain, D as CashClient, E as CashClientOptions, F as CashCorridorPricing, G as CashFeatureFlags, H as CashFillEta, J as CashLeg, K as CashMultiCurrencyLeg, L as CashNextAction, M as CashOrderState, O as CashPairFillStats, Q as CashPayeeInput, S as CashPayout, U as CashPayoutPricing, V as CashPlatformCapability, X as CashPreparedStepKind, Y as CashReceiveLeg, Z as CashoutInput, _ as CashoutOptions, $ as CuratorPayeeDataInput, a0 as EstimateInput, a1 as EstimateOptions, a2 as MARKET_SPREAD_BPS, a3 as MIN_CASHOUT_AMOUNT, a4 as NEAR_INTENTS_API_URL, a5 as NEAR_INTENTS_BASE_USDC_ASSET_ID, a6 as NEAR_INTENTS_DEFAULT_SLIPPAGE_BPS, a7 as NEAR_INTENTS_STATUSES, a8 as NearIntentsClient, a9 as NearIntentsOptions, aa as NearIntentsQuoteRequest, ab as NearIntentsStatusCode, ac as NearIntentsToken, ad as NearIntentsTradeType, ae as NearIntentsTransaction, af as ORACLE_MIN_CONVERSION_RATE_SENTINEL, ag as OrdersOptions, ah as PreparedCashoutReceipt, ai as RECOMMENDED_MIN_CASHOUT_AMOUNT, aj as RelayOptions, ak as RelayQuoteInput, al as RelaySourceInput, am as RelayTransaction, an as SignerOptions, ao as USDC_DECIMALS, ap as WatchOptions, aq as WithdrawOptions, ar as buildCapabilities, as as createCashClient, at as createNearIntentsClient, au as normalizeCashPayee, av as quoteNearIntentsToBaseUsdc, aw as readNearIntentsSourceCapabilities, ax as readNearIntentsStatus, ay as submitNearIntentsDeposit, az as toCashReferralAttributionCode } from './createCashClient-ECL3HN5H.cjs';
|
|
3
3
|
import { PublicClient, Log, Abi } from 'viem';
|
|
4
4
|
import { PaymentMethodCatalog, CurrencyType, OracleAdapterOverrides, OnchainCurrency, Zkp2pClient, PreparedTransaction } from '@zkp2p/sdk';
|
|
5
5
|
export { CurrencyType, PreparedTransaction, RuntimeEnv } from '@zkp2p/sdk';
|
|
@@ -255,9 +255,8 @@ declare function buildIntentAmountRange(amount: bigint): {
|
|
|
255
255
|
*
|
|
256
256
|
* Registers payee details with the curator (no auth), resolves payment-method
|
|
257
257
|
* hashes + the gating service from the catalog, and assembles the override
|
|
258
|
-
* arrays with signal-time oracle configs. Alipay/CNY
|
|
259
|
-
*
|
|
260
|
-
* has no CNY oracle adapter.
|
|
258
|
+
* arrays with signal-time oracle configs. Alipay/CNY and UPI/INR instead fix
|
|
259
|
+
* fresh Chainlink snapshots from Ethereum and Polygon as their maker floors.
|
|
261
260
|
*/
|
|
262
261
|
declare function prepareCashDepositParams(client: Zkp2pClient, input: CashDepositInput, adapters?: OracleAdapterOverrides, creationRateReader?: CreationRateReader, features?: CashCatalogFeatures): Promise<CreateDepositParamsArg>;
|
|
263
262
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CashPayoutInfo, I as IntentEntity, a as CashBuyerProfile, b as CashDepositInput, c as CashCatalogFeatures, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, N as NearIntentsSourceCapabilities, k as NearIntentsDepositInput, l as NearIntentsQuote, m as NearIntentsQuoteInput, n as NearIntentsStatus, o as NearIntentsStatusInput, P as PrepareResult, p as CashPreparedStep, R as RelayExecutionResult, q as RelayQuote, r as RelayStatus, s as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-
|
|
2
|
-
export { B as BASE_CHAIN_ID, t as BASE_USDC_ADDRESS, u as CASH_ATTRIBUTION_CODE, v as CASH_ORDER_POLL_INTERVAL_MS, w as CASH_ORDER_STATUSES, x as CASH_REFERRAL_ATTRIBUTION_PREFIX, y as CASH_RETAIN_ON_EMPTY, z as CashAsset, A as CashChain, D as CashClient, E as CashClientOptions, F as CashCorridorPricing, G as CashFeatureFlags, H as CashFillEta, J as CashLeg, K as CashMultiCurrencyLeg, L as CashNextAction, M as CashOrderState, O as CashPairFillStats, Q as CashPayeeInput, S as CashPayout, U as CashPayoutPricing, V as CashPlatformCapability, X as CashPreparedStepKind, Y as CashReceiveLeg, Z as CashoutInput, _ as CashoutOptions, $ as CuratorPayeeDataInput, a0 as EstimateInput, a1 as EstimateOptions, a2 as MARKET_SPREAD_BPS, a3 as MIN_CASHOUT_AMOUNT, a4 as NEAR_INTENTS_API_URL, a5 as NEAR_INTENTS_BASE_USDC_ASSET_ID, a6 as NEAR_INTENTS_DEFAULT_SLIPPAGE_BPS, a7 as NEAR_INTENTS_STATUSES, a8 as NearIntentsClient, a9 as NearIntentsOptions, aa as NearIntentsQuoteRequest, ab as NearIntentsStatusCode, ac as NearIntentsToken, ad as NearIntentsTradeType, ae as NearIntentsTransaction, af as ORACLE_MIN_CONVERSION_RATE_SENTINEL, ag as OrdersOptions, ah as PreparedCashoutReceipt, ai as RECOMMENDED_MIN_CASHOUT_AMOUNT, aj as RelayOptions, ak as RelayQuoteInput, al as RelaySourceInput, am as RelayTransaction, an as SignerOptions, ao as USDC_DECIMALS, ap as WatchOptions, aq as WithdrawOptions, ar as buildCapabilities, as as createCashClient, at as createNearIntentsClient, au as normalizeCashPayee, av as quoteNearIntentsToBaseUsdc, aw as readNearIntentsSourceCapabilities, ax as readNearIntentsStatus, ay as submitNearIntentsDeposit, az as toCashReferralAttributionCode } from './createCashClient-
|
|
1
|
+
import { C as CashPayoutInfo, I as IntentEntity, a as CashBuyerProfile, b as CashDepositInput, c as CashCatalogFeatures, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, N as NearIntentsSourceCapabilities, k as NearIntentsDepositInput, l as NearIntentsQuote, m as NearIntentsQuoteInput, n as NearIntentsStatus, o as NearIntentsStatusInput, P as PrepareResult, p as CashPreparedStep, R as RelayExecutionResult, q as RelayQuote, r as RelayStatus, s as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-ECL3HN5H.js';
|
|
2
|
+
export { B as BASE_CHAIN_ID, t as BASE_USDC_ADDRESS, u as CASH_ATTRIBUTION_CODE, v as CASH_ORDER_POLL_INTERVAL_MS, w as CASH_ORDER_STATUSES, x as CASH_REFERRAL_ATTRIBUTION_PREFIX, y as CASH_RETAIN_ON_EMPTY, z as CashAsset, A as CashChain, D as CashClient, E as CashClientOptions, F as CashCorridorPricing, G as CashFeatureFlags, H as CashFillEta, J as CashLeg, K as CashMultiCurrencyLeg, L as CashNextAction, M as CashOrderState, O as CashPairFillStats, Q as CashPayeeInput, S as CashPayout, U as CashPayoutPricing, V as CashPlatformCapability, X as CashPreparedStepKind, Y as CashReceiveLeg, Z as CashoutInput, _ as CashoutOptions, $ as CuratorPayeeDataInput, a0 as EstimateInput, a1 as EstimateOptions, a2 as MARKET_SPREAD_BPS, a3 as MIN_CASHOUT_AMOUNT, a4 as NEAR_INTENTS_API_URL, a5 as NEAR_INTENTS_BASE_USDC_ASSET_ID, a6 as NEAR_INTENTS_DEFAULT_SLIPPAGE_BPS, a7 as NEAR_INTENTS_STATUSES, a8 as NearIntentsClient, a9 as NearIntentsOptions, aa as NearIntentsQuoteRequest, ab as NearIntentsStatusCode, ac as NearIntentsToken, ad as NearIntentsTradeType, ae as NearIntentsTransaction, af as ORACLE_MIN_CONVERSION_RATE_SENTINEL, ag as OrdersOptions, ah as PreparedCashoutReceipt, ai as RECOMMENDED_MIN_CASHOUT_AMOUNT, aj as RelayOptions, ak as RelayQuoteInput, al as RelaySourceInput, am as RelayTransaction, an as SignerOptions, ao as USDC_DECIMALS, ap as WatchOptions, aq as WithdrawOptions, ar as buildCapabilities, as as createCashClient, at as createNearIntentsClient, au as normalizeCashPayee, av as quoteNearIntentsToBaseUsdc, aw as readNearIntentsSourceCapabilities, ax as readNearIntentsStatus, ay as submitNearIntentsDeposit, az as toCashReferralAttributionCode } from './createCashClient-ECL3HN5H.js';
|
|
3
3
|
import { PublicClient, Log, Abi } from 'viem';
|
|
4
4
|
import { PaymentMethodCatalog, CurrencyType, OracleAdapterOverrides, OnchainCurrency, Zkp2pClient, PreparedTransaction } from '@zkp2p/sdk';
|
|
5
5
|
export { CurrencyType, PreparedTransaction, RuntimeEnv } from '@zkp2p/sdk';
|
|
@@ -255,9 +255,8 @@ declare function buildIntentAmountRange(amount: bigint): {
|
|
|
255
255
|
*
|
|
256
256
|
* Registers payee details with the curator (no auth), resolves payment-method
|
|
257
257
|
* hashes + the gating service from the catalog, and assembles the override
|
|
258
|
-
* arrays with signal-time oracle configs. Alipay/CNY
|
|
259
|
-
*
|
|
260
|
-
* has no CNY oracle adapter.
|
|
258
|
+
* arrays with signal-time oracle configs. Alipay/CNY and UPI/INR instead fix
|
|
259
|
+
* fresh Chainlink snapshots from Ethereum and Polygon as their maker floors.
|
|
261
260
|
*/
|
|
262
261
|
declare function prepareCashDepositParams(client: Zkp2pClient, input: CashDepositInput, adapters?: OracleAdapterOverrides, creationRateReader?: CreationRateReader, features?: CashCatalogFeatures): Promise<CreateDepositParamsArg>;
|
|
263
262
|
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, CASH_RETAIN_ON_EMPTY, BASE_USDC_ADDRESS, USDC_DECIMALS, BASE_CHAIN_ID, isCashError, errors, CASH_ORDER_STATUSES, mapChainError, CashError, CASH_ACCESS_GROUP_IDS, CASH_RESTRICTED_PLATFORMS } from './chunk-UIELF4TY.js';
|
|
2
2
|
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, isUserRejectedError } from './chunk-UIELF4TY.js';
|
|
3
3
|
import { parseAbi, parseEventLogs, isAddress, http, createPublicClient, createWalletClient, encodeFunctionData } from 'viem';
|
|
4
|
-
import { mainnet, base } from 'viem/chains';
|
|
4
|
+
import { mainnet, polygon, base } from 'viem/chains';
|
|
5
5
|
import { getSpreadOracleConfig, currencyInfo, getGatingServiceAddress, resolvePaymentMethodNameFromHash, getCurrencyCodeFromHash, createCompositeDepositId, appendAttributionToCalldata, Zkp2pClient, getPaymentMethodsCatalog, resolvePaymentMethodHashFromCatalog, defaultIndexerEndpoint, IndexerClient, CHAINLINK_ORACLE_FEEDS } from '@zkp2p/sdk';
|
|
6
6
|
import { createClient, MAINNET_RELAY_API } from '@relayprotocol/relay-sdk';
|
|
7
7
|
import { fetchChainConfigs, configureDynamicChains } from '@relayprotocol/relay-sdk/chain-utils';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
|
|
10
|
-
// src/client/creationRate.ts
|
|
11
10
|
var CHAINLINK_FEED_REGISTRY = "0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf";
|
|
12
11
|
var CNY_DENOMINATION = "0x000000000000000000000000000000000000009c";
|
|
13
|
-
var INR_DENOMINATION = "0x0000000000000000000000000000000000000164";
|
|
14
12
|
var USD_DENOMINATION = "0x0000000000000000000000000000000000000348";
|
|
13
|
+
var POLYGON_INR_USD_FEED = "0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60";
|
|
14
|
+
var DIRECT_FEED_ABI = parseAbi([
|
|
15
|
+
"function decimals() view returns (uint8)",
|
|
16
|
+
"function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)"
|
|
17
|
+
]);
|
|
15
18
|
var FEED_REGISTRY_ABI = [
|
|
16
19
|
{
|
|
17
20
|
name: "decimals",
|
|
@@ -50,74 +53,85 @@ function getCreationRateDenomination(platform, currency) {
|
|
|
50
53
|
if (platform.toLowerCase() === "alipay" && currency.toUpperCase() === "CNY") {
|
|
51
54
|
return CNY_DENOMINATION;
|
|
52
55
|
}
|
|
53
|
-
if (platform.toLowerCase() === "upi" && currency.toUpperCase() === "INR") {
|
|
54
|
-
return INR_DENOMINATION;
|
|
55
|
-
}
|
|
56
56
|
throw new Error(`No creation-time rate source for ${platform}/${currency}`);
|
|
57
57
|
}
|
|
58
58
|
function divideRoundUp(numerator, denominator) {
|
|
59
59
|
return (numerator + denominator - 1n) / denominator;
|
|
60
60
|
}
|
|
61
61
|
async function readCashCreationRate(publicClient, platform, currency, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
publicClient.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
const isUpi = platform.toLowerCase() === "upi" && currency.toUpperCase() === "INR";
|
|
63
|
+
const pair = `${currency.toUpperCase()}/USD`;
|
|
64
|
+
let decimals;
|
|
65
|
+
let round;
|
|
66
|
+
if (isUpi) {
|
|
67
|
+
if (await publicClient.getChainId() !== 137) {
|
|
68
|
+
throw new Error("Chainlink INR/USD requires Polygon mainnet (137)");
|
|
69
|
+
}
|
|
70
|
+
[decimals, round] = await Promise.all([
|
|
71
|
+
publicClient.readContract({
|
|
72
|
+
address: POLYGON_INR_USD_FEED,
|
|
73
|
+
abi: DIRECT_FEED_ABI,
|
|
74
|
+
functionName: "decimals"
|
|
75
|
+
}),
|
|
76
|
+
publicClient.readContract({
|
|
77
|
+
address: POLYGON_INR_USD_FEED,
|
|
78
|
+
abi: DIRECT_FEED_ABI,
|
|
79
|
+
functionName: "latestRoundData"
|
|
80
|
+
})
|
|
81
|
+
]);
|
|
82
|
+
} else {
|
|
83
|
+
const args = [
|
|
84
|
+
getCreationRateDenomination(platform, currency),
|
|
85
|
+
USD_DENOMINATION
|
|
86
|
+
];
|
|
87
|
+
[decimals, round] = await Promise.all([
|
|
88
|
+
publicClient.readContract({
|
|
89
|
+
address: CHAINLINK_FEED_REGISTRY,
|
|
90
|
+
abi: FEED_REGISTRY_ABI,
|
|
91
|
+
functionName: "decimals",
|
|
92
|
+
args
|
|
93
|
+
}),
|
|
94
|
+
publicClient.readContract({
|
|
95
|
+
address: CHAINLINK_FEED_REGISTRY,
|
|
96
|
+
abi: FEED_REGISTRY_ABI,
|
|
97
|
+
functionName: "latestRoundData",
|
|
98
|
+
args
|
|
99
|
+
})
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
80
102
|
const [roundId, answer, , updatedAtRaw, answeredInRound] = round;
|
|
81
103
|
if (answer <= 0n || updatedAtRaw <= 0n || answeredInRound < roundId) {
|
|
82
|
-
throw new Error(
|
|
104
|
+
throw new Error(`Chainlink ${pair} returned an invalid round`);
|
|
83
105
|
}
|
|
84
106
|
const updatedAt = Number(updatedAtRaw);
|
|
85
107
|
if (!Number.isSafeInteger(updatedAt) || updatedAt > nowSeconds) {
|
|
86
|
-
throw new Error(
|
|
108
|
+
throw new Error(`Chainlink ${pair} returned an invalid timestamp`);
|
|
87
109
|
}
|
|
88
110
|
if (nowSeconds - updatedAt > CREATION_RATE_MAX_STALENESS_SECONDS) {
|
|
89
|
-
throw new Error(
|
|
111
|
+
throw new Error(`Chainlink ${pair} rate is stale`);
|
|
90
112
|
}
|
|
91
113
|
const rate1e18 = divideRoundUp(10n ** (BigInt(decimals) + 18n), answer);
|
|
92
114
|
const rate = Number(rate1e18) / 1e18;
|
|
93
115
|
if (!Number.isFinite(rate) || rate <= 0) {
|
|
94
|
-
throw new Error(
|
|
116
|
+
throw new Error(`Chainlink ${pair} produced an invalid creation rate`);
|
|
95
117
|
}
|
|
96
118
|
return { rate1e18, rate, updatedAt };
|
|
97
119
|
}
|
|
98
120
|
async function readAlipayCnyCreationRate(publicClient, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
99
121
|
return readCashCreationRate(publicClient, "alipay", "CNY", nowSeconds);
|
|
100
122
|
}
|
|
101
|
-
var UPI_STAGING_PAYMENT_METHOD_HASH = "0xe99a5081226cbbff9440a63da5caa04fa30f210c12c4dd9976132ac075054cd9";
|
|
102
123
|
function getCashPaymentMethodsCatalog(environment, features = {}) {
|
|
103
124
|
const catalog = getPaymentMethodsCatalog(8453, environment);
|
|
104
|
-
if (environment
|
|
125
|
+
if ((environment === "staging" || environment === "preproduction") && features.upi === true) {
|
|
105
126
|
return catalog;
|
|
106
127
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
paymentMethodHash: UPI_STAGING_PAYMENT_METHOD_HASH,
|
|
111
|
-
currencies: [currencyInfo.INR.currencyCodeHash],
|
|
112
|
-
timestampBuffer: 30,
|
|
113
|
-
providerHashes: []
|
|
114
|
-
}
|
|
115
|
-
};
|
|
128
|
+
const enabledCatalog = { ...catalog };
|
|
129
|
+
delete enabledCatalog.upi;
|
|
130
|
+
return enabledCatalog;
|
|
116
131
|
}
|
|
117
132
|
function resolveCashPaymentMethodHash(processorName, catalog) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return catalog.upi.paymentMethodHash;
|
|
133
|
+
if (processorName.trim().toLowerCase() === "upi" && !catalog.upi) {
|
|
134
|
+
throw new Error("UPI is not enabled in this environment catalog");
|
|
121
135
|
}
|
|
122
136
|
return resolvePaymentMethodHashFromCatalog(processorName, catalog);
|
|
123
137
|
}
|
|
@@ -621,9 +635,7 @@ function buildCapabilities(environment, features = {}) {
|
|
|
621
635
|
requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform),
|
|
622
636
|
requiresAtomicAccessPolicy: false
|
|
623
637
|
};
|
|
624
|
-
}).filter(
|
|
625
|
-
(p) => p.currencies.length > 0 && (p.platform !== "upi" || environment === "staging" && features.upi === true)
|
|
626
|
-
).sort((a, b) => a.platform.localeCompare(b.platform));
|
|
638
|
+
}).filter((p) => p.currencies.length > 0).sort((a, b) => a.platform.localeCompare(b.platform));
|
|
627
639
|
const currencies = [...new Set(platforms.flatMap((p) => p.currencies))].sort();
|
|
628
640
|
const baseUsdc = { address: BASE_USDC_ADDRESS, symbol: "USDC", decimals: USDC_DECIMALS };
|
|
629
641
|
return {
|
|
@@ -1304,10 +1316,11 @@ async function readEstimate(publicClient, input, context = {}) {
|
|
|
1304
1316
|
let rate;
|
|
1305
1317
|
let oracleUpdatedAt;
|
|
1306
1318
|
if (usesCreationRate) {
|
|
1307
|
-
|
|
1319
|
+
const rateClient = creationRatePlatform.toLowerCase() === "upi" ? context.upiCreationRateClient : context.creationRateClient;
|
|
1320
|
+
if (!rateClient) throw errors.oracleUnsupportedCurrency(currency);
|
|
1308
1321
|
try {
|
|
1309
1322
|
const snapshot = await readCashCreationRate(
|
|
1310
|
-
|
|
1323
|
+
rateClient,
|
|
1311
1324
|
creationRatePlatform,
|
|
1312
1325
|
currency,
|
|
1313
1326
|
asOf
|
|
@@ -2335,7 +2348,7 @@ function createCashAttributionReader(options) {
|
|
|
2335
2348
|
// src/client/classify.ts
|
|
2336
2349
|
function isCashPayoutSet(payouts, attributedToCash = false) {
|
|
2337
2350
|
return payouts.length > 0 && payouts.every(
|
|
2338
|
-
(payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && payout.platform
|
|
2351
|
+
(payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && isCreationRateCorridor(payout.platform, payout.currency ?? "") && payout.pricing.fixedAtCreation === true && (payout.pricing.fixedRate ?? 0) > 0
|
|
2339
2352
|
);
|
|
2340
2353
|
}
|
|
2341
2354
|
|
|
@@ -2439,6 +2452,10 @@ function createCashClient(options) {
|
|
|
2439
2452
|
chain: mainnet,
|
|
2440
2453
|
transport: creationRateTransport
|
|
2441
2454
|
});
|
|
2455
|
+
const upiCreationRateClient = createPublicClient({
|
|
2456
|
+
chain: polygon,
|
|
2457
|
+
transport: options.upiCreationRateTransport ?? http(options.upiCreationRateRpcUrl)
|
|
2458
|
+
});
|
|
2442
2459
|
const readCashAttribution = createCashAttributionReader({
|
|
2443
2460
|
environment,
|
|
2444
2461
|
...options.indexerUrl ? { indexerUrl: options.indexerUrl } : {},
|
|
@@ -2573,7 +2590,11 @@ function createCashClient(options) {
|
|
|
2573
2590
|
throw new Error(`No creation-time rate reader for ${platform}/${currency}`);
|
|
2574
2591
|
}
|
|
2575
2592
|
try {
|
|
2576
|
-
return await readCashCreationRate(
|
|
2593
|
+
return await readCashCreationRate(
|
|
2594
|
+
platform.toLowerCase() === "upi" ? upiCreationRateClient : creationRateClient,
|
|
2595
|
+
platform,
|
|
2596
|
+
currency
|
|
2597
|
+
);
|
|
2577
2598
|
} catch (err) {
|
|
2578
2599
|
throw errors.oracleReadFailed(currency, err);
|
|
2579
2600
|
}
|
|
@@ -2951,6 +2972,7 @@ function createCashClient(options) {
|
|
|
2951
2972
|
...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
|
|
2952
2973
|
etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
|
|
2953
2974
|
creationRateClient,
|
|
2975
|
+
upiCreationRateClient,
|
|
2954
2976
|
...options.relay ? { relay: options.relay } : {}
|
|
2955
2977
|
});
|
|
2956
2978
|
},
|
package/dist/react.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CurrencyType } from '@zkp2p/sdk';
|
|
2
|
-
import { D as CashClient, a0 as EstimateInput, i as CashEstimate, _ as CashoutOptions, h as CashoutResult, Z as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-
|
|
2
|
+
import { D as CashClient, a0 as EstimateInput, i as CashEstimate, _ as CashoutOptions, h as CashoutResult, Z as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-ECL3HN5H.cjs';
|
|
3
3
|
import { WalletClient } from 'viem';
|
|
4
4
|
import '@relayprotocol/relay-sdk';
|
|
5
5
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CurrencyType } from '@zkp2p/sdk';
|
|
2
|
-
import { D as CashClient, a0 as EstimateInput, i as CashEstimate, _ as CashoutOptions, h as CashoutResult, Z as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-
|
|
2
|
+
import { D as CashClient, a0 as EstimateInput, i as CashEstimate, _ as CashoutOptions, h as CashoutResult, Z as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-ECL3HN5H.js';
|
|
3
3
|
import { WalletClient } from 'viem';
|
|
4
4
|
import '@relayprotocol/relay-sdk';
|
|
5
5
|
|
package/dist/tools.cjs
CHANGED
package/dist/tools.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Peer Cash Demo: the express sell flow stored entirely onchain on Base, after zSwap by z0r0z",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@zkp2p/cash": "0.
|
|
7
|
+
"@zkp2p/cash": "0.5.3",
|
|
8
8
|
"esbuild": "0.28.2",
|
|
9
9
|
"solc": "0.8.36",
|
|
10
10
|
"viem": "2.55.18"
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
// UPI pricing reads Polygon mainnet; configure upiCreationRateRpcUrl for a dedicated provider.
|
|
1
2
|
import { createCashClient, usdc } from '@zkp2p/cash';
|
|
2
3
|
import type { WalletClient } from 'viem';
|
|
3
4
|
|
|
4
|
-
export async function cashOutToUpi(
|
|
5
|
+
export async function cashOutToUpi(
|
|
6
|
+
signer: WalletClient,
|
|
7
|
+
upiId: string,
|
|
8
|
+
environment: 'staging' | 'preproduction' = 'staging',
|
|
9
|
+
) {
|
|
5
10
|
const cash = createCashClient({
|
|
6
|
-
environment
|
|
11
|
+
environment,
|
|
7
12
|
features: { upi: true },
|
|
8
13
|
});
|
|
9
14
|
|
|
10
15
|
const upi = cash.capabilities().platforms.find(({ platform }) => platform === 'upi');
|
|
11
16
|
if (!upi?.currencies.includes('INR')) {
|
|
12
|
-
throw new Error('UPI is not enabled in this
|
|
17
|
+
throw new Error('UPI is not enabled in this environment.');
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
return cash.cashout(
|
package/llms.txt
CHANGED
|
@@ -25,7 +25,7 @@ Key facts:
|
|
|
25
25
|
then cash out Base-only. Use EXACT_OUTPUT when the order amount must be fixed.
|
|
26
26
|
- estimate() reports its binding point. Existing corridors bind the on-chain
|
|
27
27
|
oracle when an intent is signaled; Alipay/CNY fixes a fresh Ethereum
|
|
28
|
-
Chainlink snapshot during deposit preparation. ETA is `{ seconds, label }` from the same rolling
|
|
28
|
+
Chainlink snapshot during deposit preparation; UPI/INR uses a fresh Polygon Chainlink snapshot. ETA is `{ seconds, label }` from the same rolling
|
|
29
29
|
30-day, intent-attributed pair sampler as fillStats(), not a guarantee.
|
|
30
30
|
- fillStats() returns raw `{ fills, medianFillSeconds? }` evidence keyed by
|
|
31
31
|
`platform:currency` or a sorted set such as `revolut:EUR+GBP+USD`. Set
|
|
@@ -38,11 +38,11 @@ Key facts:
|
|
|
38
38
|
fillStats() separately.
|
|
39
39
|
- capabilities() exposes one Zelle platform. A zelle cashout attaches only the
|
|
40
40
|
generic Zelle payment method to the on-chain deposit.
|
|
41
|
-
- UPI/INR is staging-only and fail-closed. Enable it with
|
|
42
|
-
`createCashClient({ environment: '
|
|
41
|
+
- UPI/INR is staging/preproduction-only and fail-closed. Enable it with
|
|
42
|
+
`createCashClient({ environment: 'preproduction', features: { upi: true } })`.
|
|
43
43
|
Any valid UPI ID from any bank can receive without a seller login,
|
|
44
44
|
extension, identity attestation, or prior registration flow. Buyers
|
|
45
|
-
|
|
45
|
+
pay and verify through Amazon Pay using standard UPI.
|
|
46
46
|
- Resume any order from its depositId alone (composite escrow_onchainId).
|
|
47
47
|
- One unwind verb: withdraw(depositId) - prunes expired intents automatically;
|
|
48
48
|
pass amount for a partial withdrawal of the unlocked balance.
|
|
@@ -127,3 +127,13 @@ Key facts:
|
|
|
127
127
|
API key or referral-enrollment transaction is required.
|
|
128
128
|
- [Tools manifest](src/tools/index.ts): JSON-schema definitions of the verbs
|
|
129
129
|
- [Codecs](src/codecs/): zod schemas + lossless JSON round-trips
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
UPI/INR reads the live Chainlink Polygon mainnet proxy
|
|
133
|
+
`0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60` (chain 137), inverts
|
|
134
|
+
USD per INR, and rounds the creation-time maker floor up. Configure its
|
|
135
|
+
read-only RPC with `upiCreationRateRpcUrl` or `upiCreationRateTransport`.
|
|
136
|
+
Alipay/CNY retains the Ethereum registry and `creationRateRpcUrl` /
|
|
137
|
+
`creationRateTransport`. UPI rejects the wrong chain, invalid rounds, and
|
|
138
|
+
observations older than 24 hours; market closures do not bypass freshness.
|
|
139
|
+
UPI remains opt-in on staging/preproduction and disabled in production.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkp2p/cash",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4-rc.2",
|
|
4
4
|
"description": "Peer Cash - offramp-only SDK for routing Relay or NEAR Intents assets to Base USDC, then cashing out to fiat at zero-spread Chainlink market rates.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Peer (https://peer.xyz)",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
110
|
"@relayprotocol/relay-sdk": "^7.0.1",
|
|
111
|
-
"@zkp2p/sdk": "0.14.1",
|
|
111
|
+
"@zkp2p/sdk": "0.14.2-rc.1",
|
|
112
112
|
"zod": "^4.4.3"
|
|
113
113
|
},
|
|
114
114
|
"peerDependencies": {
|
|
@@ -32,7 +32,7 @@ protocol-held funds and no custodial off-ramp provider.
|
|
|
32
32
|
`oracleRateConfig { spreadBps: 0 }`; the binding rate is the Chainlink rate
|
|
33
33
|
when a buyer signals. Alipay/CNY is the exception: because Base has no CNY
|
|
34
34
|
oracle adapter, the SDK reads Chainlink CNY/USD on Ethereum and fixes that
|
|
35
|
-
fresh snapshot as the maker floor during deposit preparation. Read
|
|
35
|
+
fresh snapshot as the maker floor during deposit preparation. UPI/INR does the same using the direct Polygon INR/USD feed, rejecting wrong-chain, stale and invalid data. Read
|
|
36
36
|
`estimate().binding` and `capabilities().platforms[].pricing`.
|
|
37
37
|
- **Custody story.** Funds are held by the protocol contract only. An unmatched
|
|
38
38
|
deposit is withdrawable by the maker at any time. The SDK never holds keys.
|
|
@@ -183,11 +183,11 @@ platforms outright: a previously registered handle can be reused with bare
|
|
|
183
183
|
payee data. Handle `PAYEE_VERIFICATION_REQUIRED` when registration is still
|
|
184
184
|
needed.
|
|
185
185
|
|
|
186
|
-
UPI is a staging-only opt-in
|
|
187
|
-
with `features: { upi: true }` on a staging client. Accept any locally valid
|
|
186
|
+
UPI is a staging/preproduction-only opt-in while production rollout awaits review. Enable it
|
|
187
|
+
with `features: { upi: true }` on a staging or preproduction client. Accept any locally valid
|
|
188
188
|
UPI ID from any bank; do not add a seller bank-login, extension, identity
|
|
189
|
-
attestation, or pre-registration step. Buyers
|
|
190
|
-
payments
|
|
189
|
+
attestation, or pre-registration step. Buyers pay and verify through Amazon Pay using standard UPI; UPI Lite and
|
|
190
|
+
merchant payments are unsupported.
|
|
191
191
|
|
|
192
192
|
## 4. Order management - indexer-native
|
|
193
193
|
|
|
@@ -263,3 +263,23 @@ Prove both routes without waiting for a buyer:
|
|
|
263
263
|
|
|
264
264
|
If withdrawal fails with funds stuck: stop, do not retry blindly, escalate to
|
|
265
265
|
a human with the `depositId` and tx hashes.
|
|
266
|
+
|
|
267
|
+
## UPI oracle checks
|
|
268
|
+
|
|
269
|
+
UPI/INR reads the live Chainlink Polygon mainnet proxy
|
|
270
|
+
`0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60` (chain 137), inverts
|
|
271
|
+
USD per INR, and rounds the creation-time maker floor up. Configure its
|
|
272
|
+
read-only RPC with `upiCreationRateRpcUrl` or `upiCreationRateTransport`.
|
|
273
|
+
Alipay/CNY retains the Ethereum registry and `creationRateRpcUrl` /
|
|
274
|
+
`creationRateTransport`. UPI rejects the wrong chain, invalid rounds, and
|
|
275
|
+
observations older than 24 hours; market closures do not bypass freshness.
|
|
276
|
+
UPI remains opt-in on staging/preproduction and disabled in production.
|
|
277
|
+
|
|
278
|
+
Before a funded UPI QA run, call
|
|
279
|
+
`cash.estimate({ amount: 1000000n, platform: 'upi', currency: 'INR' }, { includeEta: false })`
|
|
280
|
+
using the intended live Polygon RPC. Require a positive finite rate,
|
|
281
|
+
`binding: 'deposit-creation'`, and `oracleUpdatedAt` no more than 86400 seconds
|
|
282
|
+
old and not in the future. Record the observation time and selected chain,
|
|
283
|
+
then stop before funding if the read fails. Unit-test fixtures prove routing,
|
|
284
|
+
not live availability. The authoritative feed listing is
|
|
285
|
+
https://data.chain.link/feeds/polygon/mainnet/inr-usd.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upi-preprod-qa
|
|
3
|
+
description: Verify the Cash SDK Amazon Pay UPI corridor on preproduction, including catalog gates, a small maker deposit, indexing, and cleanup.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UPI preproduction QA
|
|
7
|
+
|
|
8
|
+
Use the exact published Cash candidate and record its SDK/contracts versions.
|
|
9
|
+
Require `createCashClient({ environment: 'preproduction', features: { upi: true } })`
|
|
10
|
+
to advertise UPI/INR. Without the flag, and in production even with the flag,
|
|
11
|
+
UPI must be absent and unsigned preparation must reject before registration or rate reads.
|
|
12
|
+
The canonical contracts catalog must contain the live UPI method and INR currency;
|
|
13
|
+
never invent a catalog entry when the package or registry omits one.
|
|
14
|
+
|
|
15
|
+
From a clean consumer of the candidate, run this catalog check before creating
|
|
16
|
+
any transaction:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createCashClient } from '@zkp2p/cash';
|
|
20
|
+
|
|
21
|
+
for (const environment of ['staging', 'preproduction', 'production'] as const) {
|
|
22
|
+
for (const upi of [false, true]) {
|
|
23
|
+
const client = createCashClient({ environment, features: { upi } });
|
|
24
|
+
const method = client.capabilities().platforms.find((item) => item.platform === 'upi');
|
|
25
|
+
const expected = upi && environment !== 'production';
|
|
26
|
+
if (Boolean(method?.currencies.includes('INR')) !== expected) {
|
|
27
|
+
throw new Error(`Unexpected UPI catalog for ${environment}, flag=${upi}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use an explicitly authorized low-value wallet and verified recipient UPI ID.
|
|
34
|
+
Keep the identity and operation ledger in ignored private storage, outside this skill.
|
|
35
|
+
`cashout` accepts any valid VPA; no seller bank login or identity attestation is required.
|
|
36
|
+
Verify INR pricing is a fresh Polygon Chainlink snapshot fixed at deposit creation.
|
|
37
|
+
The official [INR/USD feed](https://data.chain.link/feeds/polygon/mainnet/inr-usd)
|
|
38
|
+
is proxy `0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60` on chain 137;
|
|
39
|
+
it is not registered in Ethereum's Feed Registry. Before funding, call
|
|
40
|
+
`cash.estimate({ amount: 5_000_000n, platform: 'upi', currency: 'INR' }, { includeEta: false })`
|
|
41
|
+
against the live default or explicitly configured Polygon RPC. Confirm a positive
|
|
42
|
+
rate, fresh `oracleUpdatedAt`, and `binding: 'deposit-creation'`. Do not accept
|
|
43
|
+
mocked-rate unit tests as proof of a functioning live corridor. The reader must
|
|
44
|
+
reject the wrong chain, invalid/incomplete rounds, future timestamps and data
|
|
45
|
+
older than 86,400 seconds. Forex market-hour gaps must fail closed; do not
|
|
46
|
+
substitute a static price or relax freshness to make QA pass.
|
|
47
|
+
|
|
48
|
+
With bounded task authorization, create one small Base-USDC deposit, recording
|
|
49
|
+
transaction hash and deposit ID before any retry. Confirm the receipt, then
|
|
50
|
+
`order`/`orders` and the matching preproduction indexer's MethodCurrency and
|
|
51
|
+
QuoteCandidate. Check exact UPI/INR hashes, payee hash, amounts, and rate. Share
|
|
52
|
+
this one fixture with authorized buyer QA instead of funding duplicate deposits.
|
|
53
|
+
Keep quotes within the maker minimum and available liquidity.
|
|
54
|
+
|
|
55
|
+
Use `examples/upi-staging-cashout.ts` with its explicit `preproduction`
|
|
56
|
+
environment argument as the maker API example. Use the supported Cash client
|
|
57
|
+
methods for signing and finalization; never copy an old escrow address or
|
|
58
|
+
transaction payload. For GraphQL, filter `MethodCurrency` and `QuoteCandidate`
|
|
59
|
+
by the recorded `depositId`, `paymentMethodHash`, and `currencyCode`. Require
|
|
60
|
+
exactly one matching tuple, Base USDC, a positive matching conversion rate,
|
|
61
|
+
`isActive: true`, `hasMinLiquidity: true`, and enough available liquidity.
|
|
62
|
+
Inspect `disputeProtectionRequiresStake` before buyer QA so stake admission is
|
|
63
|
+
not mistaken for a payment-proof failure.
|
|
64
|
+
|
|
65
|
+
Buyer QA belongs to the client and attestation repositories. Amazon Pay is the
|
|
66
|
+
sole client flow: no HDFC/Gmail selector or fallback copy. Pay with standard UPI
|
|
67
|
+
to a person, then verify from the same Amazon account. Exclude UPI Lite, merchant
|
|
68
|
+
payments and incoming transactions. Keep session evidence encrypted. A real
|
|
69
|
+
intent requires the exact payment identity, allowed payment-time window, and an
|
|
70
|
+
unused payment nullifier. Report maker deposit, buyer signal, evidence proof,
|
|
71
|
+
and settlement as separate checkpoints. Backend HDFC support is retained until
|
|
72
|
+
a separately authorized retirement; do not remove its server routes here.
|
|
73
|
+
|
|
74
|
+
After buyer intents are cancelled or otherwise terminal, call the supported
|
|
75
|
+
`withdraw` path and verify the receipt and returned order state. If a transaction
|
|
76
|
+
has an unknown result, inspect its receipt and existing deposit before retrying.
|
|
77
|
+
Stop further spending on an unexplained balance or identity mismatch.
|
|
78
|
+
|
|
79
|
+
Run `bun run ci` for SDK changes. Keep private VPA, cookies, raw receipts, keys, and request
|
|
80
|
+
bodies out of committed evidence; publish only redacted checkpoint results.
|
|
81
|
+
|
|
82
|
+
## Live order reconstruction regression
|
|
83
|
+
|
|
84
|
+
After the exact UPI fixture is indexed, require both `cash.order(depositId)` and `cash.orders(owner)` to return it. Fixed UPI/INR creation-rate deposits must have positive fixed-rate evidence and the indexed `peer-cash` attribution marker. Do not classify unrelated Advanced Sell deposits as Cash orders. A quoteable indexer row alone is insufficient: run order lookup, partial-fill observation and withdrawal checks too. Keep the method/currency pair explicit; UPI/CNY must be rejected.
|
|
85
|
+
|
|
86
|
+
## Small-fixture visibility and dust reconciliation
|
|
87
|
+
|
|
88
|
+
The web orderbook hides deposits below USD 5 by default. Enable its low-liquidity
|
|
89
|
+
filter before diagnosing an active small fixture as missing. Selector liquidity
|
|
90
|
+
can include a deposit that the orderbook display hides. A verified read-only
|
|
91
|
+
Express `getQuote` accepted INR 10 against a 1 USDC fixture; quoteability still
|
|
92
|
+
depends on the current maker minimum, available liquidity and rate. This does
|
|
93
|
+
not authorize a payment or another fixture, and a quote is not a settlement.
|
|
94
|
+
|
|
95
|
+
The verified contract dust threshold is 0.1 USDC. A remaining amount below that
|
|
96
|
+
threshold may be collected as protocol dust rather than refunded to the maker.
|
|
97
|
+
Reconcile `DustCollected` events and actual token transfers with the successful
|
|
98
|
+
receipt, buyer proceeds, fees and maker balance. Do not interpret Cash
|
|
99
|
+
`returnedAmount` alone as a maker refund. Confirm the deployed threshold again
|
|
100
|
+
if the contract version changes, and retain exact amounts in private receipts.
|