@zkp2p/pay-shared 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ /** Fiat payTo is a protocol hash; the saved quote owns the readable recipient. */
2
+ export function paymentRecoveryRecipient(payment) {
3
+ const quote = payment.quote;
4
+ if (typeof quote === 'object' && quote !== null && 'maker' in quote) {
5
+ const maker = quote.maker;
6
+ if (typeof maker === 'object' && maker !== null && 'depositData' in maker) {
7
+ const data = maker.depositData;
8
+ if (typeof data === 'object' && data !== null && 'offchainId' in data) {
9
+ const recipient = data.offchainId;
10
+ if (typeof recipient === 'string' && recipient.trim() !== '')
11
+ return recipient;
12
+ }
13
+ }
14
+ }
15
+ // Historical rows can lack maker data. Never present a bytes32 hash as a name.
16
+ return /^0x[\da-f]{64}$/i.test(payment.payTo) ? 'Recipient unavailable' : payment.payTo;
17
+ }
18
+ /** Old attempts without a snapshot have no reliable historical requested amount. */
19
+ export function paymentRecoveryAmount(payment) {
20
+ if (payment.status === 'SETTLED')
21
+ return Number(payment.paymentAmount) > 0 ? payment.paymentAmount : null;
22
+ const quote = payment.quote;
23
+ if (typeof quote !== 'object' || quote === null || !('recoveryDisplayAmount' in quote))
24
+ return null;
25
+ const amount = quote.recoveryDisplayAmount;
26
+ return typeof amount === 'string' && /^\d+(\.\d+)?$/.test(amount) && Number(amount) > 0 ? amount : null;
27
+ }
package/dist/rails.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ export declare const ZCASH_RAIL = "near_intents_133701";
1
2
  export declare enum SupportedRail {
3
+ NEAR_INTENTS_ZCASH = "near_intents_133701",
4
+ APPLE_PAY = "apple_pay",
2
5
  VENMO = "venmo",
3
6
  CASHAPP = "cashapp",
4
7
  REVOLUT = "revolut",
@@ -14,6 +17,7 @@ export declare enum SupportedRail {
14
17
  RELAY_137 = "relay_137",
15
18
  RELAY_480 = "relay_480",
16
19
  RELAY_999 = "relay_999",
20
+ RELAY_5042 = "relay_5042",
17
21
  RELAY_8453 = "relay_8453",
18
22
  RELAY_42161 = "relay_42161",
19
23
  RELAY_8253038 = "relay_8253038",
@@ -33,6 +37,7 @@ export declare enum SupportedRelayRail {
33
37
  RELAY_137 = "relay_137",
34
38
  RELAY_480 = "relay_480",
35
39
  RELAY_999 = "relay_999",
40
+ RELAY_5042 = "relay_5042",
36
41
  RELAY_8453 = "relay_8453",
37
42
  RELAY_42161 = "relay_42161",
38
43
  RELAY_8253038 = "relay_8253038",
@@ -43,6 +48,16 @@ export declare const DEFAULT_FIAT_RAILS: readonly SupportedRail[];
43
48
  export declare const SUPPORTED_RAILS: readonly SupportedRail[];
44
49
  export declare const SUPPORTED_RELAY_RAILS: readonly SupportedRelayRail[];
45
50
  export declare const FIAT_SUPPORTED_RAILS: readonly ["venmo", "cashapp", "revolut", "wise", "zelle", "paypal", "monzo", "n26", "chime"];
51
+ /**
52
+ * Coinbase Apple Pay. A third rail category: it is not a fiat rail (no fiat
53
+ * payment and no attestation) and not a crypto rail (the customer never sends
54
+ * crypto). Merchants enable it like any other method, but the resulting payment
55
+ * is recorded against the Relay rail it settles on, not against this key.
56
+ */
57
+ export declare const APPLE_PAY_RAIL = SupportedRail.APPLE_PAY;
58
+ export declare const APPLE_PAY_MAX_AMOUNT_USDC_BASE_UNITS = 2500000000n;
59
+ export declare const APPLE_PAY_LIMIT_MESSAGE = "This order exceeds the Apple Pay limit. Choose another payment method.";
60
+ export declare function isOnrampRail(rail: string | null | undefined): boolean;
46
61
  export type FiatSupportedRail = typeof FIAT_SUPPORTED_RAILS[number];
47
62
  export declare const PAYMENT_PLATFORM_LABELS: Record<string, string>;
48
63
  export declare const FIAT_RAIL_DISPLAY_NAMES: Record<FiatSupportedRail, string>;
@@ -82,9 +97,12 @@ export declare function setRuntimeDisabledFiatRails(rails: readonly string[]): v
82
97
  export declare function getRuntimeDisabledFiatRails(): string[];
83
98
  /** Returns true when the rail targets a crypto chain (legacy numeric or relay-prefixed format). */
84
99
  export declare function isCryptoRail(rail: string | null | undefined): boolean;
100
+ /** A rail the customer pays on with fiat: excludes crypto rails and onramp
101
+ * rails such as Apple Pay, whose payments settle on a Relay rail instead. */
102
+ export declare function isFiatPaymentRail(rail: string | null | undefined): boolean;
85
103
  /** Formats a chain id into the canonical relay rail string: `relay_<chainId>`. */
86
104
  export declare function formatRelayRail(chainId: number): string;
87
- /** Normalizes any crypto rail into canonical relay format (or null for non-crypto rails). */
105
+ /** Normalizes a crypto rail into its canonical provider format (or null for non-crypto rails). */
88
106
  export declare function normalizeCryptoRail(rail: string | null | undefined): string | null;
89
107
  /** Returns the canonical base fiat rail for base rails and supported variants such as `zelle-chase`. */
90
108
  export declare function normalizeFiatRail(rail: string | null | undefined): string | null;
@@ -104,7 +122,7 @@ export declare function isSarSupportedFiatRail(rail: string | null | undefined):
104
122
  * rails (e.g. `zelle`, `revolut`, incl. variants like `zelle-chase`) are dropped
105
123
  * while crypto rails and SAR rails (venmo/cashapp/wise/paypal) are kept.
106
124
  */
107
- export declare function filterRailsForExclusiveSar(rails: readonly string[]): string[];
125
+ export declare function filterRailsForExclusiveSar<T extends string>(rails: readonly T[]): T[];
108
126
  /** Canonical Zelle paymentMethodId -> attestation-service actionType suffix. */
109
127
  export declare const ZELLE_METHOD_ACTION_SUFFIX: Record<string, string>;
110
128
  /** Strict create-boundary set for Zelle method ids and transitional variant rails. */
package/dist/rails.js CHANGED
@@ -1,6 +1,10 @@
1
+ import { ZCASH_CHAIN_ID } from './crypto.js';
1
2
  const RELAY_RAIL_PREFIX = 'relay_';
3
+ export const ZCASH_RAIL = 'near_intents_133701';
2
4
  export var SupportedRail;
3
5
  (function (SupportedRail) {
6
+ SupportedRail["NEAR_INTENTS_ZCASH"] = "near_intents_133701";
7
+ SupportedRail["APPLE_PAY"] = "apple_pay";
4
8
  SupportedRail["VENMO"] = "venmo";
5
9
  SupportedRail["CASHAPP"] = "cashapp";
6
10
  SupportedRail["REVOLUT"] = "revolut";
@@ -16,6 +20,7 @@ export var SupportedRail;
16
20
  SupportedRail["RELAY_137"] = "relay_137";
17
21
  SupportedRail["RELAY_480"] = "relay_480";
18
22
  SupportedRail["RELAY_999"] = "relay_999";
23
+ SupportedRail["RELAY_5042"] = "relay_5042";
19
24
  SupportedRail["RELAY_8453"] = "relay_8453";
20
25
  SupportedRail["RELAY_42161"] = "relay_42161";
21
26
  SupportedRail["RELAY_8253038"] = "relay_8253038";
@@ -36,6 +41,7 @@ export var SupportedRelayRail;
36
41
  SupportedRelayRail["RELAY_137"] = "relay_137";
37
42
  SupportedRelayRail["RELAY_480"] = "relay_480";
38
43
  SupportedRelayRail["RELAY_999"] = "relay_999";
44
+ SupportedRelayRail["RELAY_5042"] = "relay_5042";
39
45
  SupportedRelayRail["RELAY_8453"] = "relay_8453";
40
46
  SupportedRelayRail["RELAY_42161"] = "relay_42161";
41
47
  SupportedRelayRail["RELAY_8253038"] = "relay_8253038";
@@ -68,6 +74,19 @@ export const FIAT_SUPPORTED_RAILS = [
68
74
  'n26',
69
75
  'chime',
70
76
  ];
77
+ /**
78
+ * Coinbase Apple Pay. A third rail category: it is not a fiat rail (no fiat
79
+ * payment and no attestation) and not a crypto rail (the customer never sends
80
+ * crypto). Merchants enable it like any other method, but the resulting payment
81
+ * is recorded against the Relay rail it settles on, not against this key.
82
+ */
83
+ export const APPLE_PAY_RAIL = SupportedRail.APPLE_PAY;
84
+ // Peer Pay per-order ceiling, in USDC base units. Coinbase may allow less per buyer.
85
+ export const APPLE_PAY_MAX_AMOUNT_USDC_BASE_UNITS = 2500000000n;
86
+ export const APPLE_PAY_LIMIT_MESSAGE = 'This order exceeds the Apple Pay limit. Choose another payment method.';
87
+ export function isOnrampRail(rail) {
88
+ return rail === APPLE_PAY_RAIL;
89
+ }
71
90
  const PAYMENT_PLATFORM_LABEL_ENTRIES = {
72
91
  [SupportedRail.VENMO]: 'Venmo',
73
92
  [SupportedRail.CASHAPP]: 'Cash App',
@@ -79,7 +98,10 @@ const PAYMENT_PLATFORM_LABEL_ENTRIES = {
79
98
  [SupportedRail.N26]: 'N26',
80
99
  [SupportedRail.CHIME]: 'Chime',
81
100
  };
82
- export const PAYMENT_PLATFORM_LABELS = PAYMENT_PLATFORM_LABEL_ENTRIES;
101
+ export const PAYMENT_PLATFORM_LABELS = {
102
+ ...PAYMENT_PLATFORM_LABEL_ENTRIES,
103
+ [SupportedRail.APPLE_PAY]: 'Apple Pay',
104
+ };
83
105
  export const FIAT_RAIL_DISPLAY_NAMES = PAYMENT_PLATFORM_LABEL_ENTRIES;
84
106
  // Shared FE/BE minimum so queries too short for the pg_trgm trigram index are no-ops.
85
107
  export const ORDER_SEARCH_MIN_QUERY_LENGTH = 3;
@@ -100,6 +122,7 @@ export const RELAY_CHAIN_DISPLAY_NAMES = {
100
122
  137: 'Polygon',
101
123
  480: 'World Chain',
102
124
  999: 'Hyperliquid',
125
+ 5042: 'Arc',
103
126
  8453: 'Base',
104
127
  42161: 'Arbitrum',
105
128
  8253038: 'Bitcoin',
@@ -107,6 +130,8 @@ export const RELAY_CHAIN_DISPLAY_NAMES = {
107
130
  792703809: 'Solana',
108
131
  };
109
132
  const CRYPTO_METHOD_ALIASES = [
133
+ 'zcash',
134
+ 'zec',
110
135
  'crypto',
111
136
  'usdc',
112
137
  'usdt',
@@ -134,20 +159,22 @@ export function parseCryptoRailChainId(rail) {
134
159
  return null;
135
160
  }
136
161
  const trimmed = rail.trim();
162
+ if (trimmed === ZCASH_RAIL)
163
+ return ZCASH_CHAIN_ID;
137
164
  if (trimmed === '') {
138
165
  return null;
139
166
  }
140
167
  const numericMatch = /^\d+$/.exec(trimmed);
141
168
  if (numericMatch !== null) {
142
169
  const parsed = Number(trimmed);
143
- return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
170
+ return Number.isSafeInteger(parsed) && parsed > 0 && parsed !== ZCASH_CHAIN_ID ? parsed : null;
144
171
  }
145
172
  const prefixedMatch = /^relay_(\d+)$/i.exec(trimmed);
146
173
  if (prefixedMatch === null) {
147
174
  return null;
148
175
  }
149
176
  const parsed = Number(prefixedMatch[1]);
150
- return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
177
+ return Number.isSafeInteger(parsed) && parsed > 0 && parsed !== ZCASH_CHAIN_ID ? parsed : null;
151
178
  }
152
179
  export function isSupportedRail(rail) {
153
180
  return SUPPORTED_RAIL_SET.has(rail);
@@ -195,6 +222,11 @@ export function getRuntimeDisabledFiatRails() {
195
222
  export function isCryptoRail(rail) {
196
223
  return parseCryptoRailChainId(rail) !== null;
197
224
  }
225
+ /** A rail the customer pays on with fiat: excludes crypto rails and onramp
226
+ * rails such as Apple Pay, whose payments settle on a Relay rail instead. */
227
+ export function isFiatPaymentRail(rail) {
228
+ return !isCryptoRail(rail) && !isOnrampRail(rail);
229
+ }
198
230
  /** Formats a chain id into the canonical relay rail string: `relay_<chainId>`. */
199
231
  export function formatRelayRail(chainId) {
200
232
  if (!Number.isSafeInteger(chainId) || chainId <= 0) {
@@ -202,8 +234,10 @@ export function formatRelayRail(chainId) {
202
234
  }
203
235
  return `${RELAY_RAIL_PREFIX}${chainId}`;
204
236
  }
205
- /** Normalizes any crypto rail into canonical relay format (or null for non-crypto rails). */
237
+ /** Normalizes a crypto rail into its canonical provider format (or null for non-crypto rails). */
206
238
  export function normalizeCryptoRail(rail) {
239
+ if (rail?.trim() === ZCASH_RAIL)
240
+ return ZCASH_RAIL;
207
241
  const chainId = parseCryptoRailChainId(rail);
208
242
  return chainId === null ? null : formatRelayRail(chainId);
209
243
  }
@@ -307,7 +341,11 @@ export function getRailDisplayName(rail) {
307
341
  if (trimmed === '') {
308
342
  return 'Unknown';
309
343
  }
344
+ if (trimmed === ZCASH_RAIL)
345
+ return 'Zcash (NEAR Intents)';
310
346
  const normalized = trimmed.toLowerCase();
347
+ if (isOnrampRail(normalized))
348
+ return PAYMENT_PLATFORM_LABELS[APPLE_PAY_RAIL];
311
349
  if (isFiatSupportedRail(normalized)) {
312
350
  return FIAT_RAIL_DISPLAY_NAMES[normalized];
313
351
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Applies a deterministic per-rail spread (±3%) on top of the live fiat-per-USD
3
+ * rate so sandbox quotes vary by payment method. The spread is hashed from the
4
+ * rail identifier + currency so refreshes are stable and different merchants
5
+ * see consistent rates, without any rail getting an unrealistic 0% spread.
6
+ */
7
+ export declare function applySandboxRailSpread(baseRate: number, rail: string, currency: string): number;
8
+ /** Deterministic sandbox maker spread and fee-share sizing, shared with the CLI. */
9
+ export declare function buildSandboxSplitFiatAmounts(input: {
10
+ principalUsd: string;
11
+ currencyPerUsdRate: string;
12
+ rail: string;
13
+ fiatCurrency: string;
14
+ buyerFeeShareBps: number;
15
+ totalReferralFeeBps: number;
16
+ }): {
17
+ paymentAmount: string;
18
+ grossUsdcAmount: string;
19
+ conversionRate: string;
20
+ signalIntentAmountBaseUnits: string;
21
+ };
22
+ /** Preserve the saved signal for a full payment; scale a partial fiat payment at its saved maker rate. */
23
+ export declare function settleSandboxSplitFiatAmounts(input: {
24
+ signalIntentAmountBaseUnits: string;
25
+ conversionRateDecimal: string;
26
+ paidFiatAmount?: string;
27
+ }): {
28
+ paymentAmount: string;
29
+ grossUsdcAmount: string;
30
+ };
@@ -0,0 +1,53 @@
1
+ import { computeDisplayedFiatAmount } from './fiatAmount.js';
2
+ const MAX_RAIL_SPREAD = 0.03; // ±3% around the live FX rate
3
+ const SPREAD_BUCKETS = 601; // integer granularity for the deterministic hash
4
+ /**
5
+ * Applies a deterministic per-rail spread (±3%) on top of the live fiat-per-USD
6
+ * rate so sandbox quotes vary by payment method. The spread is hashed from the
7
+ * rail identifier + currency so refreshes are stable and different merchants
8
+ * see consistent rates, without any rail getting an unrealistic 0% spread.
9
+ */
10
+ export function applySandboxRailSpread(baseRate, rail, currency) {
11
+ const seed = `${rail.trim().toLowerCase()}|${currency.trim().toUpperCase()}`;
12
+ let hash = 0;
13
+ for (let i = 0; i < seed.length; i += 1) {
14
+ hash = ((hash * 31) + seed.charCodeAt(i)) | 0;
15
+ }
16
+ const bucket = Math.abs(hash) % SPREAD_BUCKETS; // [0, 600]
17
+ const normalized = (bucket - (SPREAD_BUCKETS - 1) / 2) / ((SPREAD_BUCKETS - 1) / 2); // [-1, 1]
18
+ const spread = normalized * MAX_RAIL_SPREAD;
19
+ return baseRate * (1 + spread);
20
+ }
21
+ function decimalUnits(value, decimals) {
22
+ const [whole, fraction = ''] = value.split('.');
23
+ return BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction.slice(0, decimals).padEnd(decimals, '0'));
24
+ }
25
+ function usdFromUnits(units) {
26
+ return `${units / 1000000n}.${(units % 1000000n).toString().padStart(6, '0')}`;
27
+ }
28
+ /** Deterministic sandbox maker spread and fee-share sizing, shared with the CLI. */
29
+ export function buildSandboxSplitFiatAmounts(input) {
30
+ const rawRate = String(applySandboxRailSpread(Number(input.currencyPerUsdRate), input.rail, input.fiatCurrency));
31
+ const [whole, fraction = ''] = rawRate.split('.');
32
+ const conversionRate = fraction === '' ? whole : `${whole}.${fraction.slice(0, 6)}`;
33
+ const rate = decimalUnits(conversionRate, 18);
34
+ const peg = decimalUnits(input.currencyPerUsdRate, 18);
35
+ const share = BigInt(input.buyerFeeShareBps);
36
+ const denominator = (10000n - share) * rate * 10000n + share * (10000n - BigInt(input.totalReferralFeeBps)) * peg;
37
+ if (denominator <= 0n)
38
+ throw new Error('Split sandbox quote has no positive principal contribution');
39
+ const numerator = decimalUnits(input.principalUsd, 6) * 100000000n * peg;
40
+ const signalIntentAmountBaseUnits = ((numerator + denominator - 1n) / denominator).toString();
41
+ return { conversionRate, signalIntentAmountBaseUnits,
42
+ ...settleSandboxSplitFiatAmounts({ signalIntentAmountBaseUnits, conversionRateDecimal: conversionRate }) };
43
+ }
44
+ /** Preserve the saved signal for a full payment; scale a partial fiat payment at its saved maker rate. */
45
+ export function settleSandboxSplitFiatAmounts(input) {
46
+ const paymentAmount = input.paidFiatAmount ?? computeDisplayedFiatAmount({
47
+ feePayer: 'SPLIT', signalIntentAmountBaseUnits: input.signalIntentAmountBaseUnits,
48
+ conversionRateDecimal: input.conversionRateDecimal, remainingUsdcAmount: '1', currencyPerUsdRate: '1', paymentAmountFallback: '0',
49
+ });
50
+ const grossUnits = input.paidFiatAmount === undefined ? BigInt(input.signalIntentAmountBaseUnits)
51
+ : decimalUnits(paymentAmount, 18) * 1000000n / decimalUnits(input.conversionRateDecimal, 18);
52
+ return { paymentAmount, grossUsdcAmount: usdFromUnits(grossUnits) };
53
+ }
package/dist/tiers.d.ts CHANGED
@@ -4,6 +4,8 @@ export declare const TierChangeSource: {
4
4
  readonly SELF_SERVE: "SELF_SERVE";
5
5
  readonly ADMIN: "ADMIN";
6
6
  readonly MIGRATION: "MIGRATION";
7
+ /** A Master Merchant Account sub-merchant copied its Master Merchant Account's tier at creation. */
8
+ readonly SUB_MERCHANT: "SUB_MERCHANT";
7
9
  };
8
10
  export type TierChangeSourceType = typeof TierChangeSource[keyof typeof TierChangeSource];
9
11
  /**
@@ -60,7 +62,7 @@ export declare function canUseScanToPay(input: {
60
62
  readonly environment: MerchantEnvironmentType;
61
63
  }): boolean;
62
64
  /**
63
- * Formats a tier's monthly caps for display. `volumeUsdc` is a whole-USDC decimal string;
65
+ * Formats a tier's monthly caps for display; caps count fiat-paid orders only. `volumeUsdc` is a whole-USDC decimal string;
64
66
  * exact thousands render as `$10K`, anything else as `$2,500`.
65
67
  */
66
68
  export declare function formatTierMonthlyLimitsCopy(limits: TierMonthlyLimits | null): string;
package/dist/tiers.js CHANGED
@@ -3,6 +3,8 @@ export const TierChangeSource = {
3
3
  SELF_SERVE: 'SELF_SERVE',
4
4
  ADMIN: 'ADMIN',
5
5
  MIGRATION: 'MIGRATION',
6
+ /** A Master Merchant Account sub-merchant copied its Master Merchant Account's tier at creation. */
7
+ SUB_MERCHANT: 'SUB_MERCHANT',
6
8
  };
7
9
  export const TIER_DEFINITIONS = {
8
10
  BASE: {
@@ -50,7 +52,7 @@ export function canUseScanToPay(input) {
50
52
  return input.environment === 'SANDBOX' || input.tier === 'PRO' || input.tier === 'CONCIERGE';
51
53
  }
52
54
  /**
53
- * Formats a tier's monthly caps for display. `volumeUsdc` is a whole-USDC decimal string;
55
+ * Formats a tier's monthly caps for display; caps count fiat-paid orders only. `volumeUsdc` is a whole-USDC decimal string;
54
56
  * exact thousands render as `$10K`, anything else as `$2,500`.
55
57
  */
56
58
  export function formatTierMonthlyLimitsCopy(limits) {
@@ -61,5 +63,5 @@ export function formatTierMonthlyLimitsCopy(limits) {
61
63
  const volume = whole.length > 3 && /^\d+000$/.test(whole)
62
64
  ? `$${whole.slice(0, -3)}K`
63
65
  : `$${whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;
64
- return `Up to ${volume} volume and ${limits.orderCount} orders per month`;
66
+ return `Up to ${volume} fiat volume and ${limits.orderCount} fiat orders per month`;
65
67
  }