@circle-fin/app-kit 1.15.2 → 1.16.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +5 -2
  3. package/borrow.cjs +28028 -0
  4. package/borrow.cjs.map +1 -0
  5. package/borrow.d.cts +10723 -0
  6. package/borrow.d.mts +10723 -0
  7. package/borrow.d.ts +10723 -0
  8. package/borrow.mjs +28000 -0
  9. package/borrow.mjs.map +1 -0
  10. package/bridge.cjs +1942 -290
  11. package/bridge.d.cts +157 -12
  12. package/bridge.d.mts +157 -12
  13. package/bridge.d.ts +157 -12
  14. package/bridge.mjs +1942 -290
  15. package/chains.cjs +70 -3
  16. package/chains.d.cts +2 -0
  17. package/chains.d.mts +2 -0
  18. package/chains.d.ts +2 -0
  19. package/chains.mjs +70 -3
  20. package/context.cjs +1 -0
  21. package/context.d.cts +157 -12
  22. package/context.d.mts +157 -12
  23. package/context.d.ts +157 -12
  24. package/context.mjs +1 -0
  25. package/earn.cjs +5466 -3459
  26. package/earn.d.cts +1112 -983
  27. package/earn.d.mts +1112 -983
  28. package/earn.d.ts +1112 -983
  29. package/earn.mjs +5466 -3459
  30. package/estimateBridge.cjs +1940 -288
  31. package/estimateBridge.d.cts +157 -12
  32. package/estimateBridge.d.mts +157 -12
  33. package/estimateBridge.d.ts +157 -12
  34. package/estimateBridge.mjs +1940 -288
  35. package/estimateSwap.cjs +8497 -6831
  36. package/estimateSwap.d.cts +157 -12
  37. package/estimateSwap.d.mts +157 -12
  38. package/estimateSwap.d.ts +157 -12
  39. package/estimateSwap.mjs +8498 -6832
  40. package/index.cjs +30741 -18498
  41. package/index.d.cts +22724 -2322
  42. package/index.d.mts +22724 -2322
  43. package/index.d.ts +22724 -2322
  44. package/index.mjs +30682 -18492
  45. package/package.json +17 -7
  46. package/server.cjs +113 -22
  47. package/server.d.cts +10 -9
  48. package/server.d.mts +10 -9
  49. package/server.d.ts +10 -9
  50. package/server.mjs +113 -22
  51. package/swap.cjs +8497 -6831
  52. package/swap.d.cts +157 -12
  53. package/swap.d.mts +157 -12
  54. package/swap.d.ts +157 -12
  55. package/swap.mjs +8498 -6832
  56. package/unifiedBalance.cjs +457 -128
  57. package/unifiedBalance.d.cts +108 -11
  58. package/unifiedBalance.d.mts +108 -11
  59. package/unifiedBalance.d.ts +108 -11
  60. package/unifiedBalance.mjs +457 -128
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circle-fin/app-kit",
3
- "version": "1.15.2",
3
+ "version": "1.16.0",
4
4
  "description": "A one-stop Circle SDK solution for building stablecoin (e.g. USDC) applications, with bridging, swapping, and more on-chain operations",
5
5
  "keywords": [
6
6
  "circle",
@@ -36,12 +36,13 @@
36
36
  "module": "./index.mjs",
37
37
  "types": "./index.d.cts",
38
38
  "dependencies": {
39
- "@circle-fin/onramp-kit": "1.0.1",
40
- "@circle-fin/unified-balance-kit": "1.7.0",
41
- "@circle-fin/provider-gateway-v1": "1.5.0",
42
- "@circle-fin/earn-kit": "1.7.0",
43
- "@circle-fin/bridge-kit": "1.15.1",
44
- "@circle-fin/swap-kit": "1.7.0",
39
+ "@circle-fin/onramp-kit": "1.0.3",
40
+ "@circle-fin/unified-balance-kit": "1.8.0",
41
+ "@circle-fin/provider-gateway-v1": "1.5.1",
42
+ "@circle-fin/earn-kit": "1.8.1",
43
+ "@circle-fin/borrow-kit": "1.0.0",
44
+ "@circle-fin/bridge-kit": "1.15.2",
45
+ "@circle-fin/swap-kit": "1.7.1",
45
46
  "@coral-xyz/anchor": "^0.31.1",
46
47
  "@noble/curves": "1.4.2",
47
48
  "bn.js": "^5.2.3",
@@ -117,6 +118,14 @@
117
118
  "import": "./earn.mjs",
118
119
  "require": "./earn.cjs"
119
120
  },
121
+ "./borrow": {
122
+ "types": {
123
+ "import": "./borrow.d.mts",
124
+ "require": "./borrow.d.cts"
125
+ },
126
+ "import": "./borrow.mjs",
127
+ "require": "./borrow.cjs"
128
+ },
120
129
  "./estimateSwap": {
121
130
  "types": {
122
131
  "import": "./estimateSwap.d.mts",
@@ -152,6 +161,7 @@
152
161
  "estimateSwap.*",
153
162
  "unifiedBalance.*",
154
163
  "earn.*",
164
+ "borrow.*",
155
165
  "index.*",
156
166
  "server.*",
157
167
  "README.md",
package/server.cjs CHANGED
@@ -639,6 +639,34 @@ class KitError extends Error {
639
639
  }
640
640
  };
641
641
 
642
+ /**
643
+ * Render an object value for an error message without letting the rendering
644
+ * itself throw.
645
+ *
646
+ * `JSON.stringify` rejects `BigInt` and circular structures, and a rejected
647
+ * value is exactly the kind of thing a validation error is built from: an
648
+ * on-chain read or a receipt log carries `bigint` fields, so serializing one
649
+ * naively replaces the intended error with a `TypeError`.
650
+ *
651
+ * @param value - The value to render.
652
+ * @returns A JSON string with `bigint` fields stringified, or
653
+ * `'[unserializable object]'` if serialization still fails.
654
+ * @throws Never; serialization failures are caught and rendered as a fallback string.
655
+ * @example
656
+ * ```typescript
657
+ * import { describeObjectValue } from '@core/errors'
658
+ *
659
+ * describeObjectValue({ amount: 1n })
660
+ * // '{"amount":"1"}'
661
+ * ```
662
+ */ function describeObjectValue(value) {
663
+ try {
664
+ return JSON.stringify(value, (_key, entry)=>typeof entry === 'bigint' ? entry.toString() : entry);
665
+ } catch {
666
+ return '[unserializable object]';
667
+ }
668
+ }
669
+
642
670
  /**
643
671
  * Creates error for general validation failure.
644
672
  *
@@ -661,7 +689,7 @@ class KitError extends Error {
661
689
  * // Message: "Validation failed for 'chainId': 999 - Unsupported chain ID"
662
690
  *
663
691
  * throw createValidationFailedError('config', { invalid: true }, 'Missing required properties')
664
- * // Message: "Validation failed for 'config': [object Object] - Missing required properties"
692
+ * // Message: "Validation failed for 'config': {"invalid":true} - Missing required properties"
665
693
  * ```
666
694
  */ function createValidationFailedError(field, value, reason) {
667
695
  // Convert value to string for display, handling different types appropriately
@@ -669,7 +697,7 @@ class KitError extends Error {
669
697
  if (typeof value === 'string') {
670
698
  valueString = `'${value}'`;
671
699
  } else if (typeof value === 'object' && value !== null) {
672
- valueString = JSON.stringify(value);
700
+ valueString = describeObjectValue(value);
673
701
  } else {
674
702
  valueString = String(value);
675
703
  }
@@ -1321,6 +1349,11 @@ var EarnChain;
1321
1349
  "Arc",
1322
1350
  "Arc_Testnet"
1323
1351
  ];
1352
+ var BorrowChain;
1353
+ (function(BorrowChain) {
1354
+ BorrowChain["Arc"] = "Arc";
1355
+ BorrowChain["Arc_Testnet"] = "Arc_Testnet";
1356
+ })(BorrowChain || (BorrowChain = {}));
1324
1357
 
1325
1358
  /**
1326
1359
  * Helper function to define a chain with proper TypeScript typing.
@@ -1663,6 +1696,29 @@ var EarnChain;
1663
1696
  * on EVM-compatible testnet chains. Use this address for testnet adapter
1664
1697
  * integrations (e.g., Arc Testnet).
1665
1698
  */ const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
1699
+ /**
1700
+ * The sender-preserving batching contract on Arc (`Multicall3From`).
1701
+ *
1702
+ * This predeploy batches calls like the ubiquitous Multicall3, but routes each
1703
+ * subcall through Arc's `CallFrom` precompile
1704
+ * (`0x1800000000000000000000000000000000000003`), which executes on behalf of
1705
+ * the transaction's original sender. Every target therefore sees the calling
1706
+ * EOA as `msg.sender`, which is what makes a plain, un-upgraded EOA capable of
1707
+ * an atomic batch on Arc.
1708
+ *
1709
+ * Deployed at the same address on Arc Testnet and Arc Mainnet.
1710
+ *
1711
+ * @see https://docs.arc.io/arc/concepts/batched-transactions
1712
+ */ const BATCHER_CONTRACT_ARC_TESTNET = '0x522fAf9A91c41c443c66765030741e4AaCe147D0';
1713
+ /**
1714
+ * The sender-preserving batching contract on Arc Mainnet (`Multicall3From`).
1715
+ *
1716
+ * See {@link BATCHER_CONTRACT_ARC_TESTNET} for how this contract works.
1717
+ * Requires Arc's Zero7 hardfork, which activates the `CallFrom` precompile
1718
+ * this contract depends on.
1719
+ *
1720
+ * @see https://docs.arc.io/arc/concepts/batched-transactions
1721
+ */ const BATCHER_CONTRACT_ARC_MAINNET = '0x522fAf9A91c41c443c66765030741e4AaCe147D0';
1666
1722
  /**
1667
1723
  * The GatewayWallet contract address for EVM mainnet networks.
1668
1724
  *
@@ -1802,7 +1858,10 @@ var EarnChain;
1802
1858
  },
1803
1859
  kitContracts: {
1804
1860
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1805
- adapter: ADAPTER_CONTRACT_EVM_MAINNET
1861
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1862
+ // Arc is the only chain with a sender-preserving batcher, because it
1863
+ // takes a precompile rather than an ordinary contract to build one.
1864
+ senderPreservingBatcher: BATCHER_CONTRACT_ARC_MAINNET
1806
1865
  },
1807
1866
  gateway: {
1808
1867
  domain: 26,
@@ -1873,7 +1932,10 @@ var EarnChain;
1873
1932
  },
1874
1933
  kitContracts: {
1875
1934
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1876
- adapter: ADAPTER_CONTRACT_EVM_TESTNET
1935
+ adapter: ADAPTER_CONTRACT_EVM_TESTNET,
1936
+ // Arc is the only chain with a sender-preserving batcher, because it
1937
+ // takes a precompile rather than an ordinary contract to build one.
1938
+ senderPreservingBatcher: BATCHER_CONTRACT_ARC_TESTNET
1877
1939
  },
1878
1940
  gateway: {
1879
1941
  domain: 26,
@@ -5291,7 +5353,8 @@ var EarnChain;
5291
5353
  cctpx: zod.z.any().optional(),
5292
5354
  kitContracts: zod.z.object({
5293
5355
  bridge: zod.z.string().optional(),
5294
- adapter: zod.z.string().optional()
5356
+ adapter: zod.z.string().optional(),
5357
+ senderPreservingBatcher: zod.z.string().optional()
5295
5358
  }).optional(),
5296
5359
  gateway: gatewayConfigSchema.optional()
5297
5360
  });
@@ -5487,6 +5550,38 @@ var EarnChain;
5487
5550
  message: `"${chain.chain}" is not a supported earn chain. ` + `Supported chains: ${Object.values(EarnChain).join(', ')}`
5488
5551
  }))
5489
5552
  ]);
5553
+ /**
5554
+ * Zod schema for validating Borrow-specific chain identifiers.
5555
+ *
5556
+ * Validate that the provided chain is supported for Borrow Kit's loan
5557
+ * operations. Unlike {@link earnChainIdentifierSchema}, this includes Arc
5558
+ * mainnet as well as Arc Testnet.
5559
+ *
5560
+ * Accept a BorrowChain enum value, a matching string literal, or a
5561
+ * ChainDefinition for a supported chain.
5562
+ *
5563
+ * @example
5564
+ * ```typescript
5565
+ * import { borrowChainIdentifierSchema } from '@core/chains'
5566
+ * import { Arc, BorrowChain } from '@core/chains'
5567
+ *
5568
+ * // Valid
5569
+ * borrowChainIdentifierSchema.parse(BorrowChain.Arc)
5570
+ * borrowChainIdentifierSchema.parse('Arc')
5571
+ * borrowChainIdentifierSchema.parse(Arc)
5572
+ *
5573
+ * // Invalid (throws ZodError)
5574
+ * borrowChainIdentifierSchema.parse('Solana')
5575
+ * ```
5576
+ */ zod.z.union([
5577
+ zod.z.string().refine((val)=>val in BorrowChain, (val)=>({
5578
+ message: `"${val}" is not a supported borrow chain. ` + `Supported chains: ${Object.values(BorrowChain).join(', ')}`
5579
+ })),
5580
+ zod.z.nativeEnum(BorrowChain),
5581
+ chainDefinitionSchema.refine((chain)=>chain.chain in BorrowChain, (chain)=>({
5582
+ message: `"${chain.chain}" is not a supported borrow chain. ` + `Supported chains: ${Object.values(BorrowChain).join(', ')}`
5583
+ }))
5584
+ ]);
5490
5585
  // Widened views so `.includes()` accepts arbitrary strings under strict TS.
5491
5586
  const EARN_BRIDGE_SOURCE_CHAIN_VALUES = EARN_BRIDGE_SOURCE_BLOCKCHAINS;
5492
5587
  const EARN_BRIDGE_DESTINATION_CHAIN_VALUES = EARN_BRIDGE_DESTINATION_BLOCKCHAINS;
@@ -8650,7 +8745,7 @@ const swapTokenEnumSchema = zod.z.enum([
8650
8745
  return kit;
8651
8746
  }
8652
8747
 
8653
- var version = "1.0.1";
8748
+ var version = "1.0.3";
8654
8749
  var pkg = {
8655
8750
  version: version};
8656
8751
 
@@ -9166,10 +9261,10 @@ function isAbortLikeError(err) {
9166
9261
  * @param widgetPath - Path appended to `widgetBaseUrl`. Leading `/`
9167
9262
  * is required by the URL semantics below — see remarks.
9168
9263
  * @param params - Additional query parameters to set on the launch URL
9169
- * (e.g. `destinationWallet`, `hostApp`). Entries whose value is
9170
- * `undefined` or empty are skipped. The widget reads these from the
9171
- * query string — `destinationWallet` in particular is **required** by
9172
- * the widget, so callers that have it must pass it here.
9264
+ * (e.g. the asset-scoping `chains` / `tokens` / `pairs`). Entries whose
9265
+ * value is `undefined` or empty are skipped. Launch URLs end up in
9266
+ * browser history and access logs, so never pass user identifiers
9267
+ * such as wallet addresses here.
9173
9268
  * @returns A fully-qualified launch URL.
9174
9269
  *
9175
9270
  * @remarks
@@ -9193,9 +9288,9 @@ function isAbortLikeError(err) {
9193
9288
  * @example
9194
9289
  * ```ts
9195
9290
  * composeWidgetUrl('https://onramp.arc.io', 'tok-1', '', {
9196
- * destinationWallet: '0xabc',
9291
+ * tokens: 'USDC',
9197
9292
  * })
9198
- * // → 'https://onramp.arc.io/?sessionToken=tok-1&destinationWallet=0xabc'
9293
+ * // → 'https://onramp.arc.io/?sessionToken=tok-1&tokens=USDC'
9199
9294
  * ```
9200
9295
  *
9201
9296
  * @internal Implementation detail shared by the server kit
@@ -9434,17 +9529,13 @@ function isAbortLikeError(err) {
9434
9529
  const session = unwrapped !== null && typeof unwrapped === 'object' ? unwrapped : {};
9435
9530
  const sessionToken = typeof session['sessionToken'] === 'string' ? session['sessionToken'] : undefined;
9436
9531
  const existingWidgetUrl = typeof session['widgetUrl'] === 'string' && session['widgetUrl'].length > 0 ? session['widgetUrl'] : undefined;
9437
- // The widget reads the destination wallet from the launch URL
9438
- // (`destinationWallet` query param), NOT from the session token —
9439
- // the `/v1/stablecoinKits/sessions` body is anonymous. We must
9440
- // therefore bake it into the composed URL or the widget reports
9441
- // "Destination wallet is required." mid-flow. Host-app asset scoping
9442
- // (`assets`) rides the same launch URL as `chains`/`tokens`/`pairs`.
9532
+ // The launch URL carries only the session token and host-app asset
9533
+ // scoping (`chains`/`tokens`/`pairs`). The destination wallet must
9534
+ // stay out of it: the widget reads the wallet from the signed session
9535
+ // token's `walletAddress` claim, and a query param would only leak
9536
+ // the address into browser history and access logs.
9443
9537
  const assetParams = serializeAssetSelection(params.assets);
9444
- const composedWidgetUrl = sessionToken === undefined ? undefined : composeWidgetUrl(widgetBaseUrl, sessionToken, ONRAMP_WIDGET_PATH, {
9445
- destinationWallet: params.destinationAddress,
9446
- ...assetParams
9447
- });
9538
+ const composedWidgetUrl = sessionToken === undefined ? undefined : composeWidgetUrl(widgetBaseUrl, sessionToken, ONRAMP_WIDGET_PATH, assetParams);
9448
9539
  // When wallets-api returns a `widgetUrl`, asset scoping must still be
9449
9540
  // applied — `assets` is a display-only widget concern and is never
9450
9541
  // forwarded upstream, so we merge the params directly into the returned
package/server.d.cts CHANGED
@@ -1785,17 +1785,17 @@ interface KitEventSubscription<M extends EventMap = KitEventMap> {
1785
1785
  * embeds. Never sent back by the client; it is redeemed inside the
1786
1786
  * widget origin only.
1787
1787
  * - `widgetUrl` — fully composed launch URL (origin + path + token +
1788
- * `destinationWallet`). The server kit prefers to compose this itself
1788
+ * asset-scoping params). The server kit prefers to compose this itself
1789
1789
  * so the client kit has no exposure to the widget origin. The client
1790
1790
  * still accepts a bare `sessionToken` and composes a URL locally as a
1791
1791
  * fallback. `getOnrampUrl` is the single place that enforces the
1792
1792
  * https-scheme / origin-pin safety checks before the URL reaches the
1793
1793
  * DOM — that is a security boundary, not response validation.
1794
1794
  * - `destinationWallet` — the wallet that receives funds. The widget
1795
- * requires it as a launch query param, so the server kit echoes the
1796
- * `destinationAddress` it was minted with here (and bakes it into
1797
- * `widgetUrl`). Used by the client fallback path when it has to
1798
- * compose the URL from a bare `sessionToken`.
1795
+ * reads it from the signed session token, not the launch URL, so the
1796
+ * kit never adds it there. The server kit still echoes the
1797
+ * `destinationAddress` it was minted with here for the host's
1798
+ * convenience.
1799
1799
  * - `expiresAt` — RFC 3339 timestamp. The client refuses to launch an
1800
1800
  * expired session and throws an `INPUT` `KitError` so the host can
1801
1801
  * re-mint.
@@ -1819,8 +1819,8 @@ interface OnrampSession {
1819
1819
  */
1820
1820
  readonly sessionToken: string;
1821
1821
  /**
1822
- * Fully composed launch URL (origin + path + token +
1823
- * `destinationWallet`).
1822
+ * Fully composed launch URL (origin + path + token + optional
1823
+ * asset-scoping params).
1824
1824
  *
1825
1825
  * @remarks
1826
1826
  * The server kit prefers to compose this itself; the client falls
@@ -1833,8 +1833,9 @@ interface OnrampSession {
1833
1833
  *
1834
1834
  * @remarks
1835
1835
  * Echoed verbatim from the `destinationAddress` the server kit
1836
- * minted with. Used by the client kit's URL-composition fallback
1837
- * when `widgetUrl` is absent.
1836
+ * minted with, for the host's convenience (e.g. to display it). Never
1837
+ * added to the launch URL: the widget reads the destination wallet
1838
+ * from the signed session token.
1838
1839
  */
1839
1840
  readonly destinationWallet?: string;
1840
1841
  /**
package/server.d.mts CHANGED
@@ -1785,17 +1785,17 @@ interface KitEventSubscription<M extends EventMap = KitEventMap> {
1785
1785
  * embeds. Never sent back by the client; it is redeemed inside the
1786
1786
  * widget origin only.
1787
1787
  * - `widgetUrl` — fully composed launch URL (origin + path + token +
1788
- * `destinationWallet`). The server kit prefers to compose this itself
1788
+ * asset-scoping params). The server kit prefers to compose this itself
1789
1789
  * so the client kit has no exposure to the widget origin. The client
1790
1790
  * still accepts a bare `sessionToken` and composes a URL locally as a
1791
1791
  * fallback. `getOnrampUrl` is the single place that enforces the
1792
1792
  * https-scheme / origin-pin safety checks before the URL reaches the
1793
1793
  * DOM — that is a security boundary, not response validation.
1794
1794
  * - `destinationWallet` — the wallet that receives funds. The widget
1795
- * requires it as a launch query param, so the server kit echoes the
1796
- * `destinationAddress` it was minted with here (and bakes it into
1797
- * `widgetUrl`). Used by the client fallback path when it has to
1798
- * compose the URL from a bare `sessionToken`.
1795
+ * reads it from the signed session token, not the launch URL, so the
1796
+ * kit never adds it there. The server kit still echoes the
1797
+ * `destinationAddress` it was minted with here for the host's
1798
+ * convenience.
1799
1799
  * - `expiresAt` — RFC 3339 timestamp. The client refuses to launch an
1800
1800
  * expired session and throws an `INPUT` `KitError` so the host can
1801
1801
  * re-mint.
@@ -1819,8 +1819,8 @@ interface OnrampSession {
1819
1819
  */
1820
1820
  readonly sessionToken: string;
1821
1821
  /**
1822
- * Fully composed launch URL (origin + path + token +
1823
- * `destinationWallet`).
1822
+ * Fully composed launch URL (origin + path + token + optional
1823
+ * asset-scoping params).
1824
1824
  *
1825
1825
  * @remarks
1826
1826
  * The server kit prefers to compose this itself; the client falls
@@ -1833,8 +1833,9 @@ interface OnrampSession {
1833
1833
  *
1834
1834
  * @remarks
1835
1835
  * Echoed verbatim from the `destinationAddress` the server kit
1836
- * minted with. Used by the client kit's URL-composition fallback
1837
- * when `widgetUrl` is absent.
1836
+ * minted with, for the host's convenience (e.g. to display it). Never
1837
+ * added to the launch URL: the widget reads the destination wallet
1838
+ * from the signed session token.
1838
1839
  */
1839
1840
  readonly destinationWallet?: string;
1840
1841
  /**
package/server.d.ts CHANGED
@@ -1785,17 +1785,17 @@ interface KitEventSubscription<M extends EventMap = KitEventMap> {
1785
1785
  * embeds. Never sent back by the client; it is redeemed inside the
1786
1786
  * widget origin only.
1787
1787
  * - `widgetUrl` — fully composed launch URL (origin + path + token +
1788
- * `destinationWallet`). The server kit prefers to compose this itself
1788
+ * asset-scoping params). The server kit prefers to compose this itself
1789
1789
  * so the client kit has no exposure to the widget origin. The client
1790
1790
  * still accepts a bare `sessionToken` and composes a URL locally as a
1791
1791
  * fallback. `getOnrampUrl` is the single place that enforces the
1792
1792
  * https-scheme / origin-pin safety checks before the URL reaches the
1793
1793
  * DOM — that is a security boundary, not response validation.
1794
1794
  * - `destinationWallet` — the wallet that receives funds. The widget
1795
- * requires it as a launch query param, so the server kit echoes the
1796
- * `destinationAddress` it was minted with here (and bakes it into
1797
- * `widgetUrl`). Used by the client fallback path when it has to
1798
- * compose the URL from a bare `sessionToken`.
1795
+ * reads it from the signed session token, not the launch URL, so the
1796
+ * kit never adds it there. The server kit still echoes the
1797
+ * `destinationAddress` it was minted with here for the host's
1798
+ * convenience.
1799
1799
  * - `expiresAt` — RFC 3339 timestamp. The client refuses to launch an
1800
1800
  * expired session and throws an `INPUT` `KitError` so the host can
1801
1801
  * re-mint.
@@ -1819,8 +1819,8 @@ interface OnrampSession {
1819
1819
  */
1820
1820
  readonly sessionToken: string;
1821
1821
  /**
1822
- * Fully composed launch URL (origin + path + token +
1823
- * `destinationWallet`).
1822
+ * Fully composed launch URL (origin + path + token + optional
1823
+ * asset-scoping params).
1824
1824
  *
1825
1825
  * @remarks
1826
1826
  * The server kit prefers to compose this itself; the client falls
@@ -1833,8 +1833,9 @@ interface OnrampSession {
1833
1833
  *
1834
1834
  * @remarks
1835
1835
  * Echoed verbatim from the `destinationAddress` the server kit
1836
- * minted with. Used by the client kit's URL-composition fallback
1837
- * when `widgetUrl` is absent.
1836
+ * minted with, for the host's convenience (e.g. to display it). Never
1837
+ * added to the launch URL: the widget reads the destination wallet
1838
+ * from the signed session token.
1838
1839
  */
1839
1840
  readonly destinationWallet?: string;
1840
1841
  /**
package/server.mjs CHANGED
@@ -633,6 +633,34 @@ class KitError extends Error {
633
633
  }
634
634
  };
635
635
 
636
+ /**
637
+ * Render an object value for an error message without letting the rendering
638
+ * itself throw.
639
+ *
640
+ * `JSON.stringify` rejects `BigInt` and circular structures, and a rejected
641
+ * value is exactly the kind of thing a validation error is built from: an
642
+ * on-chain read or a receipt log carries `bigint` fields, so serializing one
643
+ * naively replaces the intended error with a `TypeError`.
644
+ *
645
+ * @param value - The value to render.
646
+ * @returns A JSON string with `bigint` fields stringified, or
647
+ * `'[unserializable object]'` if serialization still fails.
648
+ * @throws Never; serialization failures are caught and rendered as a fallback string.
649
+ * @example
650
+ * ```typescript
651
+ * import { describeObjectValue } from '@core/errors'
652
+ *
653
+ * describeObjectValue({ amount: 1n })
654
+ * // '{"amount":"1"}'
655
+ * ```
656
+ */ function describeObjectValue(value) {
657
+ try {
658
+ return JSON.stringify(value, (_key, entry)=>typeof entry === 'bigint' ? entry.toString() : entry);
659
+ } catch {
660
+ return '[unserializable object]';
661
+ }
662
+ }
663
+
636
664
  /**
637
665
  * Creates error for general validation failure.
638
666
  *
@@ -655,7 +683,7 @@ class KitError extends Error {
655
683
  * // Message: "Validation failed for 'chainId': 999 - Unsupported chain ID"
656
684
  *
657
685
  * throw createValidationFailedError('config', { invalid: true }, 'Missing required properties')
658
- * // Message: "Validation failed for 'config': [object Object] - Missing required properties"
686
+ * // Message: "Validation failed for 'config': {"invalid":true} - Missing required properties"
659
687
  * ```
660
688
  */ function createValidationFailedError(field, value, reason) {
661
689
  // Convert value to string for display, handling different types appropriately
@@ -663,7 +691,7 @@ class KitError extends Error {
663
691
  if (typeof value === 'string') {
664
692
  valueString = `'${value}'`;
665
693
  } else if (typeof value === 'object' && value !== null) {
666
- valueString = JSON.stringify(value);
694
+ valueString = describeObjectValue(value);
667
695
  } else {
668
696
  valueString = String(value);
669
697
  }
@@ -1315,6 +1343,11 @@ var EarnChain;
1315
1343
  "Arc",
1316
1344
  "Arc_Testnet"
1317
1345
  ];
1346
+ var BorrowChain;
1347
+ (function(BorrowChain) {
1348
+ BorrowChain["Arc"] = "Arc";
1349
+ BorrowChain["Arc_Testnet"] = "Arc_Testnet";
1350
+ })(BorrowChain || (BorrowChain = {}));
1318
1351
 
1319
1352
  /**
1320
1353
  * Helper function to define a chain with proper TypeScript typing.
@@ -1657,6 +1690,29 @@ var EarnChain;
1657
1690
  * on EVM-compatible testnet chains. Use this address for testnet adapter
1658
1691
  * integrations (e.g., Arc Testnet).
1659
1692
  */ const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
1693
+ /**
1694
+ * The sender-preserving batching contract on Arc (`Multicall3From`).
1695
+ *
1696
+ * This predeploy batches calls like the ubiquitous Multicall3, but routes each
1697
+ * subcall through Arc's `CallFrom` precompile
1698
+ * (`0x1800000000000000000000000000000000000003`), which executes on behalf of
1699
+ * the transaction's original sender. Every target therefore sees the calling
1700
+ * EOA as `msg.sender`, which is what makes a plain, un-upgraded EOA capable of
1701
+ * an atomic batch on Arc.
1702
+ *
1703
+ * Deployed at the same address on Arc Testnet and Arc Mainnet.
1704
+ *
1705
+ * @see https://docs.arc.io/arc/concepts/batched-transactions
1706
+ */ const BATCHER_CONTRACT_ARC_TESTNET = '0x522fAf9A91c41c443c66765030741e4AaCe147D0';
1707
+ /**
1708
+ * The sender-preserving batching contract on Arc Mainnet (`Multicall3From`).
1709
+ *
1710
+ * See {@link BATCHER_CONTRACT_ARC_TESTNET} for how this contract works.
1711
+ * Requires Arc's Zero7 hardfork, which activates the `CallFrom` precompile
1712
+ * this contract depends on.
1713
+ *
1714
+ * @see https://docs.arc.io/arc/concepts/batched-transactions
1715
+ */ const BATCHER_CONTRACT_ARC_MAINNET = '0x522fAf9A91c41c443c66765030741e4AaCe147D0';
1660
1716
  /**
1661
1717
  * The GatewayWallet contract address for EVM mainnet networks.
1662
1718
  *
@@ -1796,7 +1852,10 @@ var EarnChain;
1796
1852
  },
1797
1853
  kitContracts: {
1798
1854
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1799
- adapter: ADAPTER_CONTRACT_EVM_MAINNET
1855
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1856
+ // Arc is the only chain with a sender-preserving batcher, because it
1857
+ // takes a precompile rather than an ordinary contract to build one.
1858
+ senderPreservingBatcher: BATCHER_CONTRACT_ARC_MAINNET
1800
1859
  },
1801
1860
  gateway: {
1802
1861
  domain: 26,
@@ -1867,7 +1926,10 @@ var EarnChain;
1867
1926
  },
1868
1927
  kitContracts: {
1869
1928
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1870
- adapter: ADAPTER_CONTRACT_EVM_TESTNET
1929
+ adapter: ADAPTER_CONTRACT_EVM_TESTNET,
1930
+ // Arc is the only chain with a sender-preserving batcher, because it
1931
+ // takes a precompile rather than an ordinary contract to build one.
1932
+ senderPreservingBatcher: BATCHER_CONTRACT_ARC_TESTNET
1871
1933
  },
1872
1934
  gateway: {
1873
1935
  domain: 26,
@@ -5285,7 +5347,8 @@ var EarnChain;
5285
5347
  cctpx: z.any().optional(),
5286
5348
  kitContracts: z.object({
5287
5349
  bridge: z.string().optional(),
5288
- adapter: z.string().optional()
5350
+ adapter: z.string().optional(),
5351
+ senderPreservingBatcher: z.string().optional()
5289
5352
  }).optional(),
5290
5353
  gateway: gatewayConfigSchema.optional()
5291
5354
  });
@@ -5481,6 +5544,38 @@ var EarnChain;
5481
5544
  message: `"${chain.chain}" is not a supported earn chain. ` + `Supported chains: ${Object.values(EarnChain).join(', ')}`
5482
5545
  }))
5483
5546
  ]);
5547
+ /**
5548
+ * Zod schema for validating Borrow-specific chain identifiers.
5549
+ *
5550
+ * Validate that the provided chain is supported for Borrow Kit's loan
5551
+ * operations. Unlike {@link earnChainIdentifierSchema}, this includes Arc
5552
+ * mainnet as well as Arc Testnet.
5553
+ *
5554
+ * Accept a BorrowChain enum value, a matching string literal, or a
5555
+ * ChainDefinition for a supported chain.
5556
+ *
5557
+ * @example
5558
+ * ```typescript
5559
+ * import { borrowChainIdentifierSchema } from '@core/chains'
5560
+ * import { Arc, BorrowChain } from '@core/chains'
5561
+ *
5562
+ * // Valid
5563
+ * borrowChainIdentifierSchema.parse(BorrowChain.Arc)
5564
+ * borrowChainIdentifierSchema.parse('Arc')
5565
+ * borrowChainIdentifierSchema.parse(Arc)
5566
+ *
5567
+ * // Invalid (throws ZodError)
5568
+ * borrowChainIdentifierSchema.parse('Solana')
5569
+ * ```
5570
+ */ z.union([
5571
+ z.string().refine((val)=>val in BorrowChain, (val)=>({
5572
+ message: `"${val}" is not a supported borrow chain. ` + `Supported chains: ${Object.values(BorrowChain).join(', ')}`
5573
+ })),
5574
+ z.nativeEnum(BorrowChain),
5575
+ chainDefinitionSchema.refine((chain)=>chain.chain in BorrowChain, (chain)=>({
5576
+ message: `"${chain.chain}" is not a supported borrow chain. ` + `Supported chains: ${Object.values(BorrowChain).join(', ')}`
5577
+ }))
5578
+ ]);
5484
5579
  // Widened views so `.includes()` accepts arbitrary strings under strict TS.
5485
5580
  const EARN_BRIDGE_SOURCE_CHAIN_VALUES = EARN_BRIDGE_SOURCE_BLOCKCHAINS;
5486
5581
  const EARN_BRIDGE_DESTINATION_CHAIN_VALUES = EARN_BRIDGE_DESTINATION_BLOCKCHAINS;
@@ -8644,7 +8739,7 @@ const swapTokenEnumSchema = z.enum([
8644
8739
  return kit;
8645
8740
  }
8646
8741
 
8647
- var version = "1.0.1";
8742
+ var version = "1.0.3";
8648
8743
  var pkg = {
8649
8744
  version: version};
8650
8745
 
@@ -9160,10 +9255,10 @@ function isAbortLikeError(err) {
9160
9255
  * @param widgetPath - Path appended to `widgetBaseUrl`. Leading `/`
9161
9256
  * is required by the URL semantics below — see remarks.
9162
9257
  * @param params - Additional query parameters to set on the launch URL
9163
- * (e.g. `destinationWallet`, `hostApp`). Entries whose value is
9164
- * `undefined` or empty are skipped. The widget reads these from the
9165
- * query string — `destinationWallet` in particular is **required** by
9166
- * the widget, so callers that have it must pass it here.
9258
+ * (e.g. the asset-scoping `chains` / `tokens` / `pairs`). Entries whose
9259
+ * value is `undefined` or empty are skipped. Launch URLs end up in
9260
+ * browser history and access logs, so never pass user identifiers
9261
+ * such as wallet addresses here.
9167
9262
  * @returns A fully-qualified launch URL.
9168
9263
  *
9169
9264
  * @remarks
@@ -9187,9 +9282,9 @@ function isAbortLikeError(err) {
9187
9282
  * @example
9188
9283
  * ```ts
9189
9284
  * composeWidgetUrl('https://onramp.arc.io', 'tok-1', '', {
9190
- * destinationWallet: '0xabc',
9285
+ * tokens: 'USDC',
9191
9286
  * })
9192
- * // → 'https://onramp.arc.io/?sessionToken=tok-1&destinationWallet=0xabc'
9287
+ * // → 'https://onramp.arc.io/?sessionToken=tok-1&tokens=USDC'
9193
9288
  * ```
9194
9289
  *
9195
9290
  * @internal Implementation detail shared by the server kit
@@ -9428,17 +9523,13 @@ function isAbortLikeError(err) {
9428
9523
  const session = unwrapped !== null && typeof unwrapped === 'object' ? unwrapped : {};
9429
9524
  const sessionToken = typeof session['sessionToken'] === 'string' ? session['sessionToken'] : undefined;
9430
9525
  const existingWidgetUrl = typeof session['widgetUrl'] === 'string' && session['widgetUrl'].length > 0 ? session['widgetUrl'] : undefined;
9431
- // The widget reads the destination wallet from the launch URL
9432
- // (`destinationWallet` query param), NOT from the session token —
9433
- // the `/v1/stablecoinKits/sessions` body is anonymous. We must
9434
- // therefore bake it into the composed URL or the widget reports
9435
- // "Destination wallet is required." mid-flow. Host-app asset scoping
9436
- // (`assets`) rides the same launch URL as `chains`/`tokens`/`pairs`.
9526
+ // The launch URL carries only the session token and host-app asset
9527
+ // scoping (`chains`/`tokens`/`pairs`). The destination wallet must
9528
+ // stay out of it: the widget reads the wallet from the signed session
9529
+ // token's `walletAddress` claim, and a query param would only leak
9530
+ // the address into browser history and access logs.
9437
9531
  const assetParams = serializeAssetSelection(params.assets);
9438
- const composedWidgetUrl = sessionToken === undefined ? undefined : composeWidgetUrl(widgetBaseUrl, sessionToken, ONRAMP_WIDGET_PATH, {
9439
- destinationWallet: params.destinationAddress,
9440
- ...assetParams
9441
- });
9532
+ const composedWidgetUrl = sessionToken === undefined ? undefined : composeWidgetUrl(widgetBaseUrl, sessionToken, ONRAMP_WIDGET_PATH, assetParams);
9442
9533
  // When wallets-api returns a `widgetUrl`, asset scoping must still be
9443
9534
  // applied — `assets` is a display-only widget concern and is never
9444
9535
  // forwarded upstream, so we merge the params directly into the returned