@zkp2p/cash 0.5.3 → 0.5.4-rc.1

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
@@ -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
+ This does not change the staging-only UPI opt-in gate.
package/README.md CHANGED
@@ -498,3 +498,12 @@ the package, not the contributor entry point.
498
498
  ## License
499
499
 
500
500
  MIT
501
+
502
+ UPI/INR reads the live Chainlink Polygon mainnet proxy
503
+ `0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60` (chain 137), inverts
504
+ USD per INR, and rounds the creation-time maker floor up. Configure its
505
+ read-only RPC with `upiCreationRateRpcUrl` or `upiCreationRateTransport`.
506
+ Alipay/CNY retains the Ethereum registry and `creationRateRpcUrl` /
507
+ `creationRateTransport`. UPI rejects the wrong chain, invalid rounds, and
508
+ observations older than 24 hours; market closures do not bypass freshness.
509
+ This does not change the staging-only UPI opt-in gate.
@@ -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's Ethereum feed
589
- * and fix that fresh snapshot as the maker floor when preparing the deposit.
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's Ethereum feed
589
- * and fix that fresh snapshot as the maker floor when preparing the deposit.
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,48 +75,67 @@ 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 args = [
86
- getCreationRateDenomination(platform, currency),
87
- USD_DENOMINATION
88
- ];
89
- const [decimals, round] = await Promise.all([
90
- publicClient.readContract({
91
- address: CHAINLINK_FEED_REGISTRY,
92
- abi: FEED_REGISTRY_ABI,
93
- functionName: "decimals",
94
- args
95
- }),
96
- publicClient.readContract({
97
- address: CHAINLINK_FEED_REGISTRY,
98
- abi: FEED_REGISTRY_ABI,
99
- functionName: "latestRoundData",
100
- args
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("Chainlink CNY/USD returned an invalid round");
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("Chainlink CNY/USD returned an invalid timestamp");
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("Chainlink CNY/USD rate is stale");
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("Chainlink CNY/USD produced an invalid creation rate");
138
+ throw new Error(`Chainlink ${pair} produced an invalid creation rate`);
118
139
  }
119
140
  return { rate1e18, rate, updatedAt };
120
141
  }
@@ -1799,10 +1820,11 @@ async function readEstimate(publicClient, input, context = {}) {
1799
1820
  let rate;
1800
1821
  let oracleUpdatedAt;
1801
1822
  if (usesCreationRate) {
1802
- if (!context.creationRateClient) throw errors.oracleUnsupportedCurrency(currency);
1823
+ const rateClient = creationRatePlatform.toLowerCase() === "upi" ? context.upiCreationRateClient : context.creationRateClient;
1824
+ if (!rateClient) throw errors.oracleUnsupportedCurrency(currency);
1803
1825
  try {
1804
1826
  const snapshot = await readCashCreationRate(
1805
- context.creationRateClient,
1827
+ rateClient,
1806
1828
  creationRatePlatform,
1807
1829
  currency,
1808
1830
  asOf
@@ -2830,7 +2852,7 @@ function createCashAttributionReader(options) {
2830
2852
  // src/client/classify.ts
2831
2853
  function isCashPayoutSet(payouts, attributedToCash = false) {
2832
2854
  return payouts.length > 0 && payouts.every(
2833
- (payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && payout.platform === "alipay" && payout.currency === "CNY" && payout.pricing.fixedAtCreation === true && (payout.pricing.fixedRate ?? 0) > 0
2855
+ (payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && isCreationRateCorridor(payout.platform, payout.currency ?? "") && payout.pricing.fixedAtCreation === true && (payout.pricing.fixedRate ?? 0) > 0
2834
2856
  );
2835
2857
  }
2836
2858
 
@@ -2934,6 +2956,10 @@ function createCashClient(options) {
2934
2956
  chain: chains.mainnet,
2935
2957
  transport: creationRateTransport
2936
2958
  });
2959
+ const upiCreationRateClient = viem.createPublicClient({
2960
+ chain: chains.polygon,
2961
+ transport: options.upiCreationRateTransport ?? viem.http(options.upiCreationRateRpcUrl)
2962
+ });
2937
2963
  const readCashAttribution = createCashAttributionReader({
2938
2964
  environment,
2939
2965
  ...options.indexerUrl ? { indexerUrl: options.indexerUrl } : {},
@@ -3068,7 +3094,11 @@ function createCashClient(options) {
3068
3094
  throw new Error(`No creation-time rate reader for ${platform}/${currency}`);
3069
3095
  }
3070
3096
  try {
3071
- return await readCashCreationRate(creationRateClient, platform, currency);
3097
+ return await readCashCreationRate(
3098
+ platform.toLowerCase() === "upi" ? upiCreationRateClient : creationRateClient,
3099
+ platform,
3100
+ currency
3101
+ );
3072
3102
  } catch (err) {
3073
3103
  throw errors.oracleReadFailed(currency, err);
3074
3104
  }
@@ -3446,6 +3476,7 @@ function createCashClient(options) {
3446
3476
  ...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
3447
3477
  etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
3448
3478
  creationRateClient,
3479
+ upiCreationRateClient,
3449
3480
  ...options.relay ? { relay: options.relay } : {}
3450
3481
  });
3451
3482
  },
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-DimDWJU4.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-DimDWJU4.cjs';
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';
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-DimDWJU4.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-DimDWJU4.js';
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';
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,48 +53,67 @@ 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 args = [
63
- getCreationRateDenomination(platform, currency),
64
- USD_DENOMINATION
65
- ];
66
- const [decimals, round] = await Promise.all([
67
- publicClient.readContract({
68
- address: CHAINLINK_FEED_REGISTRY,
69
- abi: FEED_REGISTRY_ABI,
70
- functionName: "decimals",
71
- args
72
- }),
73
- publicClient.readContract({
74
- address: CHAINLINK_FEED_REGISTRY,
75
- abi: FEED_REGISTRY_ABI,
76
- functionName: "latestRoundData",
77
- args
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("Chainlink CNY/USD returned an invalid round");
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("Chainlink CNY/USD returned an invalid timestamp");
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("Chainlink CNY/USD rate is stale");
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("Chainlink CNY/USD produced an invalid creation rate");
116
+ throw new Error(`Chainlink ${pair} produced an invalid creation rate`);
95
117
  }
96
118
  return { rate1e18, rate, updatedAt };
97
119
  }
@@ -1304,10 +1326,11 @@ async function readEstimate(publicClient, input, context = {}) {
1304
1326
  let rate;
1305
1327
  let oracleUpdatedAt;
1306
1328
  if (usesCreationRate) {
1307
- if (!context.creationRateClient) throw errors.oracleUnsupportedCurrency(currency);
1329
+ const rateClient = creationRatePlatform.toLowerCase() === "upi" ? context.upiCreationRateClient : context.creationRateClient;
1330
+ if (!rateClient) throw errors.oracleUnsupportedCurrency(currency);
1308
1331
  try {
1309
1332
  const snapshot = await readCashCreationRate(
1310
- context.creationRateClient,
1333
+ rateClient,
1311
1334
  creationRatePlatform,
1312
1335
  currency,
1313
1336
  asOf
@@ -2335,7 +2358,7 @@ function createCashAttributionReader(options) {
2335
2358
  // src/client/classify.ts
2336
2359
  function isCashPayoutSet(payouts, attributedToCash = false) {
2337
2360
  return payouts.length > 0 && payouts.every(
2338
- (payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && payout.platform === "alipay" && payout.currency === "CNY" && payout.pricing.fixedAtCreation === true && (payout.pricing.fixedRate ?? 0) > 0
2361
+ (payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 || attributedToCash && isCreationRateCorridor(payout.platform, payout.currency ?? "") && payout.pricing.fixedAtCreation === true && (payout.pricing.fixedRate ?? 0) > 0
2339
2362
  );
2340
2363
  }
2341
2364
 
@@ -2439,6 +2462,10 @@ function createCashClient(options) {
2439
2462
  chain: mainnet,
2440
2463
  transport: creationRateTransport
2441
2464
  });
2465
+ const upiCreationRateClient = createPublicClient({
2466
+ chain: polygon,
2467
+ transport: options.upiCreationRateTransport ?? http(options.upiCreationRateRpcUrl)
2468
+ });
2442
2469
  const readCashAttribution = createCashAttributionReader({
2443
2470
  environment,
2444
2471
  ...options.indexerUrl ? { indexerUrl: options.indexerUrl } : {},
@@ -2573,7 +2600,11 @@ function createCashClient(options) {
2573
2600
  throw new Error(`No creation-time rate reader for ${platform}/${currency}`);
2574
2601
  }
2575
2602
  try {
2576
- return await readCashCreationRate(creationRateClient, platform, currency);
2603
+ return await readCashCreationRate(
2604
+ platform.toLowerCase() === "upi" ? upiCreationRateClient : creationRateClient,
2605
+ platform,
2606
+ currency
2607
+ );
2577
2608
  } catch (err) {
2578
2609
  throw errors.oracleReadFailed(currency, err);
2579
2610
  }
@@ -2951,6 +2982,7 @@ function createCashClient(options) {
2951
2982
  ...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
2952
2983
  etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
2953
2984
  creationRateClient,
2985
+ upiCreationRateClient,
2954
2986
  ...options.relay ? { relay: options.relay } : {}
2955
2987
  });
2956
2988
  },
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-DimDWJU4.cjs';
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-DimDWJU4.js';
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var package_default = {
5
- version: "0.5.3"};
5
+ version: "0.5.4-rc.1"};
6
6
 
7
7
  // src/tools/index.ts
8
8
  var bigintString = {
package/dist/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var package_default = {
3
- version: "0.5.3"};
3
+ version: "0.5.4-rc.1"};
4
4
 
5
5
  // src/tools/index.ts
6
6
  var bigintString = {
@@ -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.4.10",
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,3 +1,4 @@
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
 
package/llms.txt CHANGED
@@ -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
+ This does not change the staging-only UPI opt-in gate.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkp2p/cash",
3
- "version": "0.5.3",
3
+ "version": "0.5.4-rc.1",
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": {
@@ -263,3 +263,21 @@ 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/INR reads the live Chainlink Polygon mainnet proxy
268
+ `0xDA0F8Df6F5dB15b346f4B8D1156722027E194E60` (chain 137), inverts
269
+ USD per INR, and rounds the creation-time maker floor up. Configure its
270
+ read-only RPC with `upiCreationRateRpcUrl` or `upiCreationRateTransport`.
271
+ Alipay/CNY retains the Ethereum registry and `creationRateRpcUrl` /
272
+ `creationRateTransport`. UPI rejects the wrong chain, invalid rounds, and
273
+ observations older than 24 hours; market closures do not bypass freshness.
274
+ This does not change the staging-only UPI opt-in gate.
275
+
276
+ Before a funded UPI QA run, call
277
+ `cash.estimate({ amount: 1000000n, platform: 'upi', currency: 'INR' }, { includeEta: false })`
278
+ using the intended live Polygon RPC. Require a positive finite rate,
279
+ `binding: 'deposit-creation'`, and `oracleUpdatedAt` no more than 86400 seconds
280
+ old and not in the future. Record the observation time and selected chain,
281
+ then stop before funding if the read fails. Unit-test fixtures prove routing,
282
+ not live availability. The authoritative feed listing is
283
+ https://data.chain.link/feeds/polygon/mainnet/inr-usd.