@hyperbridge/sdk 2.8.0 → 2.8.2

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.
@@ -2699,13 +2699,26 @@ var chainConfigs = {
2699
2699
  tokenDecimals: {
2700
2700
  USDC: 6,
2701
2701
  USDT: 6,
2702
- cNGN: 6
2702
+ cNGN: 6,
2703
+ ZARP: 18,
2704
+ EURC: 6,
2705
+ XSGD: 6,
2706
+ TRYB: 6,
2707
+ USDR: 6
2703
2708
  },
2704
2709
  tokenStorageSlots: {
2705
2710
  USDT: { balanceSlot: 2, allowanceSlot: 5 },
2706
2711
  USDC: { balanceSlot: 9, allowanceSlot: 10 },
2707
2712
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2708
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2713
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2714
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 },
2715
+ // custom upgradeable layout
2716
+ ZARP: { balanceSlot: 51, allowanceSlot: 52 },
2717
+ EURC: { balanceSlot: 9, allowanceSlot: 10 },
2718
+ // Circle FiatToken layout
2719
+ XSGD: { balanceSlot: 7, allowanceSlot: 8 },
2720
+ TRYB: { balanceSlot: 9, allowanceSlot: 10 },
2721
+ USDR: { balanceSlot: 51, allowanceSlot: 52 }
2709
2722
  },
2710
2723
  addresses: {
2711
2724
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -2885,13 +2898,22 @@ var chainConfigs = {
2885
2898
  USDC: 6,
2886
2899
  USDT: 6,
2887
2900
  cNGN: 6,
2888
- EXT: 18
2901
+ EXT: 18,
2902
+ ZARP: 18,
2903
+ EURC: 6,
2904
+ USDR: 6
2889
2905
  },
2890
2906
  tokenStorageSlots: {
2891
2907
  USDT: { balanceSlot: 0, allowanceSlot: 1 },
2892
2908
  USDC: { balanceSlot: 9, allowanceSlot: 10 },
2893
2909
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2894
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2910
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2911
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 },
2912
+ // custom upgradeable layout
2913
+ ZARP: { balanceSlot: 51, allowanceSlot: 52 },
2914
+ EURC: { balanceSlot: 9, allowanceSlot: 10 },
2915
+ // Circle FiatToken layout
2916
+ USDR: { balanceSlot: 51, allowanceSlot: 52 }
2895
2917
  },
2896
2918
  addresses: {
2897
2919
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -2952,13 +2974,21 @@ var chainConfigs = {
2952
2974
  USDC: 6,
2953
2975
  USDT: 6,
2954
2976
  EXT: 18,
2955
- cNGN: 6
2977
+ cNGN: 6,
2978
+ ZARP: 18,
2979
+ XSGD: 6,
2980
+ USDR: 6
2956
2981
  },
2957
2982
  tokenStorageSlots: {
2958
2983
  USDT: { balanceSlot: 0, allowanceSlot: 1 },
2959
2984
  USDC: { balanceSlot: 9, allowanceSlot: 10 },
2960
2985
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2961
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2986
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2987
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 },
2988
+ // custom upgradeable layout
2989
+ ZARP: { balanceSlot: 51, allowanceSlot: 52 },
2990
+ XSGD: { balanceSlot: 7, allowanceSlot: 8 },
2991
+ USDR: { balanceSlot: 51, allowanceSlot: 52 }
2962
2992
  },
2963
2993
  addresses: {
2964
2994
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -3259,8 +3289,9 @@ var chainConfigs = {
3259
3289
  assets: {
3260
3290
  WETH: "0x0000000000000000000000000000000000000000",
3261
3291
  DAI: "0x0000000000000000000000000000000000000000",
3292
+ // Asset Hub assets at their asset-id precompile addresses: 0x539 = 1337, 0x7c0 = 1984.
3262
3293
  USDC: "0x0000053900000000000000000000000001200000",
3263
- USDT: "0x0000000000000000000000000000000000000000"
3294
+ USDT: "0x000007c000000000000000000000000001200000"
3264
3295
  },
3265
3296
  tokenDecimals: {
3266
3297
  USDC: 6,
@@ -7917,15 +7948,25 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7917
7948
  /**
7918
7949
  * Signs and sends an extrinsic. Submissions are serialised through {@link submissionQueue} so
7919
7950
  * concurrent calls never collide on the substrate account nonce — each extrinsic reaches a block
7920
- * before the next is signed.
7951
+ * (or is confirmed still pooled and returned as `pending`) before the next is signed; auto-nonce
7952
+ * via `system.accountNextIndex` counts pooled extrinsics, so a pending one is never re-used.
7921
7953
  */
7922
7954
  async signAndSendExtrinsic(extrinsic, maxRetries = 3, timeoutMs = 3e4) {
7923
- const result = await this.submissionQueue.add(() => this.sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs));
7955
+ const result = await this.submissionQueue.add(
7956
+ () => this.sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs)
7957
+ );
7924
7958
  return result ?? { success: false, error: "Submission queue returned no result" };
7925
7959
  }
7926
7960
  /**
7927
7961
  * Signs and sends an extrinsic, handling status updates and errors.
7928
7962
  * Implements retry logic with progressive tip increases for stuck transactions.
7963
+ *
7964
+ * A retry only happens when the previous attempt verifiably went nowhere. Once an attempt's
7965
+ * extrinsic is known to be pooled (`pending`), re-signing the same call would race our own
7966
+ * submission: the copy either bounces off the pool (1014, same nonce below the replacement
7967
+ * priority bump) or — if the original lands first, freeing the nonce — executes as a duplicate
7968
+ * and fails on-chain (e.g. `BidNotFound` for a retraction). Neither can succeed, so the pending
7969
+ * result is returned for the caller to confirm later.
7929
7970
  */
7930
7971
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
7931
7972
  const keyPair = this.getKeyPair();
@@ -7936,7 +7977,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7936
7977
  attempt++;
7937
7978
  try {
7938
7979
  const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs);
7939
- if (result.success || result.error?.includes("Dispatch error")) {
7980
+ if (result.success || result.pending || result.error?.includes("Dispatch error")) {
7940
7981
  return result;
7941
7982
  }
7942
7983
  } catch (err) {
@@ -7952,12 +7993,30 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7952
7993
  };
7953
7994
  }
7954
7995
  /**
7955
- * Sends an extrinsic with a timeout
7996
+ * Classifies a submission rejection. Codes 1013 ("already imported") and 1014 ("priority is
7997
+ * too low") both mean a copy of this account+nonce is already in the pool — almost always our
7998
+ * own earlier attempt whose watch handle didn't confirm cleanly. That extrinsic is in flight;
7999
+ * resubmitting can only bounce again or land a duplicate, so these are surfaced as `pending`
8000
+ * rather than failure.
8001
+ */
8002
+ classifySubmissionError(err) {
8003
+ const code = err.code;
8004
+ const inFlight = code === 1013 || code === 1014 || /already imported|already in the pool|priority is too low/i.test(err.message);
8005
+ return { success: false, pending: inFlight || void 0, error: err.message };
8006
+ }
8007
+ /**
8008
+ * Sends an extrinsic with a timeout.
8009
+ *
8010
+ * A timeout is only a failure when the extrinsic never made it into the transaction pool.
8011
+ * Once a pool-entry status (Future/Ready/Broadcast/Retracted) has been seen, the extrinsic is
8012
+ * in flight and may well execute after the watch is abandoned — the result is then `pending`,
8013
+ * telling the caller to confirm the outcome later instead of re-signing the same call.
7956
8014
  */
7957
8015
  async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs) {
7958
8016
  return new Promise((resolve) => {
7959
8017
  let resolved = false;
7960
8018
  let unsubscribe = null;
8019
+ let enteredPool = false;
7961
8020
  const timeoutId = setTimeout(() => {
7962
8021
  if (!resolved) {
7963
8022
  resolved = true;
@@ -7966,12 +8025,17 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7966
8025
  }
7967
8026
  resolve({
7968
8027
  success: false,
7969
- error: `Transaction timed out after ${timeoutMs}ms`
8028
+ pending: enteredPool || void 0,
8029
+ extrinsicHash: enteredPool ? extrinsic.hash.toHex() : void 0,
8030
+ error: `Transaction timed out after ${timeoutMs}ms${enteredPool ? " while in the transaction pool" : ""}`
7970
8031
  });
7971
8032
  }
7972
8033
  }, timeoutMs);
7973
8034
  extrinsic.signAndSend(keyPair, { tip }, (result) => {
7974
8035
  if (resolved) return;
8036
+ if (result.status.isFuture || result.status.isReady || result.status.isBroadcast || result.status.isRetracted) {
8037
+ enteredPool = true;
8038
+ }
7975
8039
  if (result.dispatchError && (result.status.isInBlock || result.status.isFinalized)) {
7976
8040
  resolved = true;
7977
8041
  clearTimeout(timeoutId);
@@ -8029,7 +8093,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8029
8093
  if (!resolved) {
8030
8094
  resolved = true;
8031
8095
  clearTimeout(timeoutId);
8032
- resolve({ success: false, error: err.message });
8096
+ resolve(this.classifySubmissionError(err));
8033
8097
  }
8034
8098
  });
8035
8099
  });
@@ -8203,9 +8267,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8203
8267
  if (!result || result.isNone) return null;
8204
8268
  const rawHex = result.unwrap().toHex();
8205
8269
  if (rawHex === "0x" || rawHex === "0x00") return null;
8206
- const placeOrderAbi = IntentGatewayV2_default.ABI.find(
8207
- (item) => item.type === "function" && item.name === "placeOrder"
8208
- );
8270
+ const placeOrderAbi = IntentGatewayV2_default.ABI.find((item) => item.type === "function" && item.name === "placeOrder");
8209
8271
  const orderType = placeOrderAbi?.inputs?.[0];
8210
8272
  if (!orderType) return null;
8211
8273
  const [decoded] = viem.decodeAbiParameters([orderType], rawHex);
@@ -16994,14 +17056,14 @@ var BidManager = class {
16994
17056
  }
16995
17057
  /**
16996
17058
  * Autopilot bid selection: sorts the given bids by output value, simulates
16997
- * each in order until one passes, then executes that bid. For consumers that
16998
- * do not need custom selection logic.
17059
+ * each in order and attempts execution. Any bid that fails simulation or
17060
+ * execution is skipped once so the next ranked bid can be attempted in the
17061
+ * same round. For consumers that do not need custom selection logic.
16999
17062
  *
17000
17063
  * @param order - The placed order to fill.
17001
17064
  * @param bids - Candidate bids (from {@link buildBids}).
17002
17065
  * @returns A {@link SelectBidResult} for the executed bid.
17003
- * @throws If no valid bids exist, all simulations fail, or the bundler rejects
17004
- * the UserOperation.
17066
+ * @throws If no valid bids exist or every bid fails simulation/execution.
17005
17067
  */
17006
17068
  async selectAndExecuteBest(order, bids) {
17007
17069
  const commitment = order.id;
@@ -17018,22 +17080,34 @@ var BidManager = class {
17018
17080
  throw new Error("No valid bids found");
17019
17081
  }
17020
17082
  console.log(`[BidManager] Simulating ${sortedBids.length} sorted bid(s) to find a valid one`);
17083
+ let simulationFailures = 0;
17084
+ let executionFailures = 0;
17021
17085
  for (let idx = 0; idx < sortedBids.length; idx++) {
17022
17086
  const bid = sortedBids[idx];
17023
17087
  console.log(`[BidManager] Simulating bid ${idx + 1}/${sortedBids.length} from solver=${bid.solverAddress}`);
17024
17088
  try {
17025
17089
  await bid.simulate();
17026
17090
  } catch (err) {
17091
+ simulationFailures += 1;
17027
17092
  console.warn(
17028
17093
  `[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation FAILED: ${err instanceof Error ? err.message : String(err)}`
17029
17094
  );
17030
17095
  continue;
17031
17096
  }
17032
17097
  console.log(`[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation PASSED`);
17033
- return bid.execute();
17098
+ try {
17099
+ return await bid.execute();
17100
+ } catch (err) {
17101
+ executionFailures += 1;
17102
+ console.warn(
17103
+ `[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: execution FAILED: ${err instanceof Error ? err.message : String(err)}; trying next bid`
17104
+ );
17105
+ }
17034
17106
  }
17035
- console.error(`[BidManager] All ${sortedBids.length} bid(s) failed simulation for commitment=${commitment}`);
17036
- throw new Error("No bids passed simulation");
17107
+ console.error(
17108
+ `[BidManager] No executable bids for commitment=${commitment}: ${simulationFailures} simulation failure(s), ${executionFailures} execution failure(s)`
17109
+ );
17110
+ throw new Error("No bids passed simulation and execution");
17037
17111
  }
17038
17112
  /**
17039
17113
  * Sorts a list of bids for the given order by output value.
@@ -17373,6 +17447,10 @@ var BidManager = class {
17373
17447
  }
17374
17448
  };
17375
17449
  var RELAYER_MESSAGE_GAS = 1000000n;
17450
+ var NO_BUNDLER_FILL_GAS_BASE = 700000n;
17451
+ var NO_BUNDLER_FILL_GAS_PER_OUTPUT = 150000n;
17452
+ var NO_BUNDLER_PAYMASTER_VERIFICATION_GAS = 250000n;
17453
+ var NO_BUNDLER_PAYMASTER_POST_OP_GAS = 100000n;
17376
17454
  var GasEstimator = class {
17377
17455
  /**
17378
17456
  * @param ctx - Shared IntentsV2 context providing the source and destination
@@ -17406,8 +17484,10 @@ var GasEstimator = class {
17406
17484
  * headroom. If the bundler is Pimlico, gas prices are refined with
17407
17485
  * `pimlico_getUserOperationGasPrice`.
17408
17486
  *
17409
- * **Fallback path (no bundler):** calls `estimateContractGas` directly on
17410
- * `fillOrder` with state overrides.
17487
+ * **Fallback path (no bundler):** uses a fixed budget
17488
+ * ({@link NO_BUNDLER_FILL_GAS_BASE} plus {@link NO_BUNDLER_FILL_GAS_PER_OUTPUT}
17489
+ * per output leg) — public RPCs don't reliably support estimation with
17490
+ * state overrides.
17411
17491
  *
17412
17492
  * @param params - Parameters including the order to estimate and optional
17413
17493
  * percentage bumps for `maxPriorityFeePerGas` and `maxFeePerGas`.
@@ -17442,14 +17522,14 @@ var GasEstimator = class {
17442
17522
  }
17443
17523
  const isSameChain = souceStateMachineId === destStateMachineId;
17444
17524
  const [stateOverridesResult, crossChainFees] = await Promise.all([
17445
- this.buildStateOverride({
17525
+ this.ctx.bundlerUrl ? this.buildStateOverride({
17446
17526
  accountAddress: solverAccountAddress,
17447
17527
  chain: destStateMachineId,
17448
17528
  outputAssets: assetsForOverrides,
17449
17529
  spenderAddress: intentGatewayV2Address,
17450
17530
  intentGatewayV2Address,
17451
17531
  entryPointAddress
17452
- }),
17532
+ }) : Promise.resolve({ viem: [], bundler: {} }),
17453
17533
  isSameChain ? Promise.resolve({ postRequestFee: 0n, relayerFeeInSourceFeeToken: 0n }) : this.estimateCrossChainFees(
17454
17534
  sourceFeeToken,
17455
17535
  destFeeToken,
@@ -17457,7 +17537,7 @@ var GasEstimator = class {
17457
17537
  orderFeeGasPriceBumpPercent
17458
17538
  )
17459
17539
  ]);
17460
- const { viem: stateOverrides, bundler: bundlerStateOverrides } = stateOverridesResult;
17540
+ const { bundler: bundlerStateOverrides } = stateOverridesResult;
17461
17541
  const fillOptions = {
17462
17542
  relayerFee: crossChainFees.postRequestFee,
17463
17543
  // Always dispatch with the fee token (see the method docs).
@@ -17598,20 +17678,9 @@ var GasEstimator = class {
17598
17678
  console.warn("Bundler gas estimation failed, using fallback values:", e);
17599
17679
  }
17600
17680
  } else {
17601
- try {
17602
- const estimatedGas = await this.ctx.dest.client.estimateContractGas({
17603
- abi: ABI3,
17604
- address: intentGatewayV2Address,
17605
- functionName: "fillOrder",
17606
- args: [transformOrderForContract(order), fillOptions],
17607
- account: solverAccountAddress,
17608
- value: totalNativeValue,
17609
- stateOverride: stateOverrides
17610
- });
17611
- callGasLimit = estimatedGas * 105n / 100n;
17612
- } catch (e) {
17613
- console.warn("fillOrder gas estimation failed, using fallback:", e);
17614
- }
17681
+ callGasLimit = NO_BUNDLER_FILL_GAS_BASE + NO_BUNDLER_FILL_GAS_PER_OUTPUT * BigInt(order.output.assets.length);
17682
+ paymasterVerificationGasLimit = NO_BUNDLER_PAYMASTER_VERIFICATION_GAS;
17683
+ paymasterPostOpGasLimit = NO_BUNDLER_PAYMASTER_POST_OP_GAS;
17615
17684
  }
17616
17685
  const totalGas = callGasLimit + verificationGasLimit + preVerificationGas + paymasterVerificationGasLimit + paymasterPostOpGasLimit;
17617
17686
  const rawTotalGasCostWei = totalGas * maxFeePerGas;