@hbar-kit/payments 0.1.3 → 0.2.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
@@ -21,3 +21,44 @@ if (result.matched) {
21
21
  Statuses: `confirmed | pending | underpaid | overpaid | duplicate | mismatch | expired | failed`.
22
22
  A non-match is a result (with `reason`), not a thrown error. See the
23
23
  [docs](https://github.com/devwhodevs/hbar-kit).
24
+
25
+ ## USDC
26
+
27
+ USDC on Hedera is an HTS token, so `verifyUsdcPayment` / `waitForUsdcPayment` are thin wrappers over
28
+ `verifyHtsPayment` / `waitForHtsPayment` that fill in the **verified USDC token id** for the network
29
+ and parse amounts at **6 decimals**.
30
+
31
+ ```ts
32
+ import { verifyUsdcPayment } from "@hbar-kit/payments"
33
+
34
+ const result = await verifyUsdcPayment({
35
+ network: "mainnet", // required — USDC token ids are network-specific (no default)
36
+ receiver: "0.0.12345",
37
+ amount: "25.00", // decimal string, 6 decimals; ">6 dp" throws InvalidAmountError
38
+ memo: "invoice_123",
39
+ after: new Date(Date.now() - 30 * 60 * 1000),
40
+ })
41
+ // result.asset === { tokenId: "0.0.456858", decimals: 6, symbol: "USDC" }
42
+ ```
43
+
44
+ - **When to use the USDC helper vs `verifyHtsPayment`:** use `verifyUsdcPayment` when you're
45
+ accepting USDC and want the token id + 6-decimal parsing handled for you; use `verifyHtsPayment`
46
+ for any other token or when you need full control over `tokenId`/`decimals`.
47
+ - **Verified token ids:** mainnet `0.0.456858`, testnet `0.0.429274` (verified against the Mirror
48
+ Node and Circle's official docs); `previewnet` throws `UnsupportedAssetError`. Also exported as
49
+ `USDC_TOKEN_IDS` / `getUsdcTokenId(network)`.
50
+ - **Precision:** amounts are decimal strings, never floats; USDC is always 6 decimals.
51
+ - **Custom / mock token:** pass an explicit `tokenId` (still parsed at 6 decimals) for dev/testnet:
52
+
53
+ ```ts
54
+ await verifyUsdcPayment({
55
+ network: "testnet",
56
+ tokenId: process.env.TESTNET_USDC_TOKEN_ID!,
57
+ receiver: "0.0.12345",
58
+ amount: "10.00",
59
+ memo: "test_invoice_1",
60
+ })
61
+ ```
62
+
63
+ Use `isUsdcPaymentResult(result)` to detect USDC results. Full guide:
64
+ [Verify a USDC payment](https://devwhodevs.github.io/hbar-kit/guide/verify-usdc-payment).
package/dist/index.cjs CHANGED
@@ -148,6 +148,39 @@ async function verifyHtsPayment(p) {
148
148
  }
149
149
  return runVerify(p, { tokenId: p.tokenId, decimals }, core.parseUnits(p.amount, decimals), decimals);
150
150
  }
151
+ var USDC_DECIMALS = 6;
152
+ var USDC_SYMBOL = "USDC";
153
+ var USDC_TOKEN_IDS = {
154
+ mainnet: "0.0.456858",
155
+ testnet: "0.0.429274",
156
+ previewnet: void 0
157
+ };
158
+ function getUsdcTokenId(network) {
159
+ const tokenId = USDC_TOKEN_IDS[network];
160
+ if (tokenId === void 0) {
161
+ throw new core.UnsupportedAssetError(
162
+ `USDC has no verified token id on Hedera ${network}. Pass an explicit \`tokenId\` to verify a custom token, or use "mainnet" or "testnet".`,
163
+ { docsPath: "/guide/verify-usdc-payment" }
164
+ );
165
+ }
166
+ return tokenId;
167
+ }
168
+ async function verifyUsdcPayment(p) {
169
+ if (!p.network) {
170
+ throw new core.InvalidParamsError(
171
+ "`network` is required for USDC verification \u2014 USDC token ids and explorer URLs are network-specific",
172
+ { docsPath: "/guide/verify-usdc-payment" }
173
+ );
174
+ }
175
+ const tokenId = p.tokenId ?? getUsdcTokenId(p.network);
176
+ core.assertEntityId(tokenId);
177
+ const result = await verifyHtsPayment({ ...p, tokenId, decimals: USDC_DECIMALS });
178
+ const asset = result.asset === "HBAR" ? result.asset : { ...result.asset, symbol: USDC_SYMBOL };
179
+ return { ...result, asset };
180
+ }
181
+ function isUsdcPaymentResult(result) {
182
+ return typeof result.asset === "object" && result.asset.symbol === USDC_SYMBOL;
183
+ }
151
184
 
152
185
  // src/wait.ts
153
186
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -178,14 +211,23 @@ function waitForHbarPayment(p) {
178
211
  function waitForHtsPayment(p) {
179
212
  return poll(() => verifyHtsPayment(p), p);
180
213
  }
214
+ function waitForUsdcPayment(p) {
215
+ return poll(() => verifyUsdcPayment(p), p);
216
+ }
181
217
 
218
+ exports.USDC_DECIMALS = USDC_DECIMALS;
219
+ exports.USDC_TOKEN_IDS = USDC_TOKEN_IDS;
182
220
  exports.classifyAmount = classifyAmount;
221
+ exports.getUsdcTokenId = getUsdcTokenId;
183
222
  exports.hashscanTxUrl = hashscanTxUrl;
223
+ exports.isUsdcPaymentResult = isUsdcPaymentResult;
184
224
  exports.memoMatches = memoMatches;
185
225
  exports.netToReceiver = netToReceiver;
186
226
  exports.verifyHbarPayment = verifyHbarPayment;
187
227
  exports.verifyHtsPayment = verifyHtsPayment;
228
+ exports.verifyUsdcPayment = verifyUsdcPayment;
188
229
  exports.waitForHbarPayment = waitForHbarPayment;
189
230
  exports.waitForHtsPayment = waitForHtsPayment;
231
+ exports.waitForUsdcPayment = waitForUsdcPayment;
190
232
  //# sourceMappingURL=index.cjs.map
191
233
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/match.ts","../src/explorer.ts","../src/verify.ts","../src/wait.ts"],"names":["NETWORKS","txIdToMirror","createMirrorClient","InvalidParamsError","formatUnits","normalizeTransaction","parseHbar","parseUnits"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Transaction } from \"@hbar-kit/mirror\"\nimport type { MemoComparison, PaymentAsset } from \"./types.js\"\n\n/** Signed sum of ALL the receiver's legs (HBAR transfers or a specific token's transfers). */\nexport function netToReceiver(tx: Transaction, receiver: string, asset: PaymentAsset): bigint {\n if (asset === \"HBAR\") {\n return tx.transfers.filter((t) => t.account === receiver).reduce((s, t) => s + t.amount, 0n)\n }\n return tx.tokenTransfers\n .filter((t) => t.tokenId === asset.tokenId && t.account === receiver)\n .reduce((s, t) => s + t.amount, 0n)\n}\n\nexport function memoMatches(actual: string, expected: string, cmp: MemoComparison = {}): boolean {\n const mode = cmp.mode ?? \"exact\"\n if (mode === \"trim\") return actual.trim() === expected.trim()\n if (mode === \"caseInsensitive\") return actual.toLowerCase() === expected.toLowerCase()\n return actual === expected\n}\n\nexport type AmountClass = \"exact\" | \"underpaid\" | \"overpaid\"\nexport function classifyAmount(net: bigint, expected: bigint): AmountClass {\n if (net === expected) return \"exact\"\n return net < expected ? \"underpaid\" : \"overpaid\"\n}\n","import { NETWORKS, txIdToMirror, type HederaNetwork } from \"@hbar-kit/core\"\n\n/** HashScan transaction link: consensus timestamp in path, tx id as ?tid query param. */\nexport function hashscanTxUrl(\n network: HederaNetwork,\n consensusTimestamp: string,\n transactionId: string,\n): string {\n return `${NETWORKS[network].hashscan}/transaction/${consensusTimestamp}?tid=${txIdToMirror(transactionId)}`\n}\n","import {\n parseUnits,\n parseHbar,\n formatUnits,\n type HederaNetwork,\n type NetworkInput,\n InvalidParamsError,\n} from \"@hbar-kit/core\"\nimport {\n createMirrorClient,\n normalizeTransaction,\n type MirrorClient,\n type RawTransaction,\n type Transaction,\n} from \"@hbar-kit/mirror\"\nimport { netToReceiver, memoMatches, classifyAmount } from \"./match.js\"\nimport { hashscanTxUrl } from \"./explorer.js\"\nimport type { MemoComparison, PaymentAsset, PaymentMatch, PaymentResult } from \"./types.js\"\n\nexport interface VerifyBaseParams extends NetworkInput {\n client?: MirrorClient\n receiver: string\n amount: string\n memo?: string\n memoComparison?: MemoComparison\n comparison?: \"exact\" | \"atLeast\"\n after?: Date | string\n before?: Date | string\n}\nexport type VerifyHbarParams = VerifyBaseParams\nexport interface VerifyHtsParams extends VerifyBaseParams {\n tokenId: string\n decimals?: number\n}\n\nconst tokenDecimalsCache = new Map<string, number>()\n\nfunction resolveClient(p: VerifyBaseParams): { client: MirrorClient; network: HederaNetwork } {\n return {\n client: p.client ?? createMirrorClient(p),\n network: (p.network ?? \"mainnet\") as HederaNetwork,\n }\n}\n\nasync function runVerify(\n p: VerifyBaseParams,\n asset: PaymentAsset,\n expectedBase: bigint,\n decimals: number,\n): Promise<PaymentResult> {\n if (p.after && p.before && new Date(p.after) > new Date(p.before)) {\n throw new InvalidParamsError(\"`after` must be before `before`\")\n }\n const { client, network } = resolveClient(p)\n const tokenId = asset === \"HBAR\" ? undefined : asset.tokenId\n\n const candidates: PaymentMatch[] = []\n const collect = (items: Transaction[]) => {\n for (const tx of items) {\n if (tx.result !== \"SUCCESS\") continue\n const net = netToReceiver(tx, p.receiver, asset)\n if (net <= 0n) continue\n if (tokenId && !tx.tokenTransfers.some((t) => t.tokenId === tokenId)) continue\n const payer =\n tx.transfers.find((t) => t.amount < 0n)?.account ??\n tx.tokenTransfers.find((t) => t.amount < 0n)?.account\n const match: PaymentMatch = {\n transactionId: tx.transactionId,\n consensusTimestamp: tx.consensusTimestamp.raw,\n netBase: net,\n net: formatUnits(net, decimals),\n memo: tx.memo,\n transaction: tx,\n }\n if (payer !== undefined) match.payer = payer\n candidates.push(match)\n }\n }\n\n const findParams: Parameters<typeof client.transactions.find>[0] = {\n accountId: p.receiver,\n transactionType: \"cryptotransfer\",\n result: \"success\",\n order: \"desc\",\n }\n if (p.after !== undefined) findParams.after = p.after\n if (p.before !== undefined) findParams.before = p.before\n let page = await client.transactions.find(findParams)\n collect(page.items)\n while (page.next) {\n const body = (await client.transport.get(page.next)) as {\n transactions?: RawTransaction[]\n links?: { next: string | null }\n }\n collect((body.transactions ?? []).map(normalizeTransaction))\n page = { items: [], next: body.links?.next ?? null }\n }\n\n const memoFiltered = p.memo\n ? candidates.filter((c) => memoMatches(c.memo, p.memo!, p.memoComparison))\n : candidates\n\n const fail = (status: PaymentResult[\"status\"], reason: string): PaymentResult => ({\n matched: false,\n status,\n receiver: p.receiver,\n asset,\n matches: memoFiltered,\n reason,\n })\n if (candidates.length === 0)\n return fail(\"pending\", \"no matching transactions for receiver in window\")\n if (p.memo && memoFiltered.length === 0)\n return fail(\"mismatch\", \"no transaction matched the expected memo\")\n\n const wantAtLeast = p.comparison === \"atLeast\"\n const satisfying = memoFiltered.filter((c) =>\n wantAtLeast ? c.netBase >= expectedBase : c.netBase === expectedBase,\n )\n\n const resultFrom = (\n m: PaymentMatch,\n status: PaymentResult[\"status\"],\n matched: boolean,\n matches: PaymentMatch[],\n reason?: string,\n ): PaymentResult => {\n const result: PaymentResult = {\n matched,\n status,\n receiver: p.receiver,\n asset,\n transactionId: m.transactionId,\n amountBase: m.netBase,\n amount: m.net,\n memo: m.memo,\n consensusTimestamp: m.consensusTimestamp,\n explorerUrl: hashscanTxUrl(network, m.consensusTimestamp, m.transactionId),\n matches,\n }\n if (m.payer !== undefined) result.payer = m.payer\n if (reason !== undefined) result.reason = reason\n return result\n }\n\n if (satisfying.length === 0) {\n const best = memoFiltered[0]!\n const cls = classifyAmount(best.netBase, expectedBase)\n return resultFrom(\n best,\n cls === \"exact\" ? \"confirmed\" : cls,\n false,\n memoFiltered,\n `amount ${cls}`,\n )\n }\n if (satisfying.length > 1) {\n return resultFrom(\n satisfying[0]!,\n \"duplicate\",\n false,\n satisfying,\n `${satisfying.length} transactions satisfy this request`,\n )\n }\n return resultFrom(satisfying[0]!, \"confirmed\", true, satisfying)\n}\n\nexport async function verifyHbarPayment(p: VerifyHbarParams): Promise<PaymentResult> {\n return runVerify(p, \"HBAR\", parseHbar(p.amount), 8)\n}\n\nexport async function verifyHtsPayment(p: VerifyHtsParams): Promise<PaymentResult> {\n let decimals = p.decimals\n if (decimals === undefined) {\n const cached = tokenDecimalsCache.get(p.tokenId)\n if (cached !== undefined) decimals = cached\n else {\n const { client } = resolveClient(p)\n decimals = (await client.tokens.get(p.tokenId)).decimals\n tokenDecimalsCache.set(p.tokenId, decimals)\n }\n }\n return runVerify(p, { tokenId: p.tokenId, decimals }, parseUnits(p.amount, decimals), decimals)\n}\n","import {\n verifyHbarPayment,\n verifyHtsPayment,\n type VerifyHbarParams,\n type VerifyHtsParams,\n} from \"./verify.js\"\nimport type { PaymentResult } from \"./types.js\"\n\nexport interface WaitOptions {\n timeoutMs?: number\n pollIntervalMs?: number\n signal?: AbortSignal\n}\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\nasync function poll(\n verify: () => Promise<PaymentResult>,\n opts: WaitOptions,\n): Promise<PaymentResult> {\n const timeoutMs = opts.timeoutMs ?? 10 * 60 * 1000\n const pollIntervalMs = opts.pollIntervalMs ?? 3000\n const deadline = Date.now() + timeoutMs\n let last: PaymentResult | undefined\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) break\n last = await verify()\n if (last.matched) return last\n if (last.status === \"duplicate\" || last.status === \"overpaid\") return last\n await sleep(pollIntervalMs)\n }\n return last && last.status !== \"pending\"\n ? { ...last, status: \"expired\" }\n : {\n matched: false,\n status: \"expired\",\n receiver: last?.receiver ?? \"\",\n asset: last?.asset ?? \"HBAR\",\n matches: [],\n reason: \"timed out waiting for payment\",\n }\n}\n\nexport function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHbarPayment(p), p)\n}\nexport function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHtsPayment(p), p)\n}\n"]}
1
+ {"version":3,"sources":["../src/match.ts","../src/explorer.ts","../src/verify.ts","../src/usdc.ts","../src/wait.ts"],"names":["NETWORKS","txIdToMirror","createMirrorClient","InvalidParamsError","formatUnits","normalizeTransaction","parseHbar","parseUnits","UnsupportedAssetError","assertEntityId"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Transaction } from \"@hbar-kit/mirror\"\nimport type { MemoComparison, PaymentAsset } from \"./types.js\"\n\n/** Signed sum of ALL the receiver's legs (HBAR transfers or a specific token's transfers). */\nexport function netToReceiver(tx: Transaction, receiver: string, asset: PaymentAsset): bigint {\n if (asset === \"HBAR\") {\n return tx.transfers.filter((t) => t.account === receiver).reduce((s, t) => s + t.amount, 0n)\n }\n return tx.tokenTransfers\n .filter((t) => t.tokenId === asset.tokenId && t.account === receiver)\n .reduce((s, t) => s + t.amount, 0n)\n}\n\nexport function memoMatches(actual: string, expected: string, cmp: MemoComparison = {}): boolean {\n const mode = cmp.mode ?? \"exact\"\n if (mode === \"trim\") return actual.trim() === expected.trim()\n if (mode === \"caseInsensitive\") return actual.toLowerCase() === expected.toLowerCase()\n return actual === expected\n}\n\nexport type AmountClass = \"exact\" | \"underpaid\" | \"overpaid\"\nexport function classifyAmount(net: bigint, expected: bigint): AmountClass {\n if (net === expected) return \"exact\"\n return net < expected ? \"underpaid\" : \"overpaid\"\n}\n","import { NETWORKS, txIdToMirror, type HederaNetwork } from \"@hbar-kit/core\"\n\n/** HashScan transaction link: consensus timestamp in path, tx id as ?tid query param. */\nexport function hashscanTxUrl(\n network: HederaNetwork,\n consensusTimestamp: string,\n transactionId: string,\n): string {\n return `${NETWORKS[network].hashscan}/transaction/${consensusTimestamp}?tid=${txIdToMirror(transactionId)}`\n}\n","import {\n parseUnits,\n parseHbar,\n formatUnits,\n type HederaNetwork,\n type NetworkInput,\n InvalidParamsError,\n} from \"@hbar-kit/core\"\nimport {\n createMirrorClient,\n normalizeTransaction,\n type MirrorClient,\n type RawTransaction,\n type Transaction,\n} from \"@hbar-kit/mirror\"\nimport { netToReceiver, memoMatches, classifyAmount } from \"./match.js\"\nimport { hashscanTxUrl } from \"./explorer.js\"\nimport type { MemoComparison, PaymentAsset, PaymentMatch, PaymentResult } from \"./types.js\"\n\nexport interface VerifyBaseParams extends NetworkInput {\n client?: MirrorClient\n receiver: string\n amount: string\n memo?: string\n memoComparison?: MemoComparison\n comparison?: \"exact\" | \"atLeast\"\n after?: Date | string\n before?: Date | string\n}\nexport type VerifyHbarParams = VerifyBaseParams\nexport interface VerifyHtsParams extends VerifyBaseParams {\n tokenId: string\n decimals?: number\n}\n\nconst tokenDecimalsCache = new Map<string, number>()\n\nfunction resolveClient(p: VerifyBaseParams): { client: MirrorClient; network: HederaNetwork } {\n return {\n client: p.client ?? createMirrorClient(p),\n network: (p.network ?? \"mainnet\") as HederaNetwork,\n }\n}\n\nasync function runVerify(\n p: VerifyBaseParams,\n asset: PaymentAsset,\n expectedBase: bigint,\n decimals: number,\n): Promise<PaymentResult> {\n if (p.after && p.before && new Date(p.after) > new Date(p.before)) {\n throw new InvalidParamsError(\"`after` must be before `before`\")\n }\n const { client, network } = resolveClient(p)\n const tokenId = asset === \"HBAR\" ? undefined : asset.tokenId\n\n const candidates: PaymentMatch[] = []\n const collect = (items: Transaction[]) => {\n for (const tx of items) {\n if (tx.result !== \"SUCCESS\") continue\n const net = netToReceiver(tx, p.receiver, asset)\n if (net <= 0n) continue\n if (tokenId && !tx.tokenTransfers.some((t) => t.tokenId === tokenId)) continue\n const payer =\n tx.transfers.find((t) => t.amount < 0n)?.account ??\n tx.tokenTransfers.find((t) => t.amount < 0n)?.account\n const match: PaymentMatch = {\n transactionId: tx.transactionId,\n consensusTimestamp: tx.consensusTimestamp.raw,\n netBase: net,\n net: formatUnits(net, decimals),\n memo: tx.memo,\n transaction: tx,\n }\n if (payer !== undefined) match.payer = payer\n candidates.push(match)\n }\n }\n\n const findParams: Parameters<typeof client.transactions.find>[0] = {\n accountId: p.receiver,\n transactionType: \"cryptotransfer\",\n result: \"success\",\n order: \"desc\",\n }\n if (p.after !== undefined) findParams.after = p.after\n if (p.before !== undefined) findParams.before = p.before\n let page = await client.transactions.find(findParams)\n collect(page.items)\n while (page.next) {\n const body = (await client.transport.get(page.next)) as {\n transactions?: RawTransaction[]\n links?: { next: string | null }\n }\n collect((body.transactions ?? []).map(normalizeTransaction))\n page = { items: [], next: body.links?.next ?? null }\n }\n\n const memoFiltered = p.memo\n ? candidates.filter((c) => memoMatches(c.memo, p.memo!, p.memoComparison))\n : candidates\n\n const fail = (status: PaymentResult[\"status\"], reason: string): PaymentResult => ({\n matched: false,\n status,\n receiver: p.receiver,\n asset,\n matches: memoFiltered,\n reason,\n })\n if (candidates.length === 0)\n return fail(\"pending\", \"no matching transactions for receiver in window\")\n if (p.memo && memoFiltered.length === 0)\n return fail(\"mismatch\", \"no transaction matched the expected memo\")\n\n const wantAtLeast = p.comparison === \"atLeast\"\n const satisfying = memoFiltered.filter((c) =>\n wantAtLeast ? c.netBase >= expectedBase : c.netBase === expectedBase,\n )\n\n const resultFrom = (\n m: PaymentMatch,\n status: PaymentResult[\"status\"],\n matched: boolean,\n matches: PaymentMatch[],\n reason?: string,\n ): PaymentResult => {\n const result: PaymentResult = {\n matched,\n status,\n receiver: p.receiver,\n asset,\n transactionId: m.transactionId,\n amountBase: m.netBase,\n amount: m.net,\n memo: m.memo,\n consensusTimestamp: m.consensusTimestamp,\n explorerUrl: hashscanTxUrl(network, m.consensusTimestamp, m.transactionId),\n matches,\n }\n if (m.payer !== undefined) result.payer = m.payer\n if (reason !== undefined) result.reason = reason\n return result\n }\n\n if (satisfying.length === 0) {\n const best = memoFiltered[0]!\n const cls = classifyAmount(best.netBase, expectedBase)\n return resultFrom(\n best,\n cls === \"exact\" ? \"confirmed\" : cls,\n false,\n memoFiltered,\n `amount ${cls}`,\n )\n }\n if (satisfying.length > 1) {\n return resultFrom(\n satisfying[0]!,\n \"duplicate\",\n false,\n satisfying,\n `${satisfying.length} transactions satisfy this request`,\n )\n }\n return resultFrom(satisfying[0]!, \"confirmed\", true, satisfying)\n}\n\nexport async function verifyHbarPayment(p: VerifyHbarParams): Promise<PaymentResult> {\n return runVerify(p, \"HBAR\", parseHbar(p.amount), 8)\n}\n\nexport async function verifyHtsPayment(p: VerifyHtsParams): Promise<PaymentResult> {\n let decimals = p.decimals\n if (decimals === undefined) {\n const cached = tokenDecimalsCache.get(p.tokenId)\n if (cached !== undefined) decimals = cached\n else {\n const { client } = resolveClient(p)\n decimals = (await client.tokens.get(p.tokenId)).decimals\n tokenDecimalsCache.set(p.tokenId, decimals)\n }\n }\n return runVerify(p, { tokenId: p.tokenId, decimals }, parseUnits(p.amount, decimals), decimals)\n}\n","import {\n assertEntityId,\n InvalidParamsError,\n UnsupportedAssetError,\n type HederaNetwork,\n} from \"@hbar-kit/core\"\nimport { verifyHtsPayment, type VerifyHtsParams } from \"./verify.js\"\nimport type { PaymentAsset, PaymentResult } from \"./types.js\"\n\n/** USDC uses 6 decimals on every network Circle issues it on, including Hedera. */\nexport const USDC_DECIMALS = 6\nconst USDC_SYMBOL = \"USDC\"\n\n/**\n * Canonical Circle-issued USDC token ids on Hedera, per network.\n *\n * Verified two independent ways before hardcoding — do NOT change without re-verifying both:\n *\n * 1. Live Hedera Mirror Node token metadata (on-chain ground truth):\n * - mainnet `GET https://mainnet-public.mirrornode.hedera.com/api/v1/tokens/0.0.456858`\n * → { symbol: \"USDC\", name: \"USD Coin\", decimals: \"6\", type: \"FUNGIBLE_COMMON\",\n * treasury_account_id: \"0.0.439909\" }\n * - testnet `GET https://testnet.mirrornode.hedera.com/api/v1/tokens/0.0.429274`\n * → { symbol: \"USDC\", name: \"USD Coin\", decimals: \"6\", type: \"FUNGIBLE_COMMON\",\n * treasury_account_id: \"0.0.5176\" }\n *\n * 2. Circle's official \"USDC Contract Addresses\" documentation:\n * https://developers.circle.com/stablecoins/usdc-contract-addresses\n * (Hedera row links to hashscan.io/mainnet/token/0.0.456858; Hedera Testnet row links to\n * hashscan.io/testnet/token/0.0.429274). Circle publishes the native 0.0.x form, not an EVM\n * address.\n *\n * Previewnet: Circle does not issue USDC there, so there is no verified id. `getUsdcTokenId`\n * throws rather than silently falling back to a mainnet/testnet id.\n */\nexport const USDC_TOKEN_IDS = {\n mainnet: \"0.0.456858\",\n testnet: \"0.0.429274\",\n previewnet: undefined,\n} as const satisfies Record<HederaNetwork, string | undefined>\n\n/** Resolve the verified USDC token id for a network, or throw if none is known for it. */\nexport function getUsdcTokenId(network: HederaNetwork): string {\n const tokenId = USDC_TOKEN_IDS[network]\n if (tokenId === undefined) {\n throw new UnsupportedAssetError(\n `USDC has no verified token id on Hedera ${network}. ` +\n `Pass an explicit \\`tokenId\\` to verify a custom token, or use \"mainnet\" or \"testnet\".`,\n { docsPath: \"/guide/verify-usdc-payment\" },\n )\n }\n return tokenId\n}\n\n/**\n * Params for {@link verifyUsdcPayment}. Same as {@link VerifyHtsParams} minus `tokenId`/`decimals`\n * (USDC is always 6 decimals) and with `network` **required** — USDC token ids and HashScan URLs are\n * network-specific, so there is no implicit default network for the USDC helper.\n */\nexport interface VerifyUsdcParams extends Omit<VerifyHtsParams, \"tokenId\" | \"decimals\" | \"network\"> {\n network: HederaNetwork\n /**\n * Override the canonical USDC token id — useful for a dev/testnet mock token. The amount is still\n * parsed at 6 decimals. Production mainnet flows should omit this and use the verified token id.\n */\n tokenId?: string\n}\n\n/**\n * Verify a USDC payment on Hedera. A convenience wrapper over {@link verifyHtsPayment} that resolves\n * the canonical USDC token id for `network`, forces 6-decimal amount parsing, and tags the result\n * asset as USDC. Every other semantic — `confirmed`/`pending`/`underpaid`/`overpaid`/`duplicate`/\n * `mismatch`/`expired`/`failed`, memo/amount/time-window matching — is identical to HTS verification.\n */\nexport async function verifyUsdcPayment(p: VerifyUsdcParams): Promise<PaymentResult> {\n if (!p.network) {\n throw new InvalidParamsError(\n \"`network` is required for USDC verification — USDC token ids and explorer URLs are network-specific\",\n { docsPath: \"/guide/verify-usdc-payment\" },\n )\n }\n const tokenId = p.tokenId ?? getUsdcTokenId(p.network)\n assertEntityId(tokenId)\n const result = await verifyHtsPayment({ ...p, tokenId, decimals: USDC_DECIMALS })\n // verifyHtsPayment always yields a token asset (never \"HBAR\"); tag it so consumers can show USDC.\n const asset: PaymentAsset =\n result.asset === \"HBAR\" ? result.asset : { ...result.asset, symbol: USDC_SYMBOL }\n return { ...result, asset }\n}\n\n/**\n * True when a PaymentResult was produced by {@link verifyUsdcPayment} (its asset is tagged\n * `symbol: \"USDC\"`). Useful for narrowing/displaying results from a mixed payment pipeline.\n */\nexport function isUsdcPaymentResult(result: PaymentResult): boolean {\n return typeof result.asset === \"object\" && result.asset.symbol === USDC_SYMBOL\n}\n","import {\n verifyHbarPayment,\n verifyHtsPayment,\n type VerifyHbarParams,\n type VerifyHtsParams,\n} from \"./verify.js\"\nimport { verifyUsdcPayment, type VerifyUsdcParams } from \"./usdc.js\"\nimport type { PaymentResult } from \"./types.js\"\n\nexport interface WaitOptions {\n timeoutMs?: number\n pollIntervalMs?: number\n signal?: AbortSignal\n}\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\nasync function poll(\n verify: () => Promise<PaymentResult>,\n opts: WaitOptions,\n): Promise<PaymentResult> {\n const timeoutMs = opts.timeoutMs ?? 10 * 60 * 1000\n const pollIntervalMs = opts.pollIntervalMs ?? 3000\n const deadline = Date.now() + timeoutMs\n let last: PaymentResult | undefined\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) break\n last = await verify()\n if (last.matched) return last\n if (last.status === \"duplicate\" || last.status === \"overpaid\") return last\n await sleep(pollIntervalMs)\n }\n return last && last.status !== \"pending\"\n ? { ...last, status: \"expired\" }\n : {\n matched: false,\n status: \"expired\",\n receiver: last?.receiver ?? \"\",\n asset: last?.asset ?? \"HBAR\",\n matches: [],\n reason: \"timed out waiting for payment\",\n }\n}\n\nexport function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHbarPayment(p), p)\n}\nexport function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHtsPayment(p), p)\n}\nexport function waitForUsdcPayment(p: VerifyUsdcParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyUsdcPayment(p), p)\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -5,6 +5,7 @@ type PaymentStatus = "confirmed" | "pending" | "underpaid" | "overpaid" | "dupli
5
5
  type PaymentAsset = "HBAR" | {
6
6
  tokenId: string;
7
7
  decimals: number;
8
+ symbol?: string;
8
9
  };
9
10
  interface PaymentMatch {
10
11
  transactionId: string;
@@ -52,6 +53,63 @@ interface VerifyHtsParams extends VerifyBaseParams {
52
53
  declare function verifyHbarPayment(p: VerifyHbarParams): Promise<PaymentResult>;
53
54
  declare function verifyHtsPayment(p: VerifyHtsParams): Promise<PaymentResult>;
54
55
 
56
+ /** USDC uses 6 decimals on every network Circle issues it on, including Hedera. */
57
+ declare const USDC_DECIMALS = 6;
58
+ /**
59
+ * Canonical Circle-issued USDC token ids on Hedera, per network.
60
+ *
61
+ * Verified two independent ways before hardcoding — do NOT change without re-verifying both:
62
+ *
63
+ * 1. Live Hedera Mirror Node token metadata (on-chain ground truth):
64
+ * - mainnet `GET https://mainnet-public.mirrornode.hedera.com/api/v1/tokens/0.0.456858`
65
+ * → { symbol: "USDC", name: "USD Coin", decimals: "6", type: "FUNGIBLE_COMMON",
66
+ * treasury_account_id: "0.0.439909" }
67
+ * - testnet `GET https://testnet.mirrornode.hedera.com/api/v1/tokens/0.0.429274`
68
+ * → { symbol: "USDC", name: "USD Coin", decimals: "6", type: "FUNGIBLE_COMMON",
69
+ * treasury_account_id: "0.0.5176" }
70
+ *
71
+ * 2. Circle's official "USDC Contract Addresses" documentation:
72
+ * https://developers.circle.com/stablecoins/usdc-contract-addresses
73
+ * (Hedera row links to hashscan.io/mainnet/token/0.0.456858; Hedera Testnet row links to
74
+ * hashscan.io/testnet/token/0.0.429274). Circle publishes the native 0.0.x form, not an EVM
75
+ * address.
76
+ *
77
+ * Previewnet: Circle does not issue USDC there, so there is no verified id. `getUsdcTokenId`
78
+ * throws rather than silently falling back to a mainnet/testnet id.
79
+ */
80
+ declare const USDC_TOKEN_IDS: {
81
+ readonly mainnet: "0.0.456858";
82
+ readonly testnet: "0.0.429274";
83
+ readonly previewnet: undefined;
84
+ };
85
+ /** Resolve the verified USDC token id for a network, or throw if none is known for it. */
86
+ declare function getUsdcTokenId(network: HederaNetwork): string;
87
+ /**
88
+ * Params for {@link verifyUsdcPayment}. Same as {@link VerifyHtsParams} minus `tokenId`/`decimals`
89
+ * (USDC is always 6 decimals) and with `network` **required** — USDC token ids and HashScan URLs are
90
+ * network-specific, so there is no implicit default network for the USDC helper.
91
+ */
92
+ interface VerifyUsdcParams extends Omit<VerifyHtsParams, "tokenId" | "decimals" | "network"> {
93
+ network: HederaNetwork;
94
+ /**
95
+ * Override the canonical USDC token id — useful for a dev/testnet mock token. The amount is still
96
+ * parsed at 6 decimals. Production mainnet flows should omit this and use the verified token id.
97
+ */
98
+ tokenId?: string;
99
+ }
100
+ /**
101
+ * Verify a USDC payment on Hedera. A convenience wrapper over {@link verifyHtsPayment} that resolves
102
+ * the canonical USDC token id for `network`, forces 6-decimal amount parsing, and tags the result
103
+ * asset as USDC. Every other semantic — `confirmed`/`pending`/`underpaid`/`overpaid`/`duplicate`/
104
+ * `mismatch`/`expired`/`failed`, memo/amount/time-window matching — is identical to HTS verification.
105
+ */
106
+ declare function verifyUsdcPayment(p: VerifyUsdcParams): Promise<PaymentResult>;
107
+ /**
108
+ * True when a PaymentResult was produced by {@link verifyUsdcPayment} (its asset is tagged
109
+ * `symbol: "USDC"`). Useful for narrowing/displaying results from a mixed payment pipeline.
110
+ */
111
+ declare function isUsdcPaymentResult(result: PaymentResult): boolean;
112
+
55
113
  interface WaitOptions {
56
114
  timeoutMs?: number;
57
115
  pollIntervalMs?: number;
@@ -59,6 +117,7 @@ interface WaitOptions {
59
117
  }
60
118
  declare function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<PaymentResult>;
61
119
  declare function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult>;
120
+ declare function waitForUsdcPayment(p: VerifyUsdcParams & WaitOptions): Promise<PaymentResult>;
62
121
 
63
122
  /** HashScan transaction link: consensus timestamp in path, tx id as ?tid query param. */
64
123
  declare function hashscanTxUrl(network: HederaNetwork, consensusTimestamp: string, transactionId: string): string;
@@ -69,4 +128,4 @@ declare function memoMatches(actual: string, expected: string, cmp?: MemoCompari
69
128
  type AmountClass = "exact" | "underpaid" | "overpaid";
70
129
  declare function classifyAmount(net: bigint, expected: bigint): AmountClass;
71
130
 
72
- export { type MemoComparison, type PaymentAsset, type PaymentMatch, type PaymentResult, type PaymentStatus, type VerifyBaseParams, type VerifyHbarParams, type VerifyHtsParams, type WaitOptions, classifyAmount, hashscanTxUrl, memoMatches, netToReceiver, verifyHbarPayment, verifyHtsPayment, waitForHbarPayment, waitForHtsPayment };
131
+ export { type MemoComparison, type PaymentAsset, type PaymentMatch, type PaymentResult, type PaymentStatus, USDC_DECIMALS, USDC_TOKEN_IDS, type VerifyBaseParams, type VerifyHbarParams, type VerifyHtsParams, type VerifyUsdcParams, type WaitOptions, classifyAmount, getUsdcTokenId, hashscanTxUrl, isUsdcPaymentResult, memoMatches, netToReceiver, verifyHbarPayment, verifyHtsPayment, verifyUsdcPayment, waitForHbarPayment, waitForHtsPayment, waitForUsdcPayment };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ type PaymentStatus = "confirmed" | "pending" | "underpaid" | "overpaid" | "dupli
5
5
  type PaymentAsset = "HBAR" | {
6
6
  tokenId: string;
7
7
  decimals: number;
8
+ symbol?: string;
8
9
  };
9
10
  interface PaymentMatch {
10
11
  transactionId: string;
@@ -52,6 +53,63 @@ interface VerifyHtsParams extends VerifyBaseParams {
52
53
  declare function verifyHbarPayment(p: VerifyHbarParams): Promise<PaymentResult>;
53
54
  declare function verifyHtsPayment(p: VerifyHtsParams): Promise<PaymentResult>;
54
55
 
56
+ /** USDC uses 6 decimals on every network Circle issues it on, including Hedera. */
57
+ declare const USDC_DECIMALS = 6;
58
+ /**
59
+ * Canonical Circle-issued USDC token ids on Hedera, per network.
60
+ *
61
+ * Verified two independent ways before hardcoding — do NOT change without re-verifying both:
62
+ *
63
+ * 1. Live Hedera Mirror Node token metadata (on-chain ground truth):
64
+ * - mainnet `GET https://mainnet-public.mirrornode.hedera.com/api/v1/tokens/0.0.456858`
65
+ * → { symbol: "USDC", name: "USD Coin", decimals: "6", type: "FUNGIBLE_COMMON",
66
+ * treasury_account_id: "0.0.439909" }
67
+ * - testnet `GET https://testnet.mirrornode.hedera.com/api/v1/tokens/0.0.429274`
68
+ * → { symbol: "USDC", name: "USD Coin", decimals: "6", type: "FUNGIBLE_COMMON",
69
+ * treasury_account_id: "0.0.5176" }
70
+ *
71
+ * 2. Circle's official "USDC Contract Addresses" documentation:
72
+ * https://developers.circle.com/stablecoins/usdc-contract-addresses
73
+ * (Hedera row links to hashscan.io/mainnet/token/0.0.456858; Hedera Testnet row links to
74
+ * hashscan.io/testnet/token/0.0.429274). Circle publishes the native 0.0.x form, not an EVM
75
+ * address.
76
+ *
77
+ * Previewnet: Circle does not issue USDC there, so there is no verified id. `getUsdcTokenId`
78
+ * throws rather than silently falling back to a mainnet/testnet id.
79
+ */
80
+ declare const USDC_TOKEN_IDS: {
81
+ readonly mainnet: "0.0.456858";
82
+ readonly testnet: "0.0.429274";
83
+ readonly previewnet: undefined;
84
+ };
85
+ /** Resolve the verified USDC token id for a network, or throw if none is known for it. */
86
+ declare function getUsdcTokenId(network: HederaNetwork): string;
87
+ /**
88
+ * Params for {@link verifyUsdcPayment}. Same as {@link VerifyHtsParams} minus `tokenId`/`decimals`
89
+ * (USDC is always 6 decimals) and with `network` **required** — USDC token ids and HashScan URLs are
90
+ * network-specific, so there is no implicit default network for the USDC helper.
91
+ */
92
+ interface VerifyUsdcParams extends Omit<VerifyHtsParams, "tokenId" | "decimals" | "network"> {
93
+ network: HederaNetwork;
94
+ /**
95
+ * Override the canonical USDC token id — useful for a dev/testnet mock token. The amount is still
96
+ * parsed at 6 decimals. Production mainnet flows should omit this and use the verified token id.
97
+ */
98
+ tokenId?: string;
99
+ }
100
+ /**
101
+ * Verify a USDC payment on Hedera. A convenience wrapper over {@link verifyHtsPayment} that resolves
102
+ * the canonical USDC token id for `network`, forces 6-decimal amount parsing, and tags the result
103
+ * asset as USDC. Every other semantic — `confirmed`/`pending`/`underpaid`/`overpaid`/`duplicate`/
104
+ * `mismatch`/`expired`/`failed`, memo/amount/time-window matching — is identical to HTS verification.
105
+ */
106
+ declare function verifyUsdcPayment(p: VerifyUsdcParams): Promise<PaymentResult>;
107
+ /**
108
+ * True when a PaymentResult was produced by {@link verifyUsdcPayment} (its asset is tagged
109
+ * `symbol: "USDC"`). Useful for narrowing/displaying results from a mixed payment pipeline.
110
+ */
111
+ declare function isUsdcPaymentResult(result: PaymentResult): boolean;
112
+
55
113
  interface WaitOptions {
56
114
  timeoutMs?: number;
57
115
  pollIntervalMs?: number;
@@ -59,6 +117,7 @@ interface WaitOptions {
59
117
  }
60
118
  declare function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<PaymentResult>;
61
119
  declare function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult>;
120
+ declare function waitForUsdcPayment(p: VerifyUsdcParams & WaitOptions): Promise<PaymentResult>;
62
121
 
63
122
  /** HashScan transaction link: consensus timestamp in path, tx id as ?tid query param. */
64
123
  declare function hashscanTxUrl(network: HederaNetwork, consensusTimestamp: string, transactionId: string): string;
@@ -69,4 +128,4 @@ declare function memoMatches(actual: string, expected: string, cmp?: MemoCompari
69
128
  type AmountClass = "exact" | "underpaid" | "overpaid";
70
129
  declare function classifyAmount(net: bigint, expected: bigint): AmountClass;
71
130
 
72
- export { type MemoComparison, type PaymentAsset, type PaymentMatch, type PaymentResult, type PaymentStatus, type VerifyBaseParams, type VerifyHbarParams, type VerifyHtsParams, type WaitOptions, classifyAmount, hashscanTxUrl, memoMatches, netToReceiver, verifyHbarPayment, verifyHtsPayment, waitForHbarPayment, waitForHtsPayment };
131
+ export { type MemoComparison, type PaymentAsset, type PaymentMatch, type PaymentResult, type PaymentStatus, USDC_DECIMALS, USDC_TOKEN_IDS, type VerifyBaseParams, type VerifyHbarParams, type VerifyHtsParams, type VerifyUsdcParams, type WaitOptions, classifyAmount, getUsdcTokenId, hashscanTxUrl, isUsdcPaymentResult, memoMatches, netToReceiver, verifyHbarPayment, verifyHtsPayment, verifyUsdcPayment, waitForHbarPayment, waitForHtsPayment, waitForUsdcPayment };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NETWORKS, txIdToMirror, parseHbar, parseUnits, InvalidParamsError, formatUnits } from '@hbar-kit/core';
1
+ import { NETWORKS, txIdToMirror, parseHbar, parseUnits, UnsupportedAssetError, InvalidParamsError, assertEntityId, formatUnits } from '@hbar-kit/core';
2
2
  import { normalizeTransaction, createMirrorClient } from '@hbar-kit/mirror';
3
3
 
4
4
  // src/verify.ts
@@ -146,6 +146,39 @@ async function verifyHtsPayment(p) {
146
146
  }
147
147
  return runVerify(p, { tokenId: p.tokenId, decimals }, parseUnits(p.amount, decimals), decimals);
148
148
  }
149
+ var USDC_DECIMALS = 6;
150
+ var USDC_SYMBOL = "USDC";
151
+ var USDC_TOKEN_IDS = {
152
+ mainnet: "0.0.456858",
153
+ testnet: "0.0.429274",
154
+ previewnet: void 0
155
+ };
156
+ function getUsdcTokenId(network) {
157
+ const tokenId = USDC_TOKEN_IDS[network];
158
+ if (tokenId === void 0) {
159
+ throw new UnsupportedAssetError(
160
+ `USDC has no verified token id on Hedera ${network}. Pass an explicit \`tokenId\` to verify a custom token, or use "mainnet" or "testnet".`,
161
+ { docsPath: "/guide/verify-usdc-payment" }
162
+ );
163
+ }
164
+ return tokenId;
165
+ }
166
+ async function verifyUsdcPayment(p) {
167
+ if (!p.network) {
168
+ throw new InvalidParamsError(
169
+ "`network` is required for USDC verification \u2014 USDC token ids and explorer URLs are network-specific",
170
+ { docsPath: "/guide/verify-usdc-payment" }
171
+ );
172
+ }
173
+ const tokenId = p.tokenId ?? getUsdcTokenId(p.network);
174
+ assertEntityId(tokenId);
175
+ const result = await verifyHtsPayment({ ...p, tokenId, decimals: USDC_DECIMALS });
176
+ const asset = result.asset === "HBAR" ? result.asset : { ...result.asset, symbol: USDC_SYMBOL };
177
+ return { ...result, asset };
178
+ }
179
+ function isUsdcPaymentResult(result) {
180
+ return typeof result.asset === "object" && result.asset.symbol === USDC_SYMBOL;
181
+ }
149
182
 
150
183
  // src/wait.ts
151
184
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -176,7 +209,10 @@ function waitForHbarPayment(p) {
176
209
  function waitForHtsPayment(p) {
177
210
  return poll(() => verifyHtsPayment(p), p);
178
211
  }
212
+ function waitForUsdcPayment(p) {
213
+ return poll(() => verifyUsdcPayment(p), p);
214
+ }
179
215
 
180
- export { classifyAmount, hashscanTxUrl, memoMatches, netToReceiver, verifyHbarPayment, verifyHtsPayment, waitForHbarPayment, waitForHtsPayment };
216
+ export { USDC_DECIMALS, USDC_TOKEN_IDS, classifyAmount, getUsdcTokenId, hashscanTxUrl, isUsdcPaymentResult, memoMatches, netToReceiver, verifyHbarPayment, verifyHtsPayment, verifyUsdcPayment, waitForHbarPayment, waitForHtsPayment, waitForUsdcPayment };
181
217
  //# sourceMappingURL=index.js.map
182
218
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/match.ts","../src/explorer.ts","../src/verify.ts","../src/wait.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Transaction } from \"@hbar-kit/mirror\"\nimport type { MemoComparison, PaymentAsset } from \"./types.js\"\n\n/** Signed sum of ALL the receiver's legs (HBAR transfers or a specific token's transfers). */\nexport function netToReceiver(tx: Transaction, receiver: string, asset: PaymentAsset): bigint {\n if (asset === \"HBAR\") {\n return tx.transfers.filter((t) => t.account === receiver).reduce((s, t) => s + t.amount, 0n)\n }\n return tx.tokenTransfers\n .filter((t) => t.tokenId === asset.tokenId && t.account === receiver)\n .reduce((s, t) => s + t.amount, 0n)\n}\n\nexport function memoMatches(actual: string, expected: string, cmp: MemoComparison = {}): boolean {\n const mode = cmp.mode ?? \"exact\"\n if (mode === \"trim\") return actual.trim() === expected.trim()\n if (mode === \"caseInsensitive\") return actual.toLowerCase() === expected.toLowerCase()\n return actual === expected\n}\n\nexport type AmountClass = \"exact\" | \"underpaid\" | \"overpaid\"\nexport function classifyAmount(net: bigint, expected: bigint): AmountClass {\n if (net === expected) return \"exact\"\n return net < expected ? \"underpaid\" : \"overpaid\"\n}\n","import { NETWORKS, txIdToMirror, type HederaNetwork } from \"@hbar-kit/core\"\n\n/** HashScan transaction link: consensus timestamp in path, tx id as ?tid query param. */\nexport function hashscanTxUrl(\n network: HederaNetwork,\n consensusTimestamp: string,\n transactionId: string,\n): string {\n return `${NETWORKS[network].hashscan}/transaction/${consensusTimestamp}?tid=${txIdToMirror(transactionId)}`\n}\n","import {\n parseUnits,\n parseHbar,\n formatUnits,\n type HederaNetwork,\n type NetworkInput,\n InvalidParamsError,\n} from \"@hbar-kit/core\"\nimport {\n createMirrorClient,\n normalizeTransaction,\n type MirrorClient,\n type RawTransaction,\n type Transaction,\n} from \"@hbar-kit/mirror\"\nimport { netToReceiver, memoMatches, classifyAmount } from \"./match.js\"\nimport { hashscanTxUrl } from \"./explorer.js\"\nimport type { MemoComparison, PaymentAsset, PaymentMatch, PaymentResult } from \"./types.js\"\n\nexport interface VerifyBaseParams extends NetworkInput {\n client?: MirrorClient\n receiver: string\n amount: string\n memo?: string\n memoComparison?: MemoComparison\n comparison?: \"exact\" | \"atLeast\"\n after?: Date | string\n before?: Date | string\n}\nexport type VerifyHbarParams = VerifyBaseParams\nexport interface VerifyHtsParams extends VerifyBaseParams {\n tokenId: string\n decimals?: number\n}\n\nconst tokenDecimalsCache = new Map<string, number>()\n\nfunction resolveClient(p: VerifyBaseParams): { client: MirrorClient; network: HederaNetwork } {\n return {\n client: p.client ?? createMirrorClient(p),\n network: (p.network ?? \"mainnet\") as HederaNetwork,\n }\n}\n\nasync function runVerify(\n p: VerifyBaseParams,\n asset: PaymentAsset,\n expectedBase: bigint,\n decimals: number,\n): Promise<PaymentResult> {\n if (p.after && p.before && new Date(p.after) > new Date(p.before)) {\n throw new InvalidParamsError(\"`after` must be before `before`\")\n }\n const { client, network } = resolveClient(p)\n const tokenId = asset === \"HBAR\" ? undefined : asset.tokenId\n\n const candidates: PaymentMatch[] = []\n const collect = (items: Transaction[]) => {\n for (const tx of items) {\n if (tx.result !== \"SUCCESS\") continue\n const net = netToReceiver(tx, p.receiver, asset)\n if (net <= 0n) continue\n if (tokenId && !tx.tokenTransfers.some((t) => t.tokenId === tokenId)) continue\n const payer =\n tx.transfers.find((t) => t.amount < 0n)?.account ??\n tx.tokenTransfers.find((t) => t.amount < 0n)?.account\n const match: PaymentMatch = {\n transactionId: tx.transactionId,\n consensusTimestamp: tx.consensusTimestamp.raw,\n netBase: net,\n net: formatUnits(net, decimals),\n memo: tx.memo,\n transaction: tx,\n }\n if (payer !== undefined) match.payer = payer\n candidates.push(match)\n }\n }\n\n const findParams: Parameters<typeof client.transactions.find>[0] = {\n accountId: p.receiver,\n transactionType: \"cryptotransfer\",\n result: \"success\",\n order: \"desc\",\n }\n if (p.after !== undefined) findParams.after = p.after\n if (p.before !== undefined) findParams.before = p.before\n let page = await client.transactions.find(findParams)\n collect(page.items)\n while (page.next) {\n const body = (await client.transport.get(page.next)) as {\n transactions?: RawTransaction[]\n links?: { next: string | null }\n }\n collect((body.transactions ?? []).map(normalizeTransaction))\n page = { items: [], next: body.links?.next ?? null }\n }\n\n const memoFiltered = p.memo\n ? candidates.filter((c) => memoMatches(c.memo, p.memo!, p.memoComparison))\n : candidates\n\n const fail = (status: PaymentResult[\"status\"], reason: string): PaymentResult => ({\n matched: false,\n status,\n receiver: p.receiver,\n asset,\n matches: memoFiltered,\n reason,\n })\n if (candidates.length === 0)\n return fail(\"pending\", \"no matching transactions for receiver in window\")\n if (p.memo && memoFiltered.length === 0)\n return fail(\"mismatch\", \"no transaction matched the expected memo\")\n\n const wantAtLeast = p.comparison === \"atLeast\"\n const satisfying = memoFiltered.filter((c) =>\n wantAtLeast ? c.netBase >= expectedBase : c.netBase === expectedBase,\n )\n\n const resultFrom = (\n m: PaymentMatch,\n status: PaymentResult[\"status\"],\n matched: boolean,\n matches: PaymentMatch[],\n reason?: string,\n ): PaymentResult => {\n const result: PaymentResult = {\n matched,\n status,\n receiver: p.receiver,\n asset,\n transactionId: m.transactionId,\n amountBase: m.netBase,\n amount: m.net,\n memo: m.memo,\n consensusTimestamp: m.consensusTimestamp,\n explorerUrl: hashscanTxUrl(network, m.consensusTimestamp, m.transactionId),\n matches,\n }\n if (m.payer !== undefined) result.payer = m.payer\n if (reason !== undefined) result.reason = reason\n return result\n }\n\n if (satisfying.length === 0) {\n const best = memoFiltered[0]!\n const cls = classifyAmount(best.netBase, expectedBase)\n return resultFrom(\n best,\n cls === \"exact\" ? \"confirmed\" : cls,\n false,\n memoFiltered,\n `amount ${cls}`,\n )\n }\n if (satisfying.length > 1) {\n return resultFrom(\n satisfying[0]!,\n \"duplicate\",\n false,\n satisfying,\n `${satisfying.length} transactions satisfy this request`,\n )\n }\n return resultFrom(satisfying[0]!, \"confirmed\", true, satisfying)\n}\n\nexport async function verifyHbarPayment(p: VerifyHbarParams): Promise<PaymentResult> {\n return runVerify(p, \"HBAR\", parseHbar(p.amount), 8)\n}\n\nexport async function verifyHtsPayment(p: VerifyHtsParams): Promise<PaymentResult> {\n let decimals = p.decimals\n if (decimals === undefined) {\n const cached = tokenDecimalsCache.get(p.tokenId)\n if (cached !== undefined) decimals = cached\n else {\n const { client } = resolveClient(p)\n decimals = (await client.tokens.get(p.tokenId)).decimals\n tokenDecimalsCache.set(p.tokenId, decimals)\n }\n }\n return runVerify(p, { tokenId: p.tokenId, decimals }, parseUnits(p.amount, decimals), decimals)\n}\n","import {\n verifyHbarPayment,\n verifyHtsPayment,\n type VerifyHbarParams,\n type VerifyHtsParams,\n} from \"./verify.js\"\nimport type { PaymentResult } from \"./types.js\"\n\nexport interface WaitOptions {\n timeoutMs?: number\n pollIntervalMs?: number\n signal?: AbortSignal\n}\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\nasync function poll(\n verify: () => Promise<PaymentResult>,\n opts: WaitOptions,\n): Promise<PaymentResult> {\n const timeoutMs = opts.timeoutMs ?? 10 * 60 * 1000\n const pollIntervalMs = opts.pollIntervalMs ?? 3000\n const deadline = Date.now() + timeoutMs\n let last: PaymentResult | undefined\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) break\n last = await verify()\n if (last.matched) return last\n if (last.status === \"duplicate\" || last.status === \"overpaid\") return last\n await sleep(pollIntervalMs)\n }\n return last && last.status !== \"pending\"\n ? { ...last, status: \"expired\" }\n : {\n matched: false,\n status: \"expired\",\n receiver: last?.receiver ?? \"\",\n asset: last?.asset ?? \"HBAR\",\n matches: [],\n reason: \"timed out waiting for payment\",\n }\n}\n\nexport function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHbarPayment(p), p)\n}\nexport function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHtsPayment(p), p)\n}\n"]}
1
+ {"version":3,"sources":["../src/match.ts","../src/explorer.ts","../src/verify.ts","../src/usdc.ts","../src/wait.ts"],"names":["InvalidParamsError"],"mappings":"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","file":"index.js","sourcesContent":["import type { Transaction } from \"@hbar-kit/mirror\"\nimport type { MemoComparison, PaymentAsset } from \"./types.js\"\n\n/** Signed sum of ALL the receiver's legs (HBAR transfers or a specific token's transfers). */\nexport function netToReceiver(tx: Transaction, receiver: string, asset: PaymentAsset): bigint {\n if (asset === \"HBAR\") {\n return tx.transfers.filter((t) => t.account === receiver).reduce((s, t) => s + t.amount, 0n)\n }\n return tx.tokenTransfers\n .filter((t) => t.tokenId === asset.tokenId && t.account === receiver)\n .reduce((s, t) => s + t.amount, 0n)\n}\n\nexport function memoMatches(actual: string, expected: string, cmp: MemoComparison = {}): boolean {\n const mode = cmp.mode ?? \"exact\"\n if (mode === \"trim\") return actual.trim() === expected.trim()\n if (mode === \"caseInsensitive\") return actual.toLowerCase() === expected.toLowerCase()\n return actual === expected\n}\n\nexport type AmountClass = \"exact\" | \"underpaid\" | \"overpaid\"\nexport function classifyAmount(net: bigint, expected: bigint): AmountClass {\n if (net === expected) return \"exact\"\n return net < expected ? \"underpaid\" : \"overpaid\"\n}\n","import { NETWORKS, txIdToMirror, type HederaNetwork } from \"@hbar-kit/core\"\n\n/** HashScan transaction link: consensus timestamp in path, tx id as ?tid query param. */\nexport function hashscanTxUrl(\n network: HederaNetwork,\n consensusTimestamp: string,\n transactionId: string,\n): string {\n return `${NETWORKS[network].hashscan}/transaction/${consensusTimestamp}?tid=${txIdToMirror(transactionId)}`\n}\n","import {\n parseUnits,\n parseHbar,\n formatUnits,\n type HederaNetwork,\n type NetworkInput,\n InvalidParamsError,\n} from \"@hbar-kit/core\"\nimport {\n createMirrorClient,\n normalizeTransaction,\n type MirrorClient,\n type RawTransaction,\n type Transaction,\n} from \"@hbar-kit/mirror\"\nimport { netToReceiver, memoMatches, classifyAmount } from \"./match.js\"\nimport { hashscanTxUrl } from \"./explorer.js\"\nimport type { MemoComparison, PaymentAsset, PaymentMatch, PaymentResult } from \"./types.js\"\n\nexport interface VerifyBaseParams extends NetworkInput {\n client?: MirrorClient\n receiver: string\n amount: string\n memo?: string\n memoComparison?: MemoComparison\n comparison?: \"exact\" | \"atLeast\"\n after?: Date | string\n before?: Date | string\n}\nexport type VerifyHbarParams = VerifyBaseParams\nexport interface VerifyHtsParams extends VerifyBaseParams {\n tokenId: string\n decimals?: number\n}\n\nconst tokenDecimalsCache = new Map<string, number>()\n\nfunction resolveClient(p: VerifyBaseParams): { client: MirrorClient; network: HederaNetwork } {\n return {\n client: p.client ?? createMirrorClient(p),\n network: (p.network ?? \"mainnet\") as HederaNetwork,\n }\n}\n\nasync function runVerify(\n p: VerifyBaseParams,\n asset: PaymentAsset,\n expectedBase: bigint,\n decimals: number,\n): Promise<PaymentResult> {\n if (p.after && p.before && new Date(p.after) > new Date(p.before)) {\n throw new InvalidParamsError(\"`after` must be before `before`\")\n }\n const { client, network } = resolveClient(p)\n const tokenId = asset === \"HBAR\" ? undefined : asset.tokenId\n\n const candidates: PaymentMatch[] = []\n const collect = (items: Transaction[]) => {\n for (const tx of items) {\n if (tx.result !== \"SUCCESS\") continue\n const net = netToReceiver(tx, p.receiver, asset)\n if (net <= 0n) continue\n if (tokenId && !tx.tokenTransfers.some((t) => t.tokenId === tokenId)) continue\n const payer =\n tx.transfers.find((t) => t.amount < 0n)?.account ??\n tx.tokenTransfers.find((t) => t.amount < 0n)?.account\n const match: PaymentMatch = {\n transactionId: tx.transactionId,\n consensusTimestamp: tx.consensusTimestamp.raw,\n netBase: net,\n net: formatUnits(net, decimals),\n memo: tx.memo,\n transaction: tx,\n }\n if (payer !== undefined) match.payer = payer\n candidates.push(match)\n }\n }\n\n const findParams: Parameters<typeof client.transactions.find>[0] = {\n accountId: p.receiver,\n transactionType: \"cryptotransfer\",\n result: \"success\",\n order: \"desc\",\n }\n if (p.after !== undefined) findParams.after = p.after\n if (p.before !== undefined) findParams.before = p.before\n let page = await client.transactions.find(findParams)\n collect(page.items)\n while (page.next) {\n const body = (await client.transport.get(page.next)) as {\n transactions?: RawTransaction[]\n links?: { next: string | null }\n }\n collect((body.transactions ?? []).map(normalizeTransaction))\n page = { items: [], next: body.links?.next ?? null }\n }\n\n const memoFiltered = p.memo\n ? candidates.filter((c) => memoMatches(c.memo, p.memo!, p.memoComparison))\n : candidates\n\n const fail = (status: PaymentResult[\"status\"], reason: string): PaymentResult => ({\n matched: false,\n status,\n receiver: p.receiver,\n asset,\n matches: memoFiltered,\n reason,\n })\n if (candidates.length === 0)\n return fail(\"pending\", \"no matching transactions for receiver in window\")\n if (p.memo && memoFiltered.length === 0)\n return fail(\"mismatch\", \"no transaction matched the expected memo\")\n\n const wantAtLeast = p.comparison === \"atLeast\"\n const satisfying = memoFiltered.filter((c) =>\n wantAtLeast ? c.netBase >= expectedBase : c.netBase === expectedBase,\n )\n\n const resultFrom = (\n m: PaymentMatch,\n status: PaymentResult[\"status\"],\n matched: boolean,\n matches: PaymentMatch[],\n reason?: string,\n ): PaymentResult => {\n const result: PaymentResult = {\n matched,\n status,\n receiver: p.receiver,\n asset,\n transactionId: m.transactionId,\n amountBase: m.netBase,\n amount: m.net,\n memo: m.memo,\n consensusTimestamp: m.consensusTimestamp,\n explorerUrl: hashscanTxUrl(network, m.consensusTimestamp, m.transactionId),\n matches,\n }\n if (m.payer !== undefined) result.payer = m.payer\n if (reason !== undefined) result.reason = reason\n return result\n }\n\n if (satisfying.length === 0) {\n const best = memoFiltered[0]!\n const cls = classifyAmount(best.netBase, expectedBase)\n return resultFrom(\n best,\n cls === \"exact\" ? \"confirmed\" : cls,\n false,\n memoFiltered,\n `amount ${cls}`,\n )\n }\n if (satisfying.length > 1) {\n return resultFrom(\n satisfying[0]!,\n \"duplicate\",\n false,\n satisfying,\n `${satisfying.length} transactions satisfy this request`,\n )\n }\n return resultFrom(satisfying[0]!, \"confirmed\", true, satisfying)\n}\n\nexport async function verifyHbarPayment(p: VerifyHbarParams): Promise<PaymentResult> {\n return runVerify(p, \"HBAR\", parseHbar(p.amount), 8)\n}\n\nexport async function verifyHtsPayment(p: VerifyHtsParams): Promise<PaymentResult> {\n let decimals = p.decimals\n if (decimals === undefined) {\n const cached = tokenDecimalsCache.get(p.tokenId)\n if (cached !== undefined) decimals = cached\n else {\n const { client } = resolveClient(p)\n decimals = (await client.tokens.get(p.tokenId)).decimals\n tokenDecimalsCache.set(p.tokenId, decimals)\n }\n }\n return runVerify(p, { tokenId: p.tokenId, decimals }, parseUnits(p.amount, decimals), decimals)\n}\n","import {\n assertEntityId,\n InvalidParamsError,\n UnsupportedAssetError,\n type HederaNetwork,\n} from \"@hbar-kit/core\"\nimport { verifyHtsPayment, type VerifyHtsParams } from \"./verify.js\"\nimport type { PaymentAsset, PaymentResult } from \"./types.js\"\n\n/** USDC uses 6 decimals on every network Circle issues it on, including Hedera. */\nexport const USDC_DECIMALS = 6\nconst USDC_SYMBOL = \"USDC\"\n\n/**\n * Canonical Circle-issued USDC token ids on Hedera, per network.\n *\n * Verified two independent ways before hardcoding — do NOT change without re-verifying both:\n *\n * 1. Live Hedera Mirror Node token metadata (on-chain ground truth):\n * - mainnet `GET https://mainnet-public.mirrornode.hedera.com/api/v1/tokens/0.0.456858`\n * → { symbol: \"USDC\", name: \"USD Coin\", decimals: \"6\", type: \"FUNGIBLE_COMMON\",\n * treasury_account_id: \"0.0.439909\" }\n * - testnet `GET https://testnet.mirrornode.hedera.com/api/v1/tokens/0.0.429274`\n * → { symbol: \"USDC\", name: \"USD Coin\", decimals: \"6\", type: \"FUNGIBLE_COMMON\",\n * treasury_account_id: \"0.0.5176\" }\n *\n * 2. Circle's official \"USDC Contract Addresses\" documentation:\n * https://developers.circle.com/stablecoins/usdc-contract-addresses\n * (Hedera row links to hashscan.io/mainnet/token/0.0.456858; Hedera Testnet row links to\n * hashscan.io/testnet/token/0.0.429274). Circle publishes the native 0.0.x form, not an EVM\n * address.\n *\n * Previewnet: Circle does not issue USDC there, so there is no verified id. `getUsdcTokenId`\n * throws rather than silently falling back to a mainnet/testnet id.\n */\nexport const USDC_TOKEN_IDS = {\n mainnet: \"0.0.456858\",\n testnet: \"0.0.429274\",\n previewnet: undefined,\n} as const satisfies Record<HederaNetwork, string | undefined>\n\n/** Resolve the verified USDC token id for a network, or throw if none is known for it. */\nexport function getUsdcTokenId(network: HederaNetwork): string {\n const tokenId = USDC_TOKEN_IDS[network]\n if (tokenId === undefined) {\n throw new UnsupportedAssetError(\n `USDC has no verified token id on Hedera ${network}. ` +\n `Pass an explicit \\`tokenId\\` to verify a custom token, or use \"mainnet\" or \"testnet\".`,\n { docsPath: \"/guide/verify-usdc-payment\" },\n )\n }\n return tokenId\n}\n\n/**\n * Params for {@link verifyUsdcPayment}. Same as {@link VerifyHtsParams} minus `tokenId`/`decimals`\n * (USDC is always 6 decimals) and with `network` **required** — USDC token ids and HashScan URLs are\n * network-specific, so there is no implicit default network for the USDC helper.\n */\nexport interface VerifyUsdcParams extends Omit<VerifyHtsParams, \"tokenId\" | \"decimals\" | \"network\"> {\n network: HederaNetwork\n /**\n * Override the canonical USDC token id — useful for a dev/testnet mock token. The amount is still\n * parsed at 6 decimals. Production mainnet flows should omit this and use the verified token id.\n */\n tokenId?: string\n}\n\n/**\n * Verify a USDC payment on Hedera. A convenience wrapper over {@link verifyHtsPayment} that resolves\n * the canonical USDC token id for `network`, forces 6-decimal amount parsing, and tags the result\n * asset as USDC. Every other semantic — `confirmed`/`pending`/`underpaid`/`overpaid`/`duplicate`/\n * `mismatch`/`expired`/`failed`, memo/amount/time-window matching — is identical to HTS verification.\n */\nexport async function verifyUsdcPayment(p: VerifyUsdcParams): Promise<PaymentResult> {\n if (!p.network) {\n throw new InvalidParamsError(\n \"`network` is required for USDC verification — USDC token ids and explorer URLs are network-specific\",\n { docsPath: \"/guide/verify-usdc-payment\" },\n )\n }\n const tokenId = p.tokenId ?? getUsdcTokenId(p.network)\n assertEntityId(tokenId)\n const result = await verifyHtsPayment({ ...p, tokenId, decimals: USDC_DECIMALS })\n // verifyHtsPayment always yields a token asset (never \"HBAR\"); tag it so consumers can show USDC.\n const asset: PaymentAsset =\n result.asset === \"HBAR\" ? result.asset : { ...result.asset, symbol: USDC_SYMBOL }\n return { ...result, asset }\n}\n\n/**\n * True when a PaymentResult was produced by {@link verifyUsdcPayment} (its asset is tagged\n * `symbol: \"USDC\"`). Useful for narrowing/displaying results from a mixed payment pipeline.\n */\nexport function isUsdcPaymentResult(result: PaymentResult): boolean {\n return typeof result.asset === \"object\" && result.asset.symbol === USDC_SYMBOL\n}\n","import {\n verifyHbarPayment,\n verifyHtsPayment,\n type VerifyHbarParams,\n type VerifyHtsParams,\n} from \"./verify.js\"\nimport { verifyUsdcPayment, type VerifyUsdcParams } from \"./usdc.js\"\nimport type { PaymentResult } from \"./types.js\"\n\nexport interface WaitOptions {\n timeoutMs?: number\n pollIntervalMs?: number\n signal?: AbortSignal\n}\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\nasync function poll(\n verify: () => Promise<PaymentResult>,\n opts: WaitOptions,\n): Promise<PaymentResult> {\n const timeoutMs = opts.timeoutMs ?? 10 * 60 * 1000\n const pollIntervalMs = opts.pollIntervalMs ?? 3000\n const deadline = Date.now() + timeoutMs\n let last: PaymentResult | undefined\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) break\n last = await verify()\n if (last.matched) return last\n if (last.status === \"duplicate\" || last.status === \"overpaid\") return last\n await sleep(pollIntervalMs)\n }\n return last && last.status !== \"pending\"\n ? { ...last, status: \"expired\" }\n : {\n matched: false,\n status: \"expired\",\n receiver: last?.receiver ?? \"\",\n asset: last?.asset ?? \"HBAR\",\n matches: [],\n reason: \"timed out waiting for payment\",\n }\n}\n\nexport function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHbarPayment(p), p)\n}\nexport function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyHtsPayment(p), p)\n}\nexport function waitForUsdcPayment(p: VerifyUsdcParams & WaitOptions): Promise<PaymentResult> {\n return poll(() => verifyUsdcPayment(p), p)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hbar-kit/payments",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Verify native Hedera payments (HBAR & HTS) by receiver, amount, memo, and time window via the Mirror Node — read-only, non-custodial, bigint-safe.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://devwhodevs.github.io/hbar-kit/",
@@ -47,8 +47,8 @@
47
47
  "module": "./dist/index.js",
48
48
  "types": "./dist/index.d.ts",
49
49
  "dependencies": {
50
- "@hbar-kit/core": "0.1.3",
51
- "@hbar-kit/mirror": "0.1.3"
50
+ "@hbar-kit/core": "0.2.0",
51
+ "@hbar-kit/mirror": "0.1.4"
52
52
  },
53
53
  "devDependencies": {
54
54
  "typescript": "^5.6.0",
package/src/index.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  export { verifyHbarPayment, verifyHtsPayment } from "./verify.js"
2
2
  export type { VerifyHbarParams, VerifyHtsParams, VerifyBaseParams } from "./verify.js"
3
- export { waitForHbarPayment, waitForHtsPayment } from "./wait.js"
3
+ export { waitForHbarPayment, waitForHtsPayment, waitForUsdcPayment } from "./wait.js"
4
4
  export type { WaitOptions } from "./wait.js"
5
+ export {
6
+ verifyUsdcPayment,
7
+ getUsdcTokenId,
8
+ isUsdcPaymentResult,
9
+ USDC_TOKEN_IDS,
10
+ USDC_DECIMALS,
11
+ } from "./usdc.js"
12
+ export type { VerifyUsdcParams } from "./usdc.js"
5
13
  export { hashscanTxUrl } from "./explorer.js"
6
14
  export { netToReceiver, memoMatches, classifyAmount } from "./match.js"
7
15
  export type {
package/src/types.ts CHANGED
@@ -10,7 +10,7 @@ export type PaymentStatus =
10
10
  | "expired"
11
11
  | "failed"
12
12
 
13
- export type PaymentAsset = "HBAR" | { tokenId: string; decimals: number }
13
+ export type PaymentAsset = "HBAR" | { tokenId: string; decimals: number; symbol?: string }
14
14
 
15
15
  export interface PaymentMatch {
16
16
  transactionId: string
package/src/usdc.ts ADDED
@@ -0,0 +1,97 @@
1
+ import {
2
+ assertEntityId,
3
+ InvalidParamsError,
4
+ UnsupportedAssetError,
5
+ type HederaNetwork,
6
+ } from "@hbar-kit/core"
7
+ import { verifyHtsPayment, type VerifyHtsParams } from "./verify.js"
8
+ import type { PaymentAsset, PaymentResult } from "./types.js"
9
+
10
+ /** USDC uses 6 decimals on every network Circle issues it on, including Hedera. */
11
+ export const USDC_DECIMALS = 6
12
+ const USDC_SYMBOL = "USDC"
13
+
14
+ /**
15
+ * Canonical Circle-issued USDC token ids on Hedera, per network.
16
+ *
17
+ * Verified two independent ways before hardcoding — do NOT change without re-verifying both:
18
+ *
19
+ * 1. Live Hedera Mirror Node token metadata (on-chain ground truth):
20
+ * - mainnet `GET https://mainnet-public.mirrornode.hedera.com/api/v1/tokens/0.0.456858`
21
+ * → { symbol: "USDC", name: "USD Coin", decimals: "6", type: "FUNGIBLE_COMMON",
22
+ * treasury_account_id: "0.0.439909" }
23
+ * - testnet `GET https://testnet.mirrornode.hedera.com/api/v1/tokens/0.0.429274`
24
+ * → { symbol: "USDC", name: "USD Coin", decimals: "6", type: "FUNGIBLE_COMMON",
25
+ * treasury_account_id: "0.0.5176" }
26
+ *
27
+ * 2. Circle's official "USDC Contract Addresses" documentation:
28
+ * https://developers.circle.com/stablecoins/usdc-contract-addresses
29
+ * (Hedera row links to hashscan.io/mainnet/token/0.0.456858; Hedera Testnet row links to
30
+ * hashscan.io/testnet/token/0.0.429274). Circle publishes the native 0.0.x form, not an EVM
31
+ * address.
32
+ *
33
+ * Previewnet: Circle does not issue USDC there, so there is no verified id. `getUsdcTokenId`
34
+ * throws rather than silently falling back to a mainnet/testnet id.
35
+ */
36
+ export const USDC_TOKEN_IDS = {
37
+ mainnet: "0.0.456858",
38
+ testnet: "0.0.429274",
39
+ previewnet: undefined,
40
+ } as const satisfies Record<HederaNetwork, string | undefined>
41
+
42
+ /** Resolve the verified USDC token id for a network, or throw if none is known for it. */
43
+ export function getUsdcTokenId(network: HederaNetwork): string {
44
+ const tokenId = USDC_TOKEN_IDS[network]
45
+ if (tokenId === undefined) {
46
+ throw new UnsupportedAssetError(
47
+ `USDC has no verified token id on Hedera ${network}. ` +
48
+ `Pass an explicit \`tokenId\` to verify a custom token, or use "mainnet" or "testnet".`,
49
+ { docsPath: "/guide/verify-usdc-payment" },
50
+ )
51
+ }
52
+ return tokenId
53
+ }
54
+
55
+ /**
56
+ * Params for {@link verifyUsdcPayment}. Same as {@link VerifyHtsParams} minus `tokenId`/`decimals`
57
+ * (USDC is always 6 decimals) and with `network` **required** — USDC token ids and HashScan URLs are
58
+ * network-specific, so there is no implicit default network for the USDC helper.
59
+ */
60
+ export interface VerifyUsdcParams extends Omit<VerifyHtsParams, "tokenId" | "decimals" | "network"> {
61
+ network: HederaNetwork
62
+ /**
63
+ * Override the canonical USDC token id — useful for a dev/testnet mock token. The amount is still
64
+ * parsed at 6 decimals. Production mainnet flows should omit this and use the verified token id.
65
+ */
66
+ tokenId?: string
67
+ }
68
+
69
+ /**
70
+ * Verify a USDC payment on Hedera. A convenience wrapper over {@link verifyHtsPayment} that resolves
71
+ * the canonical USDC token id for `network`, forces 6-decimal amount parsing, and tags the result
72
+ * asset as USDC. Every other semantic — `confirmed`/`pending`/`underpaid`/`overpaid`/`duplicate`/
73
+ * `mismatch`/`expired`/`failed`, memo/amount/time-window matching — is identical to HTS verification.
74
+ */
75
+ export async function verifyUsdcPayment(p: VerifyUsdcParams): Promise<PaymentResult> {
76
+ if (!p.network) {
77
+ throw new InvalidParamsError(
78
+ "`network` is required for USDC verification — USDC token ids and explorer URLs are network-specific",
79
+ { docsPath: "/guide/verify-usdc-payment" },
80
+ )
81
+ }
82
+ const tokenId = p.tokenId ?? getUsdcTokenId(p.network)
83
+ assertEntityId(tokenId)
84
+ const result = await verifyHtsPayment({ ...p, tokenId, decimals: USDC_DECIMALS })
85
+ // verifyHtsPayment always yields a token asset (never "HBAR"); tag it so consumers can show USDC.
86
+ const asset: PaymentAsset =
87
+ result.asset === "HBAR" ? result.asset : { ...result.asset, symbol: USDC_SYMBOL }
88
+ return { ...result, asset }
89
+ }
90
+
91
+ /**
92
+ * True when a PaymentResult was produced by {@link verifyUsdcPayment} (its asset is tagged
93
+ * `symbol: "USDC"`). Useful for narrowing/displaying results from a mixed payment pipeline.
94
+ */
95
+ export function isUsdcPaymentResult(result: PaymentResult): boolean {
96
+ return typeof result.asset === "object" && result.asset.symbol === USDC_SYMBOL
97
+ }
package/src/wait.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  type VerifyHbarParams,
5
5
  type VerifyHtsParams,
6
6
  } from "./verify.js"
7
+ import { verifyUsdcPayment, type VerifyUsdcParams } from "./usdc.js"
7
8
  import type { PaymentResult } from "./types.js"
8
9
 
9
10
  export interface WaitOptions {
@@ -46,3 +47,6 @@ export function waitForHbarPayment(p: VerifyHbarParams & WaitOptions): Promise<P
46
47
  export function waitForHtsPayment(p: VerifyHtsParams & WaitOptions): Promise<PaymentResult> {
47
48
  return poll(() => verifyHtsPayment(p), p)
48
49
  }
50
+ export function waitForUsdcPayment(p: VerifyUsdcParams & WaitOptions): Promise<PaymentResult> {
51
+ return poll(() => verifyUsdcPayment(p), p)
52
+ }