@hyperbridge/sdk 2.8.0 → 2.8.3

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",
@@ -2754,18 +2767,25 @@ var chainConfigs = {
2754
2767
  DAI: "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3",
2755
2768
  USDC: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
2756
2769
  USDT: "0x55d398326f99059ff775485246999027b3197955",
2757
- EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209"
2770
+ EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209",
2771
+ cNGN: "0xa8AEA66B361a8d53e8865c62D142167Af28Af058"
2758
2772
  },
2759
2773
  tokenDecimals: {
2760
2774
  USDC: 18,
2761
2775
  USDT: 18,
2776
+ // 6, not 18 — cNGN keeps the same decimals it has on every other chain, unlike the
2777
+ // Binance-pegged stables above. Every phantom standard_amount and pool rate divides
2778
+ // by this, so the divergence from its neighbours here is load-bearing, not a typo.
2779
+ cNGN: 6,
2762
2780
  EXT: 18
2763
2781
  },
2764
2782
  tokenStorageSlots: {
2765
2783
  USDT: { balanceSlot: 1, allowanceSlot: 2 },
2766
2784
  USDC: { balanceSlot: 1, allowanceSlot: 2 },
2767
2785
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2768
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2786
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2787
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 }
2788
+ // custom upgradeable layout, as on Base
2769
2789
  },
2770
2790
  addresses: {
2771
2791
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -2885,13 +2905,22 @@ var chainConfigs = {
2885
2905
  USDC: 6,
2886
2906
  USDT: 6,
2887
2907
  cNGN: 6,
2888
- EXT: 18
2908
+ EXT: 18,
2909
+ ZARP: 18,
2910
+ EURC: 6,
2911
+ USDR: 6
2889
2912
  },
2890
2913
  tokenStorageSlots: {
2891
2914
  USDT: { balanceSlot: 0, allowanceSlot: 1 },
2892
2915
  USDC: { balanceSlot: 9, allowanceSlot: 10 },
2893
2916
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2894
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2917
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2918
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 },
2919
+ // custom upgradeable layout
2920
+ ZARP: { balanceSlot: 51, allowanceSlot: 52 },
2921
+ EURC: { balanceSlot: 9, allowanceSlot: 10 },
2922
+ // Circle FiatToken layout
2923
+ USDR: { balanceSlot: 51, allowanceSlot: 52 }
2895
2924
  },
2896
2925
  addresses: {
2897
2926
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -2952,13 +2981,21 @@ var chainConfigs = {
2952
2981
  USDC: 6,
2953
2982
  USDT: 6,
2954
2983
  EXT: 18,
2955
- cNGN: 6
2984
+ cNGN: 6,
2985
+ ZARP: 18,
2986
+ XSGD: 6,
2987
+ USDR: 6
2956
2988
  },
2957
2989
  tokenStorageSlots: {
2958
2990
  USDT: { balanceSlot: 0, allowanceSlot: 1 },
2959
2991
  USDC: { balanceSlot: 9, allowanceSlot: 10 },
2960
2992
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2961
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2993
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2994
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 },
2995
+ // custom upgradeable layout
2996
+ ZARP: { balanceSlot: 51, allowanceSlot: 52 },
2997
+ XSGD: { balanceSlot: 7, allowanceSlot: 8 },
2998
+ USDR: { balanceSlot: 51, allowanceSlot: 52 }
2962
2999
  },
2963
3000
  addresses: {
2964
3001
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -3259,8 +3296,9 @@ var chainConfigs = {
3259
3296
  assets: {
3260
3297
  WETH: "0x0000000000000000000000000000000000000000",
3261
3298
  DAI: "0x0000000000000000000000000000000000000000",
3299
+ // Asset Hub assets at their asset-id precompile addresses: 0x539 = 1337, 0x7c0 = 1984.
3262
3300
  USDC: "0x0000053900000000000000000000000001200000",
3263
- USDT: "0x0000000000000000000000000000000000000000"
3301
+ USDT: "0x000007c000000000000000000000000001200000"
3264
3302
  },
3265
3303
  tokenDecimals: {
3266
3304
  USDC: 6,
@@ -7779,6 +7817,27 @@ function encodeISMPMessage(message) {
7779
7817
  }
7780
7818
  var OFFCHAIN_BID_PREFIX = new TextEncoder().encode("intents::bid::");
7781
7819
  var OFFCHAIN_PHANTOM_PREFIX = new TextEncoder().encode("intents::phantom::order::");
7820
+ var HYPERBRIDGE_TYPES_BUNDLE = {
7821
+ spec: {
7822
+ nexus: { hasher: utilCrypto.keccakAsU8a },
7823
+ gargantua: { hasher: utilCrypto.keccakAsU8a }
7824
+ }
7825
+ };
7826
+ var BASE_TIP = 1000000000n;
7827
+ var HTTP_CONNECT_TIMEOUT_MS = 2e4;
7828
+ var PHANTOM_POLL_INTERVAL_MS = 15e3;
7829
+ var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
7830
+ function rejectAfter(ms, message) {
7831
+ return new Promise((_resolve, reject) => {
7832
+ const timer = setTimeout(() => reject(new Error(message)), ms);
7833
+ timer.unref?.();
7834
+ });
7835
+ }
7836
+ function deriveHttpUrl(wsUrl) {
7837
+ if (wsUrl.startsWith("wss://")) return `https://${wsUrl.slice("wss://".length)}`;
7838
+ if (wsUrl.startsWith("ws://")) return `http://${wsUrl.slice("ws://".length)}`;
7839
+ throw new Error(`Cannot derive an HTTP endpoint from a non-websocket url: ${wsUrl}`);
7840
+ }
7782
7841
  var BidCodec = scaleTs.Struct({ filler: scaleTs.Bytes(32), user_op: scaleTs.Vector(scaleTs.u8) });
7783
7842
  var PackedUserOperationCodec = scaleTs.Struct({
7784
7843
  sender: scaleTs.Bytes(20),
@@ -7839,6 +7898,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7839
7898
  ownsConnection;
7840
7899
  /** Cached result of whether the node exposes intents_* RPC methods */
7841
7900
  hasIntentsRpc = null;
7901
+ /** The HTTP-backed api, connected on first use. Cleared after a failed attempt so it retries. */
7902
+ httpApi = null;
7842
7903
  // Serialises every extrinsic submission on this instance's substrate account. All submit/retract
7843
7904
  // methods funnel through signAndSendExtrinsic, each using the API's auto-nonce; fired in parallel
7844
7905
  // (bids for orders on different chains, or several phantom orders in one interval) they would grab
@@ -7855,12 +7916,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7855
7916
  static async connect(wsUrl, substratePrivateKey) {
7856
7917
  const api$1 = await api.ApiPromise.create({
7857
7918
  provider: new api.WsProvider(wsUrl),
7858
- typesBundle: {
7859
- spec: {
7860
- nexus: { hasher: utilCrypto.keccakAsU8a },
7861
- gargantua: { hasher: utilCrypto.keccakAsU8a }
7862
- }
7863
- }
7919
+ typesBundle: HYPERBRIDGE_TYPES_BUNDLE
7864
7920
  });
7865
7921
  return new _IntentsCoprocessor(api$1, substratePrivateKey, true);
7866
7922
  }
@@ -7886,15 +7942,85 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7886
7942
  static fromApi(api, substratePrivateKey) {
7887
7943
  return new _IntentsCoprocessor(api, substratePrivateKey, false);
7888
7944
  }
7945
+ /**
7946
+ * The API every RPC query runs on: HTTP, connected to the same node as the websocket. Exposed so
7947
+ * callers query through this connection rather than opening one of their own.
7948
+ *
7949
+ * The split is by what each transport is for. Queries are one-shot request/response, which HTTP
7950
+ * serves without holding any state that can silently rot between calls. The websocket earns its
7951
+ * keep only where subscriptions do — watching a submitted extrinsic to inclusion.
7952
+ */
7953
+ async queryApi() {
7954
+ return await this.http();
7955
+ }
7956
+ /**
7957
+ * The websocket API, exposed so callers share this one connection instead of opening a second
7958
+ * socket to the same node. Only needed for subscriptions; use {@link queryApi} to read.
7959
+ */
7960
+ get apiConnection() {
7961
+ return this.api;
7962
+ }
7889
7963
  /**
7890
7964
  * Disconnects the underlying API connection if this instance owns it.
7891
- * Only disconnects if created via `connect()`, not when using shared connections.
7965
+ * Only disconnects the websocket if created via `connect()`, not when using shared connections.
7966
+ * The HTTP api is always created here, so it is always ours to close.
7892
7967
  */
7893
7968
  async disconnect() {
7969
+ const http4 = this.httpApi;
7970
+ this.httpApi = null;
7971
+ if (http4) {
7972
+ await http4.then((api) => api.disconnect()).catch(() => {
7973
+ });
7974
+ }
7894
7975
  if (this.ownsConnection) {
7895
7976
  await this.api.disconnect();
7896
7977
  }
7897
7978
  }
7979
+ /**
7980
+ * The HTTP api for this node, connected on first use. Every coprocessor has one — the endpoint
7981
+ * is derived from the websocket's own endpoint, so there is nothing to configure and nothing to
7982
+ * be absent.
7983
+ *
7984
+ * The connection attempt is bounded on both sides. `isReadyOrError` rejects on a failed
7985
+ * handshake, where plain `isReady` would simply never resolve, and the timeout covers an
7986
+ * endpoint that accepts the request and then goes quiet. An unbounded wait here would hang the
7987
+ * poll tick awaiting it, which is precisely the silent stall that polling exists to avoid. A
7988
+ * failed attempt is not cached, so the next call tries again.
7989
+ */
7990
+ async http() {
7991
+ if (!this.httpApi) {
7992
+ const httpUrl = deriveHttpUrl(this.wsEndpoint());
7993
+ const api$1 = new api.ApiPromise({
7994
+ provider: new api.HttpProvider(httpUrl),
7995
+ typesBundle: HYPERBRIDGE_TYPES_BUNDLE,
7996
+ // A second connection to the node the ws api already reported on; its init warnings
7997
+ // would just be duplicates.
7998
+ noInitWarn: true
7999
+ });
8000
+ this.httpApi = Promise.race([
8001
+ api$1.isReadyOrError,
8002
+ rejectAfter(HTTP_CONNECT_TIMEOUT_MS, `HTTP RPC ${httpUrl} did not become ready`)
8003
+ ]).catch(async (err) => {
8004
+ await api$1.disconnect().catch(() => {
8005
+ });
8006
+ this.httpApi = null;
8007
+ throw new Error(`HTTP RPC ${httpUrl} is unavailable: ${err instanceof Error ? err.message : err}`);
8008
+ });
8009
+ }
8010
+ return await this.httpApi;
8011
+ }
8012
+ /**
8013
+ * The endpoint the websocket provider is connected to. Read from the provider rather than
8014
+ * remembered from a constructor argument, so it is the one endpoint in use no matter which
8015
+ * factory built this instance.
8016
+ */
8017
+ wsEndpoint() {
8018
+ const endpoint = this.api._rpcCore?.provider?.endpoint;
8019
+ if (!endpoint) {
8020
+ throw new Error("Cannot determine the Hyperbridge websocket endpoint to derive an HTTP endpoint from");
8021
+ }
8022
+ return endpoint;
8023
+ }
7898
8024
  /**
7899
8025
  * Creates a Substrate keypair from the configured private key.
7900
8026
  * Supports hex seed (with or without 0x), mnemonic phrases, and URI derivation paths (//Alice).
@@ -7917,26 +8043,67 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7917
8043
  /**
7918
8044
  * Signs and sends an extrinsic. Submissions are serialised through {@link submissionQueue} so
7919
8045
  * concurrent calls never collide on the substrate account nonce — each extrinsic reaches a block
7920
- * before the next is signed.
8046
+ * (or is confirmed still pooled and returned as `pending`) before the next is signed; auto-nonce
8047
+ * via `system.accountNextIndex` counts pooled extrinsics, so a pending one is never re-used.
8048
+ *
8049
+ * The extrinsic is built rather than passed in because the api it is built on decides where it
8050
+ * is signed and sent: a websocket that is down when the queue reaches this submission diverts it
8051
+ * to {@link sendViaHttp}, which needs the call bound to the HTTP api instead.
7921
8052
  */
7922
- async signAndSendExtrinsic(extrinsic, maxRetries = 3, timeoutMs = 3e4) {
7923
- const result = await this.submissionQueue.add(() => this.sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs));
8053
+ async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = 3e4) {
8054
+ const result = await this.submissionQueue.add(async () => {
8055
+ if (!this.api.isConnected) {
8056
+ try {
8057
+ return await this.sendViaHttp(await this.http(), build);
8058
+ } catch (err) {
8059
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
8060
+ }
8061
+ }
8062
+ return await this.sendExtrinsicWithRetries(build(this.api), maxRetries, timeoutMs);
8063
+ });
7924
8064
  return result ?? { success: false, error: "Submission queue returned no result" };
7925
8065
  }
8066
+ /**
8067
+ * Last-resort submission for when the websocket is down at signing time. A bid is only worth
8068
+ * anything inside its window, so waiting for a reconnect usually means not bidding at all.
8069
+ *
8070
+ * HTTP has no subscriptions, so this is `author_submitExtrinsic`: the node accepts the extrinsic
8071
+ * into its pool and returns its hash, and nothing further is observable from here. That is
8072
+ * exactly the `pending` contract — in flight, outcome unknown, do not re-sign — so the result
8073
+ * says so rather than claiming a success it cannot see.
8074
+ *
8075
+ * Only reached when the socket was already down before signing. A submission that got as far as
8076
+ * the pool over the websocket is never retried here: that is the duplicate-nonce race the
8077
+ * `pending` result exists to prevent.
8078
+ */
8079
+ async sendViaHttp(api, build) {
8080
+ try {
8081
+ const hash = await build(api).signAndSend(this.getKeyPair(), { tip: BASE_TIP });
8082
+ return { success: false, pending: true, extrinsicHash: hash.toHex() };
8083
+ } catch (err) {
8084
+ return this.classifySubmissionError(err instanceof Error ? err : new Error(String(err)));
8085
+ }
8086
+ }
7926
8087
  /**
7927
8088
  * Signs and sends an extrinsic, handling status updates and errors.
7928
8089
  * Implements retry logic with progressive tip increases for stuck transactions.
8090
+ *
8091
+ * A retry only happens when the previous attempt verifiably went nowhere. Once an attempt's
8092
+ * extrinsic is known to be pooled (`pending`), re-signing the same call would race our own
8093
+ * submission: the copy either bounces off the pool (1014, same nonce below the replacement
8094
+ * priority bump) or — if the original lands first, freeing the nonce — executes as a duplicate
8095
+ * and fails on-chain (e.g. `BidNotFound` for a retraction). Neither can succeed, so the pending
8096
+ * result is returned for the caller to confirm later.
7929
8097
  */
7930
8098
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
7931
8099
  const keyPair = this.getKeyPair();
7932
- const baseTip = 1000000000n;
7933
8100
  let attempt = 0;
7934
8101
  while (attempt < maxRetries) {
7935
- const currentTip = baseTip * BigInt(2 ** attempt);
8102
+ const currentTip = BASE_TIP * BigInt(2 ** attempt);
7936
8103
  attempt++;
7937
8104
  try {
7938
8105
  const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs);
7939
- if (result.success || result.error?.includes("Dispatch error")) {
8106
+ if (result.success || result.pending || result.error?.includes("Dispatch error")) {
7940
8107
  return result;
7941
8108
  }
7942
8109
  } catch (err) {
@@ -7952,12 +8119,30 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7952
8119
  };
7953
8120
  }
7954
8121
  /**
7955
- * Sends an extrinsic with a timeout
8122
+ * Classifies a submission rejection. Codes 1013 ("already imported") and 1014 ("priority is
8123
+ * too low") both mean a copy of this account+nonce is already in the pool — almost always our
8124
+ * own earlier attempt whose watch handle didn't confirm cleanly. That extrinsic is in flight;
8125
+ * resubmitting can only bounce again or land a duplicate, so these are surfaced as `pending`
8126
+ * rather than failure.
8127
+ */
8128
+ classifySubmissionError(err) {
8129
+ const code = err.code;
8130
+ const inFlight = code === 1013 || code === 1014 || /already imported|already in the pool|priority is too low/i.test(err.message);
8131
+ return { success: false, pending: inFlight || void 0, error: err.message };
8132
+ }
8133
+ /**
8134
+ * Sends an extrinsic with a timeout.
8135
+ *
8136
+ * A timeout is only a failure when the extrinsic never made it into the transaction pool.
8137
+ * Once a pool-entry status (Future/Ready/Broadcast/Retracted) has been seen, the extrinsic is
8138
+ * in flight and may well execute after the watch is abandoned — the result is then `pending`,
8139
+ * telling the caller to confirm the outcome later instead of re-signing the same call.
7956
8140
  */
7957
8141
  async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs) {
7958
8142
  return new Promise((resolve) => {
7959
8143
  let resolved = false;
7960
8144
  let unsubscribe = null;
8145
+ let enteredPool = false;
7961
8146
  const timeoutId = setTimeout(() => {
7962
8147
  if (!resolved) {
7963
8148
  resolved = true;
@@ -7966,25 +8151,23 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7966
8151
  }
7967
8152
  resolve({
7968
8153
  success: false,
7969
- error: `Transaction timed out after ${timeoutMs}ms`
8154
+ pending: enteredPool || void 0,
8155
+ extrinsicHash: enteredPool ? extrinsic.hash.toHex() : void 0,
8156
+ error: `Transaction timed out after ${timeoutMs}ms${enteredPool ? " while in the transaction pool" : ""}`
7970
8157
  });
7971
8158
  }
7972
8159
  }, timeoutMs);
7973
8160
  extrinsic.signAndSend(keyPair, { tip }, (result) => {
7974
8161
  if (resolved) return;
8162
+ if (result.status.isFuture || result.status.isReady || result.status.isBroadcast || result.status.isRetracted) {
8163
+ enteredPool = true;
8164
+ }
7975
8165
  if (result.dispatchError && (result.status.isInBlock || result.status.isFinalized)) {
7976
8166
  resolved = true;
7977
8167
  clearTimeout(timeoutId);
7978
- let errorMsg;
7979
- if (result.dispatchError.isModule) {
7980
- const decoded = this.api.registry.findMetaError(result.dispatchError.asModule);
7981
- errorMsg = `Dispatch error: ${decoded.section}::${decoded.name}`;
7982
- } else {
7983
- errorMsg = `Dispatch error: ${result.dispatchError.toString()}`;
7984
- }
7985
8168
  resolve({
7986
8169
  success: false,
7987
- error: errorMsg
8170
+ error: `Dispatch error: ${this.describeDispatchError(result.dispatchError)}`
7988
8171
  });
7989
8172
  } else if (result.status.isDropped || result.status.isInvalid || result.status.isUsurped || result.status.isFinalityTimeout) {
7990
8173
  resolved = true;
@@ -8002,21 +8185,19 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8002
8185
  if (interrupted) {
8003
8186
  const [indexCodec, dispatchError] = interrupted.event.data;
8004
8187
  if (Number(indexCodec.toString()) === 0) {
8005
- let errorMsg;
8006
- if (dispatchError?.isModule) {
8007
- const decoded = this.api.registry.findMetaError(dispatchError.asModule);
8008
- errorMsg = `Dispatch error: ${decoded.section}::${decoded.name}`;
8009
- } else {
8010
- errorMsg = `Dispatch error: batch interrupted (${dispatchError?.toString()})`;
8011
- }
8012
- resolve({ success: false, error: errorMsg });
8188
+ resolve({
8189
+ success: false,
8190
+ error: `Dispatch error: ${this.describeDispatchError(dispatchError)}`
8191
+ });
8013
8192
  return;
8014
8193
  }
8015
8194
  }
8016
8195
  resolve({
8017
8196
  success: true,
8018
8197
  blockHash: (result.status.isInBlock ? result.status.asInBlock : result.status.asFinalized).toHex(),
8019
- extrinsicHash: extrinsic.hash.toHex()
8198
+ extrinsicHash: extrinsic.hash.toHex(),
8199
+ // Carried so a batch caller can attribute each item's outcome.
8200
+ events: result.events
8020
8201
  });
8021
8202
  }
8022
8203
  }).then((unsub) => {
@@ -8029,7 +8210,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8029
8210
  if (!resolved) {
8030
8211
  resolved = true;
8031
8212
  clearTimeout(timeoutId);
8032
- resolve({ success: false, error: err.message });
8213
+ resolve(this.classifySubmissionError(err));
8033
8214
  }
8034
8215
  });
8035
8216
  });
@@ -8043,8 +8224,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8043
8224
  */
8044
8225
  async submitBid(commitment, userOp) {
8045
8226
  try {
8046
- const extrinsic = this.api.tx.intentsCoprocessor.placeBid(commitment, userOp);
8047
- return await this.signAndSendExtrinsic(extrinsic);
8227
+ return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.placeBid(commitment, userOp));
8048
8228
  } catch (error) {
8049
8229
  return {
8050
8230
  success: false,
@@ -8062,8 +8242,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8062
8242
  */
8063
8243
  async retractBid(commitment) {
8064
8244
  try {
8065
- const extrinsic = this.api.tx.intentsCoprocessor.retractBid(commitment);
8066
- return await this.signAndSendExtrinsic(extrinsic);
8245
+ return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.retractBid(commitment));
8067
8246
  } catch (error) {
8068
8247
  return {
8069
8248
  success: false,
@@ -8093,11 +8272,12 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8093
8272
  */
8094
8273
  async submitBidWithRetraction(retractCommitment, bidCommitment, userOp) {
8095
8274
  try {
8096
- const batch = this.api.tx.utility.batch([
8097
- this.api.tx.intentsCoprocessor.placeBid(bidCommitment, userOp),
8098
- this.api.tx.intentsCoprocessor.retractBid(retractCommitment)
8099
- ]);
8100
- return await this.signAndSendExtrinsic(batch);
8275
+ return await this.signAndSendExtrinsic(
8276
+ (api) => api.tx.utility.batch([
8277
+ api.tx.intentsCoprocessor.placeBid(bidCommitment, userOp),
8278
+ api.tx.intentsCoprocessor.retractBid(retractCommitment)
8279
+ ])
8280
+ );
8101
8281
  } catch (error) {
8102
8282
  return {
8103
8283
  success: false,
@@ -8105,6 +8285,98 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8105
8285
  };
8106
8286
  }
8107
8287
  }
8288
+ /**
8289
+ * Places every phantom bid of one interval in a single extrinsic, retracting each chain's
8290
+ * previous bid alongside it.
8291
+ *
8292
+ * The pallet registers one phantom order per configured chain in the same block, so this is the
8293
+ * whole interval's set. Submitting them one at a time costs a block per chain: submissions are
8294
+ * serialised on the account nonce and each waits for inclusion, so the last chain's bid is many
8295
+ * blocks behind the first, against a bid window measured in tens of blocks. Batched, every bid
8296
+ * lands in the same block.
8297
+ *
8298
+ * Uses `utility.force_batch`, not `utility.batch`. `batch` stops at the first failing call, so
8299
+ * one rejected bid — a closed window, a duplicate, an insufficient deposit — would silently
8300
+ * drop every bid after it. `force_batch` runs them all and reports each outcome. It needs no
8301
+ * special origin: any signed account may call it, exactly like `batch`.
8302
+ *
8303
+ * @param bids - The bids to place; an empty list is a no-op
8304
+ * @returns Per-bid outcomes, in the order given
8305
+ */
8306
+ async submitPhantomBids(bids) {
8307
+ if (bids.length === 0) return { bids: [] };
8308
+ const placeIndexByBid = [];
8309
+ let callCount = 0;
8310
+ for (const bid of bids) {
8311
+ placeIndexByBid.push(callCount);
8312
+ callCount += bid.retractCommitment ? 2 : 1;
8313
+ }
8314
+ const outcome = await this.signAndSendExtrinsic(
8315
+ (api) => api.tx.utility.forceBatch(
8316
+ bids.flatMap((bid) => {
8317
+ const calls = [api.tx.intentsCoprocessor.placeBid(bid.commitment, bid.userOp)];
8318
+ if (bid.retractCommitment) {
8319
+ calls.push(api.tx.intentsCoprocessor.retractBid(bid.retractCommitment));
8320
+ }
8321
+ return calls;
8322
+ })
8323
+ )
8324
+ );
8325
+ if (!outcome.success) {
8326
+ return {
8327
+ bids: bids.map((bid) => ({ commitment: bid.commitment, success: false, error: outcome.error })),
8328
+ pending: outcome.pending,
8329
+ extrinsicHash: outcome.extrinsicHash,
8330
+ error: outcome.error
8331
+ };
8332
+ }
8333
+ const items = this.readForceBatchItems(outcome.events ?? [], callCount);
8334
+ return {
8335
+ bids: bids.map((bid, index) => {
8336
+ const error = items.errors[placeIndexByBid[index]];
8337
+ return { commitment: bid.commitment, success: !error, error };
8338
+ }),
8339
+ blockHash: outcome.blockHash,
8340
+ extrinsicHash: outcome.extrinsicHash,
8341
+ error: items.error
8342
+ };
8343
+ }
8344
+ /**
8345
+ * Reads one outcome per call out of a force_batch's events.
8346
+ *
8347
+ * `ItemFailed` carries no index — pallet-utility emits exactly one `ItemCompleted` or
8348
+ * `ItemFailed` per call, in call order, so the k-th item event belongs to call k.
8349
+ *
8350
+ * A count that does not match the calls submitted means the events are not the ones assumed
8351
+ * here, and every attribution after the discrepancy would be off by one. The bids are then
8352
+ * reported as placed: a bid wrongly recorded as landed is retracted next interval and the
8353
+ * retraction harmlessly fails with `BidNotFound`, whereas one wrongly recorded as failed is
8354
+ * never retracted at all and leaves its deposit reserved.
8355
+ */
8356
+ readForceBatchItems(events, callCount) {
8357
+ const errors = [];
8358
+ for (const { event } of events) {
8359
+ if (event.section !== "utility") continue;
8360
+ if (event.method === "ItemCompleted") errors.push(void 0);
8361
+ else if (event.method === "ItemFailed") errors.push(this.describeDispatchError(event.data[0]));
8362
+ }
8363
+ if (errors.length !== callCount) {
8364
+ return {
8365
+ errors: new Array(callCount).fill(void 0),
8366
+ error: `force_batch reported ${errors.length} item events for ${callCount} calls; outcomes not attributed`
8367
+ };
8368
+ }
8369
+ return { errors };
8370
+ }
8371
+ /** Renders a DispatchError as `pallet::Error`, falling back to its raw form. */
8372
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8373
+ describeDispatchError(dispatchError) {
8374
+ if (dispatchError?.isModule) {
8375
+ const decoded = this.api.registry.findMetaError(dispatchError.asModule);
8376
+ return `${decoded.section}::${decoded.name}`;
8377
+ }
8378
+ return dispatchError?.toString() ?? "unknown dispatch error";
8379
+ }
8108
8380
  /**
8109
8381
  * Fetches all bid storage entries for a given order commitment.
8110
8382
  * Returns the on-chain data only (filler addresses and deposits).
@@ -8113,7 +8385,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8113
8385
  * @returns Array of BidStorageEntry objects
8114
8386
  */
8115
8387
  async getBidStorageEntries(commitment) {
8116
- const entries = await this.api.query.intentsCoprocessor.bids.entries(commitment);
8388
+ const api = await this.http();
8389
+ const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
8117
8390
  return entries.map(([storageKey, depositValue]) => ({
8118
8391
  commitment,
8119
8392
  filler: storageKey.args[1].toString(),
@@ -8143,9 +8416,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8143
8416
  * Single round-trip but does not include deposit amounts.
8144
8417
  */
8145
8418
  async getBidsViaRpc(commitment) {
8146
- const result = await this.api._rpcCore.provider.send("intents_getBidsForOrder", [
8147
- commitment
8148
- ]);
8419
+ const api$1 = await this.http();
8420
+ const result = await api$1._rpcCore.provider.send("intents_getBidsForOrder", [commitment]);
8149
8421
  return result.map((entry) => {
8150
8422
  const userOp = decodeUserOpScale(entry.user_op);
8151
8423
  const filler = new api.Keyring({ type: "sr25519" }).encodeAddress(util.hexToU8a(entry.filler));
@@ -8157,7 +8429,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8157
8429
  * Slower but works on all nodes and includes deposit amounts.
8158
8430
  */
8159
8431
  async getBidsViaStorage(commitment) {
8160
- const entries = await this.api.query.intentsCoprocessor.bids.entries(commitment);
8432
+ const api = await this.http();
8433
+ const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
8161
8434
  if (entries.length === 0) return [];
8162
8435
  const bidPromises = entries.map(async ([storageKey, depositValue]) => {
8163
8436
  try {
@@ -8165,7 +8438,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8165
8438
  const deposit = BigInt(depositValue.toString());
8166
8439
  const offchainKey = this.buildOffchainBidKey(commitment, filler);
8167
8440
  const offchainKeyHex = util.u8aToHex(offchainKey);
8168
- const offchainResult = await this.api.rpc.offchain.localStorageGet("PERSISTENT", offchainKeyHex);
8441
+ const offchainResult = await api.rpc.offchain.localStorageGet("PERSISTENT", offchainKeyHex);
8169
8442
  if (!offchainResult || offchainResult.isNone) return null;
8170
8443
  const bidData = offchainResult.unwrap().toHex();
8171
8444
  const decoded = this.decodeBid(bidData);
@@ -8199,13 +8472,12 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8199
8472
  */
8200
8473
  async fetchPhantomOrder(commitment) {
8201
8474
  const key = util.u8aConcat(OFFCHAIN_PHANTOM_PREFIX, util.hexToU8a(commitment));
8202
- const result = await this.api.rpc.offchain.localStorageGet("PERSISTENT", util.u8aToHex(key));
8475
+ const api = await this.http();
8476
+ const result = await api.rpc.offchain.localStorageGet("PERSISTENT", util.u8aToHex(key));
8203
8477
  if (!result || result.isNone) return null;
8204
8478
  const rawHex = result.unwrap().toHex();
8205
8479
  if (rawHex === "0x" || rawHex === "0x00") return null;
8206
- const placeOrderAbi = IntentGatewayV2_default.ABI.find(
8207
- (item) => item.type === "function" && item.name === "placeOrder"
8208
- );
8480
+ const placeOrderAbi = IntentGatewayV2_default.ABI.find((item) => item.type === "function" && item.name === "placeOrder");
8209
8481
  const orderType = placeOrderAbi?.inputs?.[0];
8210
8482
  if (!orderType) return null;
8211
8483
  const [decoded] = viem.decodeAbiParameters([orderType], rawHex);
@@ -8245,8 +8517,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8245
8517
  * Reads the PhantomOrderRegistered events emitted in a single block.
8246
8518
  */
8247
8519
  async getPhantomOrdersInBlock(blockNumber) {
8248
- const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8249
- const apiAt = await this.api.at(blockHash);
8520
+ const api = await this.http();
8521
+ const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
8522
+ const apiAt = await api.at(blockHash);
8250
8523
  const records = await apiAt.query.system.events();
8251
8524
  const orders = [];
8252
8525
  for (const { event } of records) {
@@ -8267,7 +8540,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8267
8540
  return orders;
8268
8541
  }
8269
8542
  /**
8270
- * Polls for newly registered phantom orders, invoking the callback once per order.
8543
+ * Polls for newly registered phantom orders, invoking the callback once per block that carries
8544
+ * any, with all of that block's orders.
8545
+ *
8546
+ * Per block rather than per order because that is how the pallet writes them: one order per
8547
+ * configured chain, all registered in the same `on_initialize`. Delivering them together lets a
8548
+ * caller bid on the whole interval in one extrinsic (see {@link submitPhantomBids}) instead of
8549
+ * one per chain.
8271
8550
  *
8272
8551
  * Each tick reads the current head and scans every block between the last one processed and that
8273
8552
  * head, so the block cursor — not the connection — determines what has been seen. This replaced a
@@ -8280,10 +8559,16 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8280
8559
  * cannot drop them, because the cursor only advances past a block whose events were actually
8281
8560
  * read. Recovery replays the backlog.
8282
8561
  *
8562
+ * Every read here goes over HTTP, never the websocket. Polling is a sequence of independent
8563
+ * one-shot requests with no state to lose between them, which is exactly what a stateless
8564
+ * transport does well: a request either answers or fails loudly on this tick, instead of a
8565
+ * socket that looks alive while delivering nothing. It also means a websocket outage does not
8566
+ * pause phantom bidding at all — the two transports fail independently.
8567
+ *
8283
8568
  * Returns a function that stops polling.
8284
8569
  */
8285
8570
  pollPhantomOrders(callback, options = {}) {
8286
- const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8571
+ const { intervalMs, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8287
8572
  let cursor = null;
8288
8573
  let inFlight = false;
8289
8574
  let stopped = false;
@@ -8291,7 +8576,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8291
8576
  if (inFlight || stopped) return;
8292
8577
  inFlight = true;
8293
8578
  try {
8294
- const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8579
+ const head = (await (await this.http()).rpc.chain.getHeader()).number.toNumber();
8295
8580
  if (cursor === null) {
8296
8581
  cursor = Math.max(head - 1 - lookbackBlocks, -1);
8297
8582
  }
@@ -8300,7 +8585,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8300
8585
  for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8301
8586
  if (stopped) return;
8302
8587
  const orders = await this.getPhantomOrdersInBlock(blockNumber);
8303
- for (const order of orders) callback(order);
8588
+ if (orders.length > 0) callback(orders);
8304
8589
  cursor = blockNumber;
8305
8590
  }
8306
8591
  } catch (err) {
@@ -8310,12 +8595,31 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8310
8595
  }
8311
8596
  };
8312
8597
  void tick();
8313
- const timer = setInterval(() => void tick(), intervalMs);
8598
+ let timer = null;
8599
+ const startTimer = (ms) => {
8600
+ if (stopped) return;
8601
+ timer = setInterval(() => void tick(), ms);
8602
+ };
8603
+ if (intervalMs !== void 0) startTimer(intervalMs);
8604
+ else void this.phantomPollIntervalMs().then(startTimer);
8314
8605
  return () => {
8315
8606
  stopped = true;
8316
- clearInterval(timer);
8607
+ if (timer) clearInterval(timer);
8317
8608
  };
8318
8609
  }
8610
+ /**
8611
+ * The poll cadence for the runtime this instance is connected to: Gargantua polls every block,
8612
+ * everything else every 15s. Falls back to the slower cadence if the runtime cannot be read,
8613
+ * since an unreachable node is the poll's problem to report, not the cadence lookup's.
8614
+ */
8615
+ async phantomPollIntervalMs() {
8616
+ try {
8617
+ const specName = (await this.http()).runtimeVersion.specName.toString();
8618
+ return specName === "gargantua" ? GARGANTUA_PHANTOM_POLL_INTERVAL_MS : PHANTOM_POLL_INTERVAL_MS;
8619
+ } catch {
8620
+ return PHANTOM_POLL_INTERVAL_MS;
8621
+ }
8622
+ }
8319
8623
  };
8320
8624
  var TronChain = class _TronChain {
8321
8625
  constructor(params, evm) {
@@ -15117,6 +15421,12 @@ var CryptoUtils = class _CryptoUtils {
15117
15421
  * signing infrastructure (hardware wallets, MPC/TEE policy engines) instead
15118
15422
  * of an opaque 32-byte digest.
15119
15423
  *
15424
+ * The payload must be a standard self-describing `eth_signTypedData_v4`
15425
+ * payload — `EIP712Domain` listed in `types`, `chainId` as a JSON number —
15426
+ * because some signing backends (e.g. MPC Vault) hash it server-side from
15427
+ * the JSON rather than locally via viem. viem ignores both details when
15428
+ * hashing, so the digest is unchanged for local signers.
15429
+ *
15120
15430
  * @param userOp - The packed UserOperation to sign (signature field ignored).
15121
15431
  * @param entryPoint - Address of the EntryPoint v0.8 contract.
15122
15432
  * @param chainId - Chain ID of the network on which the operation will execute.
@@ -15127,10 +15437,22 @@ var CryptoUtils = class _CryptoUtils {
15127
15437
  domain: {
15128
15438
  name: "ERC4337",
15129
15439
  version: "1",
15130
- chainId,
15440
+ // Runtime number so JSON.stringify emits a canonical v4 numeric chainId for
15441
+ // server-side hashers; viem's uint256 type mapping wants bigint but its
15442
+ // runtime accepts numbers, hence the cast.
15443
+ chainId: Number(chainId),
15131
15444
  verifyingContract: entryPoint
15132
15445
  },
15446
+ // `as const`: viem derives the domain's TYPE from `types.EIP712Domain`, so the
15447
+ // entries must stay string literals — widened `string` fields make viem's
15448
+ // typed-data generics reject the payload at every call site.
15133
15449
  types: {
15450
+ EIP712Domain: [
15451
+ { name: "name", type: "string" },
15452
+ { name: "version", type: "string" },
15453
+ { name: "chainId", type: "uint256" },
15454
+ { name: "verifyingContract", type: "address" }
15455
+ ],
15134
15456
  PackedUserOperation: [
15135
15457
  { name: "sender", type: "address" },
15136
15458
  { name: "nonce", type: "uint256" },
@@ -16994,14 +17316,14 @@ var BidManager = class {
16994
17316
  }
16995
17317
  /**
16996
17318
  * 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.
17319
+ * each in order and attempts execution. Any bid that fails simulation or
17320
+ * execution is skipped once so the next ranked bid can be attempted in the
17321
+ * same round. For consumers that do not need custom selection logic.
16999
17322
  *
17000
17323
  * @param order - The placed order to fill.
17001
17324
  * @param bids - Candidate bids (from {@link buildBids}).
17002
17325
  * @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.
17326
+ * @throws If no valid bids exist or every bid fails simulation/execution.
17005
17327
  */
17006
17328
  async selectAndExecuteBest(order, bids) {
17007
17329
  const commitment = order.id;
@@ -17018,22 +17340,34 @@ var BidManager = class {
17018
17340
  throw new Error("No valid bids found");
17019
17341
  }
17020
17342
  console.log(`[BidManager] Simulating ${sortedBids.length} sorted bid(s) to find a valid one`);
17343
+ let simulationFailures = 0;
17344
+ let executionFailures = 0;
17021
17345
  for (let idx = 0; idx < sortedBids.length; idx++) {
17022
17346
  const bid = sortedBids[idx];
17023
17347
  console.log(`[BidManager] Simulating bid ${idx + 1}/${sortedBids.length} from solver=${bid.solverAddress}`);
17024
17348
  try {
17025
17349
  await bid.simulate();
17026
17350
  } catch (err) {
17351
+ simulationFailures += 1;
17027
17352
  console.warn(
17028
17353
  `[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation FAILED: ${err instanceof Error ? err.message : String(err)}`
17029
17354
  );
17030
17355
  continue;
17031
17356
  }
17032
17357
  console.log(`[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation PASSED`);
17033
- return bid.execute();
17358
+ try {
17359
+ return await bid.execute();
17360
+ } catch (err) {
17361
+ executionFailures += 1;
17362
+ console.warn(
17363
+ `[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: execution FAILED: ${err instanceof Error ? err.message : String(err)}; trying next bid`
17364
+ );
17365
+ }
17034
17366
  }
17035
- console.error(`[BidManager] All ${sortedBids.length} bid(s) failed simulation for commitment=${commitment}`);
17036
- throw new Error("No bids passed simulation");
17367
+ console.error(
17368
+ `[BidManager] No executable bids for commitment=${commitment}: ${simulationFailures} simulation failure(s), ${executionFailures} execution failure(s)`
17369
+ );
17370
+ throw new Error("No bids passed simulation and execution");
17037
17371
  }
17038
17372
  /**
17039
17373
  * Sorts a list of bids for the given order by output value.
@@ -17373,6 +17707,10 @@ var BidManager = class {
17373
17707
  }
17374
17708
  };
17375
17709
  var RELAYER_MESSAGE_GAS = 1000000n;
17710
+ var NO_BUNDLER_FILL_GAS_BASE = 700000n;
17711
+ var NO_BUNDLER_FILL_GAS_PER_OUTPUT = 150000n;
17712
+ var NO_BUNDLER_PAYMASTER_VERIFICATION_GAS = 250000n;
17713
+ var NO_BUNDLER_PAYMASTER_POST_OP_GAS = 100000n;
17376
17714
  var GasEstimator = class {
17377
17715
  /**
17378
17716
  * @param ctx - Shared IntentsV2 context providing the source and destination
@@ -17406,8 +17744,10 @@ var GasEstimator = class {
17406
17744
  * headroom. If the bundler is Pimlico, gas prices are refined with
17407
17745
  * `pimlico_getUserOperationGasPrice`.
17408
17746
  *
17409
- * **Fallback path (no bundler):** calls `estimateContractGas` directly on
17410
- * `fillOrder` with state overrides.
17747
+ * **Fallback path (no bundler):** uses a fixed budget
17748
+ * ({@link NO_BUNDLER_FILL_GAS_BASE} plus {@link NO_BUNDLER_FILL_GAS_PER_OUTPUT}
17749
+ * per output leg) — public RPCs don't reliably support estimation with
17750
+ * state overrides.
17411
17751
  *
17412
17752
  * @param params - Parameters including the order to estimate and optional
17413
17753
  * percentage bumps for `maxPriorityFeePerGas` and `maxFeePerGas`.
@@ -17442,14 +17782,14 @@ var GasEstimator = class {
17442
17782
  }
17443
17783
  const isSameChain = souceStateMachineId === destStateMachineId;
17444
17784
  const [stateOverridesResult, crossChainFees] = await Promise.all([
17445
- this.buildStateOverride({
17785
+ this.ctx.bundlerUrl ? this.buildStateOverride({
17446
17786
  accountAddress: solverAccountAddress,
17447
17787
  chain: destStateMachineId,
17448
17788
  outputAssets: assetsForOverrides,
17449
17789
  spenderAddress: intentGatewayV2Address,
17450
17790
  intentGatewayV2Address,
17451
17791
  entryPointAddress
17452
- }),
17792
+ }) : Promise.resolve({ viem: [], bundler: {} }),
17453
17793
  isSameChain ? Promise.resolve({ postRequestFee: 0n, relayerFeeInSourceFeeToken: 0n }) : this.estimateCrossChainFees(
17454
17794
  sourceFeeToken,
17455
17795
  destFeeToken,
@@ -17457,7 +17797,7 @@ var GasEstimator = class {
17457
17797
  orderFeeGasPriceBumpPercent
17458
17798
  )
17459
17799
  ]);
17460
- const { viem: stateOverrides, bundler: bundlerStateOverrides } = stateOverridesResult;
17800
+ const { bundler: bundlerStateOverrides } = stateOverridesResult;
17461
17801
  const fillOptions = {
17462
17802
  relayerFee: crossChainFees.postRequestFee,
17463
17803
  // Always dispatch with the fee token (see the method docs).
@@ -17598,20 +17938,9 @@ var GasEstimator = class {
17598
17938
  console.warn("Bundler gas estimation failed, using fallback values:", e);
17599
17939
  }
17600
17940
  } 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
- }
17941
+ callGasLimit = NO_BUNDLER_FILL_GAS_BASE + NO_BUNDLER_FILL_GAS_PER_OUTPUT * BigInt(order.output.assets.length);
17942
+ paymasterVerificationGasLimit = NO_BUNDLER_PAYMASTER_VERIFICATION_GAS;
17943
+ paymasterPostOpGasLimit = NO_BUNDLER_PAYMASTER_POST_OP_GAS;
17615
17944
  }
17616
17945
  const totalGas = callGasLimit + verificationGasLimit + preVerificationGas + paymasterVerificationGasLimit + paymasterPostOpGasLimit;
17617
17946
  const rawTotalGasCostWei = totalGas * maxFeePerGas;
@@ -19141,14 +19470,34 @@ var IntentGateway = class _IntentGateway {
19141
19470
  }
19142
19471
  }
19143
19472
  };
19473
+
19474
+ // src/protocols/intents/phantom-aggregation.ts
19144
19475
  var FILL_ORDER_ABI = IntentGatewayV2_default.ABI;
19145
- var DECLARATION_VERSION = 1;
19146
- var MAX_DECLARED_CHAINS = 255;
19147
- function encodeAcceptedSourceChains(chains2) {
19148
- if (chains2.length > MAX_DECLARED_CHAINS) {
19149
- throw new Error(`Cannot declare more than ${MAX_DECLARED_CHAINS} source chains`);
19476
+ var DECLARATION_V1 = 1;
19477
+ var DECLARATION_V2 = 2;
19478
+ var MAX_DECLARED_ENTRIES = 255;
19479
+ var MAX_TOKEN_ID_BYTES = 32;
19480
+ function tokenIdToBytes(tokenId) {
19481
+ if (tokenId < 0n) throw new Error(`Uniswap V4 tokenId cannot be negative: ${tokenId}`);
19482
+ const bytes = [];
19483
+ let rest = tokenId;
19484
+ while (rest > 0n) {
19485
+ bytes.unshift(Number(rest & 0xffn));
19486
+ rest >>= 8n;
19487
+ }
19488
+ return bytes.length > 0 ? bytes : [0];
19489
+ }
19490
+ function encodePhantomBidDeclaration(declaration) {
19491
+ const chains2 = declaration.acceptedSourceChains ?? [];
19492
+ const positions = declaration.uniswapV4Positions ?? [];
19493
+ if (chains2.length > MAX_DECLARED_ENTRIES) {
19494
+ throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} source chains`);
19150
19495
  }
19151
- const bytes = [DECLARATION_VERSION, chains2.length];
19496
+ if (positions.length > MAX_DECLARED_ENTRIES) {
19497
+ throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} Uniswap V4 positions`);
19498
+ }
19499
+ const version = positions.length > 0 ? DECLARATION_V2 : DECLARATION_V1;
19500
+ const bytes = [version, chains2.length];
19152
19501
  for (const chain of chains2) {
19153
19502
  const encoded = util.stringToU8a(chain);
19154
19503
  if (encoded.length === 0 || encoded.length > 255) {
@@ -19156,25 +19505,59 @@ function encodeAcceptedSourceChains(chains2) {
19156
19505
  }
19157
19506
  bytes.push(encoded.length, ...encoded);
19158
19507
  }
19508
+ if (version === DECLARATION_V2) {
19509
+ bytes.push(positions.length);
19510
+ for (const tokenId of positions) {
19511
+ const encoded = tokenIdToBytes(tokenId);
19512
+ if (encoded.length > MAX_TOKEN_ID_BYTES) {
19513
+ throw new Error(`Uniswap V4 tokenId exceeds uint256: ${tokenId}`);
19514
+ }
19515
+ bytes.push(encoded.length, ...encoded);
19516
+ }
19517
+ }
19159
19518
  return util.u8aToHex(new Uint8Array(bytes));
19160
19519
  }
19161
- function decodeAcceptedSourceChains(paymasterAndData) {
19162
- if (!paymasterAndData || !util.isHex(paymasterAndData)) return null;
19520
+ function decodePhantomBidDeclaration(paymasterAndData) {
19521
+ const absent = { acceptedSources: null, uniswapV4Positions: [] };
19522
+ if (!paymasterAndData || !util.isHex(paymasterAndData)) return absent;
19163
19523
  const bytes = util.hexToU8a(paymasterAndData);
19164
- if (bytes.length < 2 || bytes[0] !== DECLARATION_VERSION) return null;
19165
- const count = bytes[1];
19524
+ if (bytes.length < 2) return absent;
19525
+ const version = bytes[0];
19526
+ if (version !== DECLARATION_V1 && version !== DECLARATION_V2) return absent;
19166
19527
  const chains2 = [];
19167
19528
  let offset = 2;
19168
- for (let entry = 0; entry < count; entry++) {
19169
- if (offset >= bytes.length) return null;
19529
+ for (let entry = 0; entry < bytes[1]; entry++) {
19530
+ if (offset >= bytes.length) return absent;
19170
19531
  const length = bytes[offset];
19171
19532
  offset += 1;
19172
- if (length === 0 || offset + length > bytes.length) return null;
19533
+ if (length === 0 || offset + length > bytes.length) return absent;
19173
19534
  chains2.push(util.u8aToString(bytes.subarray(offset, offset + length)));
19174
19535
  offset += length;
19175
19536
  }
19176
- if (offset !== bytes.length) return null;
19177
- return chains2;
19537
+ const positions = [];
19538
+ if (version === DECLARATION_V2) {
19539
+ if (offset >= bytes.length) return absent;
19540
+ const count = bytes[offset];
19541
+ offset += 1;
19542
+ for (let entry = 0; entry < count; entry++) {
19543
+ if (offset >= bytes.length) return absent;
19544
+ const length = bytes[offset];
19545
+ offset += 1;
19546
+ if (length === 0 || length > MAX_TOKEN_ID_BYTES || offset + length > bytes.length) return absent;
19547
+ let tokenId = 0n;
19548
+ for (const byte of bytes.subarray(offset, offset + length)) tokenId = tokenId << 8n | BigInt(byte);
19549
+ positions.push(tokenId);
19550
+ offset += length;
19551
+ }
19552
+ }
19553
+ if (offset !== bytes.length) return absent;
19554
+ return { acceptedSources: chains2, uniswapV4Positions: positions };
19555
+ }
19556
+ function encodeAcceptedSourceChains(chains2) {
19557
+ return encodePhantomBidDeclaration({ acceptedSourceChains: chains2 });
19558
+ }
19559
+ function decodeAcceptedSourceChains(paymasterAndData) {
19560
+ return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
19178
19561
  }
19179
19562
  FILL_ORDER_ABI.find(
19180
19563
  (item) => item?.type === "function" && item?.name === "fillOrder"
@@ -23720,10 +24103,13 @@ exports.createEvmChain = createEvmChain;
23720
24103
  exports.createQueryClient = createQueryClient;
23721
24104
  exports.decodeAcceptedSourceChains = decodeAcceptedSourceChains;
23722
24105
  exports.decodeERC7821ExecuteBatch = decodeERC7821ExecuteBatch;
24106
+ exports.decodePhantomBidDeclaration = decodePhantomBidDeclaration;
23723
24107
  exports.decodeUserOpScale = decodeUserOpScale;
24108
+ exports.deriveHttpUrl = deriveHttpUrl;
23724
24109
  exports.encodeAcceptedSourceChains = encodeAcceptedSourceChains;
23725
24110
  exports.encodeERC7821ExecuteBatch = encodeERC7821ExecuteBatch;
23726
24111
  exports.encodeISMPMessage = encodeISMPMessage;
24112
+ exports.encodePhantomBidDeclaration = encodePhantomBidDeclaration;
23727
24113
  exports.encodeStateMachineId = encodeStateMachineId;
23728
24114
  exports.encodeUserOpScale = encodeUserOpScale;
23729
24115
  exports.encodeWithdrawalRequest = encodeWithdrawalRequest;