@circle-fin/app-kit 1.10.0 → 1.12.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/bridge.mjs CHANGED
@@ -16,6 +16,17 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
+ // Buffer polyfill setup - executes before any other code
20
+ // Ensures globalThis.Buffer is available for Solana libraries
21
+ import { Buffer } from 'buffer';
22
+ if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
23
+ globalThis.Buffer = Buffer;
24
+ }
25
+ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
26
+ window.Buffer = Buffer;
27
+ }
28
+
29
+
19
30
  import { z } from 'zod';
20
31
  import pino from 'pino';
21
32
  import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ethersproject/units';
@@ -27,6 +38,7 @@ import { PublicKey } from '@solana/web3.js';
27
38
  import 'bn.js';
28
39
  import '@coral-xyz/anchor';
29
40
  import '@noble/curves/ed25519';
41
+ import 'viem';
30
42
  import { keccak256 } from '@ethersproject/keccak256';
31
43
 
32
44
  // Import global type declarations
@@ -44,6 +56,27 @@ import { keccak256 } from '@ethersproject/keccak256';
44
56
  * }
45
57
  * ```
46
58
  */ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
59
+ /**
60
+ * Return the SDK User-Agent request header only when running in Node.js.
61
+ *
62
+ * Browsers forbid manually setting `User-Agent`, and a custom fallback header
63
+ * can trigger CORS preflight. Non-Node server runtimes also omit this optional
64
+ * attribution header because they cannot set it reliably.
65
+ *
66
+ * @returns A User-Agent header in Node.js, or an empty object otherwise.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * import { getNodeUserAgentHeader } from '@core/utils'
71
+ *
72
+ * const headers = {
73
+ * 'Content-Type': 'application/json',
74
+ * ...getNodeUserAgentHeader(),
75
+ * }
76
+ * ```
77
+ */ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
78
+ 'User-Agent': getUserAgent()
79
+ } : {};
47
80
  /**
48
81
  * Detect the runtime environment and return a shortened identifier.
49
82
  *
@@ -2523,6 +2556,8 @@ class KitError extends Error {
2523
2556
  Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
2524
2557
  Blockchain["XDC"] = "XDC";
2525
2558
  Blockchain["XDC_Apothem"] = "XDC_Apothem";
2559
+ Blockchain["X_Layer"] = "X_Layer";
2560
+ Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
2526
2561
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
2527
2562
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
2528
2563
  })(Blockchain || (Blockchain = {}));
@@ -2576,6 +2611,7 @@ var BridgeChain;
2576
2611
  BridgeChain["Unichain"] = "Unichain";
2577
2612
  BridgeChain["World_Chain"] = "World_Chain";
2578
2613
  BridgeChain["XDC"] = "XDC";
2614
+ BridgeChain["X_Layer"] = "X_Layer";
2579
2615
  // Testnet chains with CCTPv2 support
2580
2616
  BridgeChain["Arc_Testnet"] = "Arc_Testnet";
2581
2617
  BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
@@ -2601,6 +2637,7 @@ var BridgeChain;
2601
2637
  BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
2602
2638
  BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
2603
2639
  BridgeChain["XDC_Apothem"] = "XDC_Apothem";
2640
+ BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
2604
2641
  })(BridgeChain || (BridgeChain = {}));
2605
2642
  var UnifiedBalanceChain;
2606
2643
  (function(UnifiedBalanceChain) {
@@ -5138,7 +5175,8 @@ var EarnChain;
5138
5175
  isTestnet: true,
5139
5176
  explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
5140
5177
  rpcEndpoints: [
5141
- 'https://rpc-amoy.polygon.technology'
5178
+ 'https://polygon-amoy-bor-rpc.publicnode.com',
5179
+ 'https://polygon-amoy.drpc.org'
5142
5180
  ],
5143
5181
  eurcAddress: null,
5144
5182
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
@@ -6003,6 +6041,104 @@ var EarnChain;
6003
6041
  }
6004
6042
  });
6005
6043
 
6044
+ /**
6045
+ * X Layer Mainnet chain definition
6046
+ * @remarks
6047
+ * This represents the official production network for the X Layer blockchain.
6048
+ * X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
6049
+ * using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
6050
+ * OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
6051
+ */ const XLayer = defineChain({
6052
+ type: 'evm',
6053
+ chain: Blockchain.X_Layer,
6054
+ name: 'X Layer',
6055
+ title: 'X Layer Mainnet',
6056
+ nativeCurrency: {
6057
+ name: 'OKB',
6058
+ symbol: 'OKB',
6059
+ decimals: 18
6060
+ },
6061
+ chainId: 196,
6062
+ isTestnet: false,
6063
+ explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
6064
+ rpcEndpoints: [
6065
+ 'https://xlayerrpc.okx.com'
6066
+ ],
6067
+ eurcAddress: null,
6068
+ usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
6069
+ usdtAddress: null,
6070
+ cctp: {
6071
+ domain: 37,
6072
+ contracts: {
6073
+ v2: {
6074
+ type: 'split',
6075
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
6076
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
6077
+ confirmations: 65,
6078
+ fastConfirmations: 1
6079
+ }
6080
+ },
6081
+ forwarderSupported: {
6082
+ source: false,
6083
+ destination: false
6084
+ }
6085
+ },
6086
+ kitContracts: {
6087
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
6088
+ }
6089
+ });
6090
+
6091
+ /**
6092
+ * X Layer Testnet chain definition
6093
+ * @remarks
6094
+ * This represents the official test network for the X Layer blockchain.
6095
+ * X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
6096
+ * using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
6097
+ * OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
6098
+ */ const XLayerTestnet = defineChain({
6099
+ type: 'evm',
6100
+ chain: Blockchain.X_Layer_Testnet,
6101
+ name: 'X Layer Testnet',
6102
+ title: 'X Layer Testnet',
6103
+ nativeCurrency: {
6104
+ name: 'OKB',
6105
+ symbol: 'OKB',
6106
+ decimals: 18
6107
+ },
6108
+ chainId: 1952,
6109
+ isTestnet: true,
6110
+ // Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
6111
+ // testnet URL targets the deprecated pre-rebrand chain ID 195, not this
6112
+ // chain's ID (1952). Verified against the internal chain-expansion-scripts
6113
+ // config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
6114
+ explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
6115
+ rpcEndpoints: [
6116
+ 'https://testrpc.xlayer.tech'
6117
+ ],
6118
+ eurcAddress: null,
6119
+ usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
6120
+ usdtAddress: null,
6121
+ cctp: {
6122
+ domain: 37,
6123
+ contracts: {
6124
+ v2: {
6125
+ type: 'split',
6126
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
6127
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
6128
+ confirmations: 65,
6129
+ fastConfirmations: 1
6130
+ }
6131
+ },
6132
+ forwarderSupported: {
6133
+ source: false,
6134
+ destination: false
6135
+ }
6136
+ },
6137
+ kitContracts: {
6138
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
6139
+ }
6140
+ });
6141
+
6006
6142
  /**
6007
6143
  * ZKSync Era Mainnet chain definition
6008
6144
  * @remarks
@@ -6122,6 +6258,8 @@ var Chains = /*#__PURE__*/Object.freeze({
6122
6258
  WorldChainSepolia: WorldChainSepolia,
6123
6259
  XDC: XDC,
6124
6260
  XDCApothem: XDCApothem,
6261
+ XLayer: XLayer,
6262
+ XLayerTestnet: XLayerTestnet,
6125
6263
  ZKSyncEra: ZKSyncEra,
6126
6264
  ZKSyncEraSepolia: ZKSyncEraSepolia
6127
6265
  });
@@ -7470,13 +7608,12 @@ const swapTokenEnumSchema = z.enum([
7470
7608
  headers: {
7471
7609
  ...DEFAULT_CONFIG$1.headers,
7472
7610
  ...config.headers ?? {},
7473
- // In browser environments, directly setting the 'User-Agent' or similar headers is restricted and may be ignored or cause errors.
7474
- // This is why we use the 'X-User-Agent' header instead.
7475
- ...typeof window === 'undefined' ? {
7476
- 'User-Agent': getUserAgent()
7477
- } : {
7478
- 'X-User-Agent': getUserAgent()
7479
- }
7611
+ // Browsers forbid setting a user-agent request header, and the custom
7612
+ // fallback header the SDK used instead trips CORS preflight against the
7613
+ // Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
7614
+ // blocking the request. So send the SDK user agent only in Node;
7615
+ // browsers omit it entirely.
7616
+ ...getNodeUserAgentHeader()
7480
7617
  }
7481
7618
  };
7482
7619
  let lastError;
@@ -8177,6 +8314,7 @@ const swapTokenEnumSchema = z.enum([
8177
8314
  [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
8178
8315
  [Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
8179
8316
  [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
8317
+ [Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
8180
8318
  [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
8181
8319
  // =========================================================================
8182
8320
  // Testnets (alphabetically sorted)
@@ -8211,6 +8349,7 @@ const swapTokenEnumSchema = z.enum([
8211
8349
  [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
8212
8350
  [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
8213
8351
  [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
8352
+ [Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
8214
8353
  [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
8215
8354
  }
8216
8355
  };
@@ -9262,6 +9401,7 @@ function resolveOptions(options) {
9262
9401
  if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
9263
9402
  if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
9264
9403
  if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
9404
+ if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
9265
9405
  if (payload.errorDetails !== undefined) {
9266
9406
  const errorDetails = {
9267
9407
  ...payload.errorDetails.errorCode !== undefined && {
@@ -9332,18 +9472,15 @@ function resolveOptions(options) {
9332
9472
  timeoutHandle.unref();
9333
9473
  }
9334
9474
  try {
9335
- const isNode = isNodeEnvironment();
9336
- const userAgent = getUserAgent();
9337
9475
  await fetch(getLogsUrl(), {
9338
9476
  method: 'POST',
9339
9477
  headers: {
9340
9478
  'Content-Type': 'application/json',
9341
- // Browser restricts setting User-Agent; use X-User-Agent instead.
9342
- ...isNode ? {
9343
- 'User-Agent': userAgent
9344
- } : {
9345
- 'X-User-Agent': userAgent
9346
- }
9479
+ // Browsers forbid setting a user-agent request header, and the custom
9480
+ // fallback header the SDK used instead trips CORS preflight (it isn't
9481
+ // in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
9482
+ // it only in Node; browsers omit it entirely.
9483
+ ...getNodeUserAgentHeader()
9347
9484
  },
9348
9485
  body: JSON.stringify(toSafePayload(payload)),
9349
9486
  signal: controller.signal
@@ -9474,7 +9611,7 @@ function resolveOptions(options) {
9474
9611
  // discards the stack trace, nested `cause`, and any custom Error
9475
9612
  // properties — exactly the context an on-call needs when a
9476
9613
  // resolver-closure regression triggers this path.
9477
- console.warn(`[stablecoin-kits telemetry] dropped error event '${eventType}':`, cause);
9614
+ console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
9478
9615
  } catch {
9479
9616
  // console.warn itself throwing is the user's environment; nothing more we
9480
9617
  // can do without risking the original operation error.
@@ -9490,7 +9627,9 @@ function resolveOptions(options) {
9490
9627
  sdkVersion: config.sdkVersion,
9491
9628
  eventType,
9492
9629
  timestamp: new Date().toISOString(),
9493
- errorDetails,
9630
+ ...errorDetails !== undefined && {
9631
+ errorDetails
9632
+ },
9494
9633
  clientContext: buildClientContext(),
9495
9634
  ...context?.sourceChain != null && {
9496
9635
  sourceChain: context.sourceChain
@@ -9506,6 +9645,9 @@ function resolveOptions(options) {
9506
9645
  },
9507
9646
  ...context?.txHash != null && {
9508
9647
  txHash: context.txHash
9648
+ },
9649
+ ...context?.correlationId != null && {
9650
+ correlationId: context.correlationId
9509
9651
  }
9510
9652
  };
9511
9653
  }
@@ -9619,7 +9761,7 @@ function resolveOptions(options) {
9619
9761
  }
9620
9762
 
9621
9763
  var name$2 = "@circle-fin/bridge-kit";
9622
- var version$3 = "1.12.1";
9764
+ var version$3 = "1.13.0";
9623
9765
  var pkg$3 = {
9624
9766
  name: name$2,
9625
9767
  version: version$3};
@@ -9656,13 +9798,21 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
9656
9798
  computeFee: z.function().returns(z.string().or(z.promise(z.string()))).optional(),
9657
9799
  calculateFee: z.function().returns(z.string().or(z.promise(z.string()))).optional(),
9658
9800
  resolveFeeRecipientAddress: z.function().returns(z.string().or(z.promise(z.string())))
9659
- }).strict().refine((data)=>{
9801
+ }).strict().superRefine((data, ctx)=>{
9660
9802
  const hasComputeFee = data.computeFee !== undefined;
9661
9803
  const hasCalculateFee = data.calculateFee !== undefined;
9662
- // XOR: exactly one must be provided
9663
- return hasComputeFee !== hasCalculateFee;
9664
- }, {
9665
- message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
9804
+ if (hasComputeFee && hasCalculateFee) {
9805
+ ctx.addIssue({
9806
+ code: z.ZodIssueCode.custom,
9807
+ message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
9808
+ });
9809
+ }
9810
+ if (!hasComputeFee && !hasCalculateFee) {
9811
+ ctx.addIssue({
9812
+ code: z.ZodIssueCode.custom,
9813
+ message: 'Provide either computeFee or calculateFee. Use computeFee (recommended) for human-readable amounts.'
9814
+ });
9815
+ }
9666
9816
  });
9667
9817
  /**
9668
9818
  * Assert that the provided value conforms to {@link CustomFeePolicy}.
@@ -12216,7 +12366,13 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
12216
12366
  /**
12217
12367
  * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
12218
12368
  * hookData must start with.
12219
- */ const CCTP_FORWARD_MAGIC_HEX = Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
12369
+ *
12370
+ * Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
12371
+ * so this module-level constant does not reference the Node `Buffer` global at
12372
+ * import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
12373
+ * bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
12374
+ * that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
12375
+ */ const CCTP_FORWARD_MAGIC_HEX = Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
12220
12376
  /**
12221
12377
  * Determine whether a hookData blob begins with the `cctp-forward` envelope.
12222
12378
  *
@@ -12417,14 +12573,32 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
12417
12573
  ;
12418
12574
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
12419
12575
  ;
12420
- // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
12421
- // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
12422
- // Kept separate from the fee-estimate averages above.
12423
- const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
12576
+ // Gas FLOORS, not ceilings kept separate from the fee-estimate averages
12577
+ // above. `executePreparedChainRequest` submits
12578
+ // max(estimate * buffer, floor), so a chain whose real cost exceeds the floor
12579
+ // is covered by its own estimate, and a chain whose estimator under-reports
12580
+ // (Cronos: returns 30_600 where the EIP-7623 calldata floor is 45_000) is
12581
+ // covered by the floor.
12582
+ //
12583
+ // Two distinct chain surcharges drive these numbers, both measured live:
12584
+ // Sei — ~+51_500 per NEWLY CREATED storage slot (73_595 vs vanilla 22_100);
12585
+ // no flat per-tx surcharge (31_535, identical to Base).
12586
+ // Edge — ~+53_200 flat on EVERY tx (84_751 vs Base 31_535); storage priced
12587
+ // normally. Edge therefore fails warm as well as cold.
12588
+ // A floor must clear the worst COLD cost, since a slot that exists at estimate
12589
+ // time can be consumed before inclusion and cost a full step more on execution.
12590
+ // Each floor is therefore derived from the worst observed estimate *after* the
12591
+ // 1.25x buffer, plus headroom — sizing it below the buffered value would leave
12592
+ // the estimate governing and defeat the point of the floor.
12593
+ //
12594
+ // The `*_GAS_LIMIT_EVM` names are kept despite these being floors: they are
12595
+ // exported, so renaming to `*_GAS_FLOOR_EVM` would be a breaking change for
12596
+ // consumers. Read "LIMIT" here as "the limit we submit", never as a ceiling.
12597
+ const APPROVE_GAS_LIMIT_EVM = 150_000n // buffered worst cold 149_355 (Edge Testnet 119_484 x 1.25) + drift headroom
12424
12598
  ;
12425
- const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
12599
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 500_000n // buffered worst 474_078 (Sei 379_263 x 1.25) + ~26k headroom
12426
12600
  ;
12427
- const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
12601
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839; clears Cronos' calldata floor ~10x
12428
12602
  ;
12429
12603
  /**
12430
12604
  * The minimum finality threshold for CCTPv2 transfers.
@@ -13889,6 +14063,63 @@ function hasPendingState(analysis, result) {
13889
14063
  return waitForPendingTransaction(pendingStep, adapter, chain);
13890
14064
  }
13891
14065
 
14066
+ /**
14067
+ * Multiplier applied to a successful gas estimate before it is submitted.
14068
+ *
14069
+ * Estimates are exact, not padded: Sei returns 109_739 for an approve that
14070
+ * consumes 107_717 (1.9% headroom). Chains that price storage in large steps
14071
+ * can exceed the estimate if state changes between estimation and inclusion,
14072
+ * so the estimate is padded before use.
14073
+ *
14074
+ * @remarks
14075
+ * This buffer alone does NOT cover Sei's ~51_500 per-new-slot step at approve
14076
+ * scale (25% of ~110_000 is only ~27_500). For approve, the FLOOR is what
14077
+ * covers a slot that exists at estimation time and is consumed before
14078
+ * inclusion — so do not lower `APPROVE_GAS_LIMIT_EVM` on the reasoning that
14079
+ * the estimate covers it. For burn the buffer does cover a step (25% of
14080
+ * ~300_000 exceeds 51_500).
14081
+ */ const GAS_ESTIMATE_BUFFER_PERCENT = 125n;
14082
+ /**
14083
+ * Resolve the gas limit for an EVM request as `max(estimate * buffer, floor)`.
14084
+ *
14085
+ * Estimates first so chains whose real cost exceeds the floor are covered by
14086
+ * their own measurement, and falls back to the floor whenever estimation is
14087
+ * unavailable or under-reports. Estimation failure is never fatal here: before
14088
+ * floors existed these requests were submitted with a pinned limit and no
14089
+ * estimate at all, so degrading to the floor is never worse than the previous
14090
+ * behaviour.
14091
+ *
14092
+ * @param request - The prepared EVM request to size a gas limit for
14093
+ * @param gasFloor - The minimum gas limit to submit, in gas units
14094
+ * @returns The gas limit to submit, in gas units
14095
+ * @throws Never — estimation failures degrade to `gasFloor`
14096
+ *
14097
+ * @example
14098
+ * ```typescript
14099
+ * const gasLimit = await resolveGasLimit(request, 150_000)
14100
+ * ```
14101
+ */ const resolveGasLimit = async (request, gasFloor)=>{
14102
+ try {
14103
+ // Deliberately called without a `fallback`: both the viem and ethers
14104
+ // adapters *return* the supplied fallback object when estimation reverts
14105
+ // rather than throwing, which would set the estimate to the floor and then
14106
+ // multiply it by the buffer below. Omitting it routes reverts through the
14107
+ // catch, so a failed estimate degrades to exactly the floor.
14108
+ const estimate = await request.estimate();
14109
+ // The arithmetic stays inside the try on purpose. `EstimatedGas.gas` is
14110
+ // typed `bigint`, but adapters are a public extension point and may be
14111
+ // implemented in plain JS, so a non-bigint `gas` would throw here
14112
+ // ("Cannot mix BigInt and other types"). Guarding it keeps the documented
14113
+ // contract — estimation never aborts a step, it degrades to the floor.
14114
+ const buffered = estimate.gas * GAS_ESTIMATE_BUFFER_PERCENT / 100n;
14115
+ // Convert before comparing: Math.max throws on BigInt operands, and gas
14116
+ // units are far below Number.MAX_SAFE_INTEGER so the narrowing is lossless.
14117
+ return Math.max(Number(buffered), gasFloor);
14118
+ } catch {
14119
+ // Estimation is best-effort; the floor is the known-safe value.
14120
+ return gasFloor;
14121
+ }
14122
+ };
13892
14123
  /**
13893
14124
  * Executes a prepared chain request and returns the result as a bridge step.
13894
14125
  *
@@ -13902,8 +14133,8 @@ function hasPendingState(analysis, result) {
13902
14133
  * - `adapter`: The adapter that will execute the transaction
13903
14134
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
13904
14135
  * - `timeout`: The timeout for the request in milliseconds
13905
- * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
13906
- * bypassing `eth_estimateGas`; ignored for non-EVM requests
14136
+ * - `gasFloor`: Optional minimum gas limit (number); the request is submitted
14137
+ * with `max(estimate * 1.25, gasFloor)`. Ignored for non-EVM requests
13907
14138
  * @returns The bridge step with the transaction details and explorer URL
13908
14139
  * @throws If the transaction execution fails
13909
14140
  *
@@ -13918,7 +14149,7 @@ function hasPendingState(analysis, result) {
13918
14149
  * })
13919
14150
  * console.log('Transaction hash:', step.txHash)
13920
14151
  * ```
13921
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
14152
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasFloor }) {
13922
14153
  const step = {
13923
14154
  name,
13924
14155
  state: 'pending'
@@ -13931,8 +14162,8 @@ function hasPendingState(analysis, result) {
13931
14162
  step.state = 'noop';
13932
14163
  return step;
13933
14164
  }
13934
- const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
13935
- gasLimit
14165
+ const txHash = request.type === 'evm' && gasFloor !== undefined ? await request.execute({
14166
+ gasLimit: await resolveGasLimit(request, gasFloor)
13936
14167
  }) : await request.execute();
13937
14168
  step.txHash = txHash;
13938
14169
  const retryOptions = {
@@ -14006,7 +14237,7 @@ function hasPendingState(analysis, result) {
14006
14237
  adapter: params.source.adapter,
14007
14238
  chain: params.source.chain,
14008
14239
  request: await provider.approve(params.source, approvalAmount),
14009
- gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
14240
+ gasFloor: Number(APPROVE_GAS_LIMIT_EVM)
14010
14241
  });
14011
14242
  }
14012
14243
 
@@ -14034,7 +14265,7 @@ function hasPendingState(analysis, result) {
14034
14265
  adapter: params.source.adapter,
14035
14266
  chain: params.source.chain,
14036
14267
  request: await provider.burn(params),
14037
- gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
14268
+ gasFloor: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
14038
14269
  });
14039
14270
  }
14040
14271
 
@@ -14128,10 +14359,9 @@ function hasPendingState(analysis, result) {
14128
14359
  request: mintRequest,
14129
14360
  // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
14130
14361
  // eth_estimateGas does not account for, returning a below-floor value
14131
- // without reverting. Pinning to a value above the observed execution max
14132
- // (310_839) bypasses re-estimation and guarantees we clear both the floor
14133
- // and the actual execution cost.
14134
- gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
14362
+ // without reverting. The floor covers those; chains that cost more than the
14363
+ // floor are covered by their own estimate.
14364
+ gasFloor: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
14135
14365
  });
14136
14366
  // Add forwarded: false for non-relayer mints
14137
14367
  return {
@@ -14654,7 +14884,7 @@ const mockAttestationMessage = {
14654
14884
  return step;
14655
14885
  }
14656
14886
 
14657
- var version$2 = "1.10.0";
14887
+ var version$2 = "1.10.2";
14658
14888
  var pkg$2 = {
14659
14889
  version: version$2};
14660
14890
 
@@ -17252,7 +17482,9 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
17252
17482
  headers: context.headers
17253
17483
  }
17254
17484
  });
17255
- if (hasBoth) {
17485
+ if (context.customFeePolicy?.bridge != null) {
17486
+ kit.setCustomFeePolicy(context.customFeePolicy.bridge);
17487
+ } else if (hasBoth) {
17256
17488
  kit.setCustomFeePolicy({
17257
17489
  calculateFee: async (params)=>{
17258
17490
  const feeStr = await getFee('bridge', params);
@@ -17268,7 +17500,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
17268
17500
  };
17269
17501
 
17270
17502
  var name$1 = "@circle-fin/swap-kit";
17271
- var version$1 = "1.4.0";
17503
+ var version$1 = "1.5.1";
17272
17504
  var pkg$1 = {
17273
17505
  name: name$1,
17274
17506
  version: version$1};
@@ -18009,6 +18241,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
18009
18241
  required_error: 'estimatedAmount is required',
18010
18242
  invalid_type_error: 'estimatedAmount must be a string'
18011
18243
  }).min(1, 'estimatedAmount must be a non-empty string'),
18244
+ // Per-swap join key echoed back verbatim on success telemetry. Optional so a
18245
+ // not-yet-upgraded service (no field) still validates during rollout. A
18246
+ // malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
18247
+ // than throwing: this is a telemetry-only field (stripped from the developer
18248
+ // result, never used for control flow), so it must not be able to abort the
18249
+ // swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
18250
+ // never-throw contract of the rest of the telemetry stack. Implemented with
18251
+ // `preprocess` rather than Zod's `.catch()` because static analysis misreads
18252
+ // `.catch` on the schema chain as an unhandled Promise (S7785).
18253
+ correlationId: z.preprocess((value)=>z.string().uuid().safeParse(value).success ? value : undefined, z.string().optional()),
18012
18254
  config: createSwapRequestBaseSchema.shape.config.optional(),
18013
18255
  fees: createSwapFeesSchema.optional(),
18014
18256
  transaction: createSwapTransactionSchema
@@ -19201,7 +19443,7 @@ new Set(Object.values(Blockchain));
19201
19443
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
19202
19444
 
19203
19445
  var name = "@circle-fin/earn-kit";
19204
- var version = "1.3.0";
19446
+ var version = "1.5.0";
19205
19447
  var pkg = {
19206
19448
  name: name,
19207
19449
  version: version};
@@ -20212,11 +20454,16 @@ const sourceAdapterContextSchema = z.object({
20212
20454
  *
20213
20455
  * Validate the optional Kit Key field using the standard `apiKeySchema`
20214
20456
  * format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
20215
- * operates in permissionless mode.
20457
+ * operates in permissionless mode. `baseUrl` overrides the Earn Service
20458
+ * endpoint (e.g. staging); `batchTransactions: false` opts out of atomic
20459
+ * batched execution. Both are forwarded to the provider, so this `.strict()`
20460
+ * schema must accept them or a valid config object is rejected.
20216
20461
  *
20217
20462
  * @internal
20218
20463
  */ const earnConfigSchema = z.object({
20219
- kitKey: apiKeySchema.optional()
20464
+ kitKey: apiKeySchema.optional(),
20465
+ baseUrl: z.string().optional(),
20466
+ batchTransactions: z.boolean().optional()
20220
20467
  }).strict();
20221
20468
  /**
20222
20469
  * Canonical decimal form: a leading digit with no leading zeros (a single
@@ -20665,6 +20912,8 @@ function hasCrossChainDepositQuoteShape(params) {
20665
20912
  config: earnConfigSchema.optional()
20666
20913
  });
20667
20914
 
20915
+ /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg.name);
20916
+
20668
20917
  // Auto-register this kit for user agent tracking
20669
20918
  registerKit(`${pkg.name}/${pkg.version}`);
20670
20919