@zkp2p/pay-shared 1.0.0 → 3.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.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Shared TypeScript types, enums, and chain utilities used by ZKP2P Pay API, SDK,
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- npm install @zkp2p/pay-shared@1.0.0
12
+ npm install @zkp2p/pay-shared@3.0.0
13
13
  ```
14
14
 
15
15
  ## Published artifacts
@@ -66,7 +66,7 @@ import {
66
66
 
67
67
  const chainId = parseSupportedChainSelection('polygon'); // 137
68
68
  const usdc = resolveDestinationTokenAddress('USDC', 137); // Chain-specific USDC address
69
- const supportedAliases = getSupportedDestinationTokenAliasesForChain(BASE_CHAIN_ID); // ["USDC"]
69
+ const supportedAliases = getSupportedDestinationTokenAliasesForChain(BASE_CHAIN_ID); // ["USDC", "USDT"]
70
70
 
71
71
  console.log(getSupportedChainIds(), chainId, usdc, supportedAliases);
72
72
  ```
@@ -0,0 +1,5 @@
1
+ export declare function sha256(value: Uint8Array): Uint8Array;
2
+ export declare function doubleSha256(value: Uint8Array): Uint8Array;
3
+ export declare function decodeBase58(value: string): Uint8Array | null;
4
+ export declare function encodeBase58(value: Uint8Array): string;
5
+ export declare function hasBase58CheckChecksum(value: string, version: number): boolean;
@@ -0,0 +1,126 @@
1
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
2
+ const SHA256_CONSTANTS = new Uint32Array([
3
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
4
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
5
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
6
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
7
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
8
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
9
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
10
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
11
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
12
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
13
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
14
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
15
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
16
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
17
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
18
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
19
+ ]);
20
+ function rotateRight(value, shift) {
21
+ return (value >>> shift) | (value << (32 - shift));
22
+ }
23
+ export function sha256(value) {
24
+ const paddedLength = Math.ceil((value.length + 9) / 64) * 64;
25
+ const message = new Uint8Array(paddedLength);
26
+ message.set(value);
27
+ message[value.length] = 0x80;
28
+ const bitLength = BigInt(value.length) * 8n;
29
+ const view = new DataView(message.buffer);
30
+ view.setUint32(paddedLength - 8, Number(bitLength >> 32n), false);
31
+ view.setUint32(paddedLength - 4, Number(bitLength & 0xffffffffn), false);
32
+ const hash = new Uint32Array([
33
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
34
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
35
+ ]);
36
+ const words = new Uint32Array(64);
37
+ for (let offset = 0; offset < paddedLength; offset += 64) {
38
+ for (let index = 0; index < 16; index += 1) {
39
+ words[index] = view.getUint32(offset + (index * 4), false);
40
+ }
41
+ for (let index = 16; index < 64; index += 1) {
42
+ const previous15 = words[index - 15];
43
+ const previous2 = words[index - 2];
44
+ const sigma0 = rotateRight(previous15, 7) ^ rotateRight(previous15, 18) ^ (previous15 >>> 3);
45
+ const sigma1 = rotateRight(previous2, 17) ^ rotateRight(previous2, 19) ^ (previous2 >>> 10);
46
+ words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;
47
+ }
48
+ let [a, b, c, d, e, f, g, h] = hash;
49
+ for (let index = 0; index < 64; index += 1) {
50
+ const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
51
+ const choice = (e & f) ^ (~e & g);
52
+ const temporary1 = (h + sum1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0;
53
+ const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
54
+ const majority = (a & b) ^ (a & c) ^ (b & c);
55
+ const temporary2 = (sum0 + majority) >>> 0;
56
+ h = g;
57
+ g = f;
58
+ f = e;
59
+ e = (d + temporary1) >>> 0;
60
+ d = c;
61
+ c = b;
62
+ b = a;
63
+ a = (temporary1 + temporary2) >>> 0;
64
+ }
65
+ hash[0] = (hash[0] + a) >>> 0;
66
+ hash[1] = (hash[1] + b) >>> 0;
67
+ hash[2] = (hash[2] + c) >>> 0;
68
+ hash[3] = (hash[3] + d) >>> 0;
69
+ hash[4] = (hash[4] + e) >>> 0;
70
+ hash[5] = (hash[5] + f) >>> 0;
71
+ hash[6] = (hash[6] + g) >>> 0;
72
+ hash[7] = (hash[7] + h) >>> 0;
73
+ }
74
+ const digest = new Uint8Array(32);
75
+ const digestView = new DataView(digest.buffer);
76
+ hash.forEach((word, index) => digestView.setUint32(index * 4, word, false));
77
+ return digest;
78
+ }
79
+ export function doubleSha256(value) {
80
+ return sha256(sha256(value));
81
+ }
82
+ export function decodeBase58(value) {
83
+ let decodedValue = 0n;
84
+ for (const character of value) {
85
+ const digit = BASE58_ALPHABET.indexOf(character);
86
+ if (digit === -1) {
87
+ return null;
88
+ }
89
+ decodedValue = (decodedValue * 58n) + BigInt(digit);
90
+ }
91
+ const bytes = [];
92
+ while (decodedValue > 0n) {
93
+ bytes.push(Number(decodedValue & 0xffn));
94
+ decodedValue >>= 8n;
95
+ }
96
+ bytes.reverse();
97
+ const leadingZeroes = value.match(/^1*/)?.[0].length ?? 0;
98
+ const decoded = new Uint8Array(leadingZeroes + bytes.length);
99
+ decoded.set(bytes, leadingZeroes);
100
+ return decoded;
101
+ }
102
+ export function encodeBase58(value) {
103
+ let encodedValue = 0n;
104
+ for (const byte of value) {
105
+ encodedValue = (encodedValue << 8n) + BigInt(byte);
106
+ }
107
+ let encoded = '';
108
+ while (encodedValue > 0n) {
109
+ encoded = BASE58_ALPHABET[Number(encodedValue % 58n)] + encoded;
110
+ encodedValue /= 58n;
111
+ }
112
+ let leadingZeroes = 0;
113
+ while (leadingZeroes < value.length && value[leadingZeroes] === 0) {
114
+ leadingZeroes += 1;
115
+ }
116
+ return '1'.repeat(leadingZeroes) + encoded;
117
+ }
118
+ export function hasBase58CheckChecksum(value, version) {
119
+ const decoded = decodeBase58(value);
120
+ if (decoded === null || decoded.length !== 25 || decoded[0] !== version) {
121
+ return false;
122
+ }
123
+ const payload = decoded.subarray(0, 21);
124
+ const expectedChecksum = doubleSha256(payload).subarray(0, 4);
125
+ return decoded.subarray(21).every((byte, index) => byte === expectedChecksum[index]);
126
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isValidSolanaAddress(value: string): boolean;
2
+ export declare function isValidTronAddress(value: string): boolean;
@@ -0,0 +1,14 @@
1
+ import { decodeBase58, encodeBase58, hasBase58CheckChecksum, } from './addressEncoding.js';
2
+ export function isValidSolanaAddress(value) {
3
+ if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value)) {
4
+ return false;
5
+ }
6
+ const decoded = decodeBase58(value);
7
+ return decoded !== null
8
+ && decoded.length === 32
9
+ && encodeBase58(decoded) === value;
10
+ }
11
+ export function isValidTronAddress(value) {
12
+ return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(value)
13
+ && hasBase58CheckChecksum(value, 0x41);
14
+ }
package/dist/bitcoin.js CHANGED
@@ -1,13 +1,79 @@
1
- const BITCOIN_BASE58_ADDRESS_REGEX = /^[13][1-9A-HJ-NP-Za-km-z]{25,34}$/;
2
- const BITCOIN_BECH32_ADDRESS_REGEX = /^(bc1)[ac-hj-np-z02-9]{11,71}$/i;
1
+ import { hasBase58CheckChecksum } from './addressEncoding.js';
2
+ const BECH32_ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
3
+ const BECH32_CHECKSUM = 1;
4
+ const BECH32M_CHECKSUM = 0x2bc830a3;
5
+ function calculatePolymod(values) {
6
+ const generators = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
7
+ let checksum = 1;
8
+ for (const value of values) {
9
+ const highBits = checksum >>> 25;
10
+ checksum = (((checksum & 0x1ffffff) << 5) ^ value) >>> 0;
11
+ for (let index = 0; index < generators.length; index += 1) {
12
+ if (((highBits >>> index) & 1) !== 0) {
13
+ checksum = (checksum ^ generators[index]) >>> 0;
14
+ }
15
+ }
16
+ }
17
+ return checksum;
18
+ }
19
+ function convertWitnessProgram(values) {
20
+ const decoded = [];
21
+ let accumulator = 0;
22
+ let bits = 0;
23
+ for (const value of values) {
24
+ accumulator = (accumulator << 5) | value;
25
+ bits += 5;
26
+ while (bits >= 8) {
27
+ bits -= 8;
28
+ decoded.push((accumulator >>> bits) & 0xff);
29
+ }
30
+ }
31
+ if (bits >= 5 || ((accumulator << (8 - bits)) & 0xff) !== 0) {
32
+ return null;
33
+ }
34
+ return Uint8Array.from(decoded);
35
+ }
36
+ function isValidBech32Address(value) {
37
+ if (value.length > 90 || (value !== value.toLowerCase() && value !== value.toUpperCase())) {
38
+ return false;
39
+ }
40
+ const normalized = value.toLowerCase();
41
+ const separatorIndex = normalized.lastIndexOf('1');
42
+ if (separatorIndex !== 2 || normalized.slice(0, separatorIndex) !== 'bc') {
43
+ return false;
44
+ }
45
+ const data = [...normalized.slice(separatorIndex + 1)]
46
+ .map((character) => BECH32_ALPHABET.indexOf(character));
47
+ if (data.length < 7 || data.some((item) => item === -1)) {
48
+ return false;
49
+ }
50
+ const hrpValues = [
51
+ ...[...'bc'].map((character) => character.charCodeAt(0) >>> 5),
52
+ 0,
53
+ ...[...'bc'].map((character) => character.charCodeAt(0) & 31),
54
+ ];
55
+ const witnessVersion = data[0];
56
+ const polymod = calculatePolymod([...hrpValues, ...data]);
57
+ if (witnessVersion > 16
58
+ || (witnessVersion === 0 && polymod !== BECH32_CHECKSUM)
59
+ || (witnessVersion > 0 && polymod !== BECH32M_CHECKSUM)) {
60
+ return false;
61
+ }
62
+ const witnessProgram = convertWitnessProgram(data.slice(1, -6));
63
+ if (witnessProgram === null || witnessProgram.length < 2 || witnessProgram.length > 40) {
64
+ return false;
65
+ }
66
+ return witnessVersion !== 0 || witnessProgram.length === 20 || witnessProgram.length === 32;
67
+ }
3
68
  export function isValidBitcoinAddress(value) {
4
69
  const trimmed = value.trim();
5
70
  if (!trimmed) {
6
71
  return false;
7
72
  }
8
73
  if (trimmed.toLowerCase().startsWith('bc1')) {
9
- const isUniformCase = trimmed === trimmed.toLowerCase() || trimmed === trimmed.toUpperCase();
10
- return isUniformCase && BITCOIN_BECH32_ADDRESS_REGEX.test(trimmed);
74
+ return isValidBech32Address(trimmed);
11
75
  }
12
- return BITCOIN_BASE58_ADDRESS_REGEX.test(trimmed);
76
+ return /^[13][1-9A-HJ-NP-Za-km-z]{25,34}$/.test(trimmed)
77
+ && (hasBase58CheckChecksum(trimmed, 0x00)
78
+ || hasBase58CheckChecksum(trimmed, 0x05));
13
79
  }
@@ -0,0 +1,4 @@
1
+ export declare const ProofMode: {
2
+ readonly BUYER_TEE: "buyerTee";
3
+ };
4
+ export type ProofMode = typeof ProofMode[keyof typeof ProofMode];
@@ -0,0 +1,3 @@
1
+ export const ProofMode = {
2
+ BUYER_TEE: 'buyerTee',
3
+ };
package/dist/chains.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * Import this module instead of duplicating chain configs in each app.
6
6
  */
7
7
  export declare const BASE_CHAIN_ID = 8453;
8
+ export declare const ETHEREUM_CHAIN_ID = 1;
8
9
  export declare const HYPEREVM_CHAIN_ID = 999;
9
10
  export declare const SOLANA_CHAIN_ID = 792703809;
10
11
  export declare const SUPPORTED_DESTINATION_TOKEN_ALIASES: readonly ["USDC", "USDT"];
package/dist/chains.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  // Chain IDs
8
8
  export const BASE_CHAIN_ID = 8453;
9
+ export const ETHEREUM_CHAIN_ID = 1;
9
10
  export const HYPEREVM_CHAIN_ID = 999;
10
11
  export const SOLANA_CHAIN_ID = 792703809;
11
12
  export const SUPPORTED_DESTINATION_TOKEN_ALIASES = ['USDC', 'USDT'];
@@ -135,6 +136,10 @@ for (const chainId of SUPPORTED_CHAIN_IDS) {
135
136
  }
136
137
  }
137
138
  const DESTINATION_TOKEN_ADDRESSES = {
139
+ // Base
140
+ 8453: {
141
+ usdt: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',
142
+ },
138
143
  // Ethereum
139
144
  1: {
140
145
  usdt: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
package/dist/crypto.d.ts CHANGED
@@ -4,6 +4,12 @@ export declare const PAY_CRYPTO_TOKEN_SYMBOLS: readonly ["BTC", "USDC", "USDT",
4
4
  export type PayCryptoTokenSymbol = (typeof PAY_CRYPTO_TOKEN_SYMBOLS)[number];
5
5
  export declare const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS: PayCryptoTokenSymbol[];
6
6
  export declare const DEFAULT_ENABLED_DESTINATION_TOKENS: PayCryptoTokenSymbol[];
7
+ /**
8
+ * Display-order priority for the "Pay with crypto" chain dropdown.
9
+ * Chains not in this list fall back to the existing numeric chain-ID
10
+ * iteration order of PAY_CRYPTO_CHAIN_CONFIGS as a stable tiebreaker.
11
+ */
12
+ export declare const PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY: readonly number[];
7
13
  export declare const STABLECOIN_SYMBOLS: Set<"USDC" | "USDT" | "ETH" | "BNB" | "SOL" | "BTC" | "PYUSD" | "HYPE" | "WBTC" | "USDH">;
8
14
  export interface SupportedCryptoTokenConfig {
9
15
  chainId: number;
@@ -18,6 +24,31 @@ export declare function normalizePayCryptoTokenSymbol(value: string | null | und
18
24
  export declare function isStablecoinSymbol(symbol: string | null | undefined): boolean;
19
25
  export declare function getSupportedPayCryptoTokenConfigsForChain(chainId: number, enabledSymbols?: readonly string[] | null): SupportedCryptoTokenConfig[];
20
26
  export declare function getSupportedPayCryptoChainIds(enabledSymbols?: readonly string[] | null): number[];
27
+ /**
28
+ * Returns the union of tokens supported by any of `enabledChainIds`,
29
+ * sorted by the canonical pay-crypto display order
30
+ * (DEFAULT_ENABLED_PAY_CRYPTO_TOKENS order).
31
+ *
32
+ * When `enabledChainIds` is undefined, uses every supported chain.
33
+ * When `enabledChainIds` is an empty array, returns [].
34
+ */
35
+ export declare function getAvailablePayCryptoTokens(enabledChainIds?: readonly number[]): PayCryptoTokenSymbol[];
36
+ /**
37
+ * Returns the chain IDs that support `symbol`, optionally filtered
38
+ * to `enabledChainIds`, sorted by PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY
39
+ * with ascending numeric chain-ID order as the stable tiebreaker.
40
+ *
41
+ * When `enabledChainIds` is undefined, uses every supported chain.
42
+ * When `enabledChainIds` is an empty array, returns [].
43
+ */
44
+ export declare function getSupportedPayCryptoChainIdsForToken(symbol: PayCryptoTokenSymbol, enabledChainIds?: readonly number[]): number[];
45
+ /**
46
+ * Returns the display name for a chain used by the "Pay with crypto"
47
+ * flow. Resolves through PAY_CRYPTO_CHAIN_CONFIGS first so Bitcoin
48
+ * and Tron (which are not in MAINNET_CHAINS) get the correct label.
49
+ * Falls back to getChainName for chains not registered here.
50
+ */
51
+ export declare function getPayCryptoChainName(chainId: number): string;
21
52
  export declare function resolvePayCryptoTokenConfig(chainId: number, symbol: string | null | undefined): SupportedCryptoTokenConfig | null;
22
53
  /**
23
54
  * Reverse lookup of a token config by contract address on a specific chain.
package/dist/crypto.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MAINNET_CHAINS, } from './chains.js';
1
+ import { MAINNET_CHAINS, BASE_CHAIN_ID, ETHEREUM_CHAIN_ID, SOLANA_CHAIN_ID, getChainName, } from './chains.js';
2
2
  export const BITCOIN_CHAIN_ID = 8253038;
3
3
  export const TRON_CHAIN_ID = 728126428;
4
4
  export const PAY_CRYPTO_TOKEN_SYMBOLS = [
@@ -15,9 +15,9 @@ export const PAY_CRYPTO_TOKEN_SYMBOLS = [
15
15
  ];
16
16
  export const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS = [
17
17
  'BTC',
18
- 'USDC',
19
- 'USDT',
20
18
  'ETH',
19
+ 'USDT',
20
+ 'USDC',
21
21
  'PYUSD',
22
22
  'SOL',
23
23
  'BNB',
@@ -29,6 +29,17 @@ export const DEFAULT_ENABLED_DESTINATION_TOKENS = [
29
29
  'USDC',
30
30
  'USDT',
31
31
  ];
32
+ /**
33
+ * Display-order priority for the "Pay with crypto" chain dropdown.
34
+ * Chains not in this list fall back to the existing numeric chain-ID
35
+ * iteration order of PAY_CRYPTO_CHAIN_CONFIGS as a stable tiebreaker.
36
+ */
37
+ export const PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY = [
38
+ BITCOIN_CHAIN_ID,
39
+ ETHEREUM_CHAIN_ID,
40
+ SOLANA_CHAIN_ID,
41
+ BASE_CHAIN_ID,
42
+ ];
32
43
  export const STABLECOIN_SYMBOLS = new Set([
33
44
  'USDC',
34
45
  'USDT',
@@ -160,6 +171,60 @@ export function getSupportedPayCryptoChainIds(enabledSymbols) {
160
171
  .map(Number)
161
172
  .filter((chainId) => getSupportedPayCryptoTokenConfigsForChain(chainId, enabledSymbols).length > 0);
162
173
  }
174
+ /**
175
+ * Returns the union of tokens supported by any of `enabledChainIds`,
176
+ * sorted by the canonical pay-crypto display order
177
+ * (DEFAULT_ENABLED_PAY_CRYPTO_TOKENS order).
178
+ *
179
+ * When `enabledChainIds` is undefined, uses every supported chain.
180
+ * When `enabledChainIds` is an empty array, returns [].
181
+ */
182
+ export function getAvailablePayCryptoTokens(enabledChainIds) {
183
+ const chainIds = enabledChainIds === undefined
184
+ ? Object.keys(CHAIN_TOKEN_CONFIGS).map(Number)
185
+ : enabledChainIds;
186
+ const available = new Set();
187
+ for (const chainId of chainIds) {
188
+ const chainTokens = CHAIN_TOKEN_CONFIGS[chainId];
189
+ if (chainTokens === undefined)
190
+ continue;
191
+ for (const symbol of Object.keys(chainTokens)) {
192
+ available.add(symbol);
193
+ }
194
+ }
195
+ return DEFAULT_ENABLED_PAY_CRYPTO_TOKENS.filter((symbol) => available.has(symbol));
196
+ }
197
+ /**
198
+ * Returns the chain IDs that support `symbol`, optionally filtered
199
+ * to `enabledChainIds`, sorted by PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY
200
+ * with ascending numeric chain-ID order as the stable tiebreaker.
201
+ *
202
+ * When `enabledChainIds` is undefined, uses every supported chain.
203
+ * When `enabledChainIds` is an empty array, returns [].
204
+ */
205
+ export function getSupportedPayCryptoChainIdsForToken(symbol, enabledChainIds) {
206
+ const candidateChainIds = enabledChainIds === undefined
207
+ ? Object.keys(CHAIN_TOKEN_CONFIGS).map(Number)
208
+ : Array.from(new Set(enabledChainIds));
209
+ const supporting = candidateChainIds.filter((chainId) => (CHAIN_TOKEN_CONFIGS[chainId]?.[symbol] !== undefined));
210
+ const supportingSet = new Set(supporting);
211
+ const priorityPortion = PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY
212
+ .filter((chainId) => supportingSet.has(chainId));
213
+ const prioritySet = new Set(PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY);
214
+ const restPortion = supporting
215
+ .filter((chainId) => !prioritySet.has(chainId))
216
+ .sort((a, b) => a - b);
217
+ return [...priorityPortion, ...restPortion];
218
+ }
219
+ /**
220
+ * Returns the display name for a chain used by the "Pay with crypto"
221
+ * flow. Resolves through PAY_CRYPTO_CHAIN_CONFIGS first so Bitcoin
222
+ * and Tron (which are not in MAINNET_CHAINS) get the correct label.
223
+ * Falls back to getChainName for chains not registered here.
224
+ */
225
+ export function getPayCryptoChainName(chainId) {
226
+ return PAY_CRYPTO_CHAIN_CONFIGS[chainId]?.name ?? getChainName(chainId);
227
+ }
163
228
  export function resolvePayCryptoTokenConfig(chainId, symbol) {
164
229
  const normalizedSymbol = normalizePayCryptoTokenSymbol(symbol);
165
230
  if (!normalizedSymbol) {
@@ -0,0 +1,19 @@
1
+ export interface DisplayedFiatAmountInput {
2
+ readonly feePayer: 'MERCHANT' | 'PAYEE';
3
+ readonly signalIntentAmountBaseUnits: string | null;
4
+ readonly conversionRateDecimal: string | null;
5
+ readonly remainingUsdcAmount: string;
6
+ readonly currencyPerUsdRate: string;
7
+ readonly paymentAmountFallback: string;
8
+ }
9
+ /**
10
+ * The exact fiat amount the buyer is instructed to pay, as a decimal string.
11
+ * BYTE-IDENTICAL to checkoutData.ts mapCheckoutPaymentToQuote (lines 336-351):
12
+ * quotedFiatAmount (PAYEE ceil-cents, else null)
13
+ * ?? (validRemaining&Rate ? (remaining*rate).toFixed(2) : paymentAmount)
14
+ * Always returns a string; the API caller passes it to fiatMinorUnitsFromDisplayedAmount,
15
+ * which returns null for an unusable "0"/malformed value so the API fails SAR closed.
16
+ */
17
+ export declare function computeDisplayedFiatAmount(input: DisplayedFiatAmountInput): string;
18
+ /** Parse a 2-decimal (or fewer) positive decimal string to integer minor units. Null if non-positive/malformed/>2dp. */
19
+ export declare function fiatMinorUnitsFromDisplayedAmount(displayed: string): bigint | null;
@@ -0,0 +1,58 @@
1
+ const USDC_SCALE = 1000000n;
2
+ const RATE_SCALE = 10n ** 18n;
3
+ const CENT_SCALE = 100n;
4
+ const QUOTE_DENOMINATOR = USDC_SCALE * RATE_SCALE; // 1e6 * 1e18
5
+ function parseDecimalToScaledUnits(value, decimals) {
6
+ const normalized = value.trim();
7
+ if (!/^\d+(\.\d+)?$/.test(normalized))
8
+ return null;
9
+ const [whole, fraction = ''] = normalized.split('.');
10
+ const base = 10n ** BigInt(decimals);
11
+ const scaledFraction = fraction.slice(0, decimals).padEnd(decimals, '0');
12
+ return BigInt(whole) * base + BigInt(scaledFraction);
13
+ }
14
+ function ceilCentsFromSignalIntent(amountBaseUnits, conversionRateDecimal) {
15
+ if (!/^\d+$/.test(amountBaseUnits))
16
+ return null;
17
+ const units = BigInt(amountBaseUnits);
18
+ const rate = parseDecimalToScaledUnits(conversionRateDecimal, 18);
19
+ if (rate === null || rate <= 0n || units <= 0n)
20
+ return null;
21
+ const numerator = units * rate * CENT_SCALE;
22
+ return (numerator + QUOTE_DENOMINATOR - 1n) / QUOTE_DENOMINATOR; // ceil
23
+ }
24
+ function centsToDecimalString(cents) {
25
+ const whole = cents / CENT_SCALE;
26
+ const fraction = (cents % CENT_SCALE).toString().padStart(2, '0');
27
+ return `${whole.toString()}.${fraction}`;
28
+ }
29
+ /**
30
+ * The exact fiat amount the buyer is instructed to pay, as a decimal string.
31
+ * BYTE-IDENTICAL to checkoutData.ts mapCheckoutPaymentToQuote (lines 336-351):
32
+ * quotedFiatAmount (PAYEE ceil-cents, else null)
33
+ * ?? (validRemaining&Rate ? (remaining*rate).toFixed(2) : paymentAmount)
34
+ * Always returns a string; the API caller passes it to fiatMinorUnitsFromDisplayedAmount,
35
+ * which returns null for an unusable "0"/malformed value so the API fails SAR closed.
36
+ */
37
+ export function computeDisplayedFiatAmount(input) {
38
+ const quotedCents = input.feePayer === 'PAYEE' && input.signalIntentAmountBaseUnits !== null && input.conversionRateDecimal !== null
39
+ ? ceilCentsFromSignalIntent(input.signalIntentAmountBaseUnits, input.conversionRateDecimal)
40
+ : null;
41
+ if (quotedCents !== null)
42
+ return centsToDecimalString(quotedCents);
43
+ const remainingUsdc = Number(input.remainingUsdcAmount);
44
+ const rate = Number(input.currencyPerUsdRate);
45
+ if (Number.isFinite(remainingUsdc) && Number.isFinite(rate) && remainingUsdc > 0 && rate > 0) {
46
+ return (remainingUsdc * rate).toFixed(2);
47
+ }
48
+ return input.paymentAmountFallback;
49
+ }
50
+ /** Parse a 2-decimal (or fewer) positive decimal string to integer minor units. Null if non-positive/malformed/>2dp. */
51
+ export function fiatMinorUnitsFromDisplayedAmount(displayed) {
52
+ const normalized = displayed.trim();
53
+ if (!/^\d+(\.\d{1,2})?$/.test(normalized))
54
+ return null;
55
+ const [whole, fraction = ''] = normalized.split('.');
56
+ const cents = BigInt(whole) * CENT_SCALE + BigInt(fraction.padEnd(2, '0'));
57
+ return cents > 0n ? cents : null;
58
+ }
package/dist/index.d.ts CHANGED
@@ -3,4 +3,11 @@ export * from './chains.js';
3
3
  export * from './fees.js';
4
4
  export * from './crypto.js';
5
5
  export * from './rails.js';
6
+ export * from './buyerTee.js';
6
7
  export * from './bitcoin.js';
8
+ export * from './addressValidators.js';
9
+ export * from './fiatAmount.js';
10
+ export * from './paypalSarPhase.js';
11
+ export * from './paypalSarVerify.js';
12
+ export * from './payeeFeeCap.js';
13
+ export * from './onboarding.js';
package/dist/index.js CHANGED
@@ -3,4 +3,11 @@ export * from './chains.js';
3
3
  export * from './fees.js';
4
4
  export * from './crypto.js';
5
5
  export * from './rails.js';
6
+ export * from './buyerTee.js';
6
7
  export * from './bitcoin.js';
8
+ export * from './addressValidators.js';
9
+ export * from './fiatAmount.js';
10
+ export * from './paypalSarPhase.js';
11
+ export * from './paypalSarVerify.js';
12
+ export * from './payeeFeeCap.js';
13
+ export * from './onboarding.js';
@@ -0,0 +1,39 @@
1
+ import type { MerchantIntegrationPathValue, OnboardingStepState, OnboardingStepVerifiedByValue } from './types.js';
2
+ export type OnboardingFacts = {
3
+ hasName: boolean;
4
+ hasLogo: boolean;
5
+ hasIndustry: boolean;
6
+ hasIntegrationPath: boolean;
7
+ hasWebhook: boolean;
8
+ hasSandboxOrder: boolean;
9
+ hasSandboxFulfilledOrder: boolean;
10
+ hasLiveOrder: boolean;
11
+ hasLiveFulfilledOrder: boolean;
12
+ manualSteps: readonly string[];
13
+ };
14
+ export declare const OnboardingStepId: {
15
+ readonly PROFILE: "profile";
16
+ readonly INDUSTRY: "industry";
17
+ readonly PATH: "path";
18
+ readonly RAILS: "rails";
19
+ readonly INTEGRATION: "integration";
20
+ readonly WEBHOOK: "webhook";
21
+ readonly GO_LIVE: "goLive";
22
+ readonly DOWNLOAD_PLUGIN: "downloadPlugin";
23
+ readonly PLUGIN_API_KEY: "pluginApiKey";
24
+ readonly TEST_ORDER: "testOrder";
25
+ readonly FIRST_QR: "firstQr";
26
+ readonly LINK_BOT: "linkBot";
27
+ readonly REQUEST_SETUP: "requestSetup";
28
+ };
29
+ export type OnboardingStepIdValue = typeof OnboardingStepId[keyof typeof OnboardingStepId];
30
+ export type OnboardingStepRegistryEntry = {
31
+ readonly id: OnboardingStepIdValue;
32
+ readonly paths: readonly MerchantIntegrationPathValue[] | 'ALL';
33
+ readonly verifiedBy: OnboardingStepVerifiedByValue;
34
+ readonly inChecklist: boolean;
35
+ readonly impliesPrevious: boolean;
36
+ };
37
+ export declare const ONBOARDING_STEP_REGISTRY: readonly OnboardingStepRegistryEntry[];
38
+ export declare function resolveOnboardingSteps(facts: OnboardingFacts, path: MerchantIntegrationPathValue | null): OnboardingStepState[];
39
+ export declare function isOnboardingComplete(steps: readonly OnboardingStepState[]): boolean;