@hyperbridge/sdk 1.8.6 → 1.9.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.
@@ -606,10 +606,9 @@ var IntentOrderStatus = Object.freeze({
606
606
  AWAITING_BIDS: "AWAITING_BIDS",
607
607
  BIDS_RECEIVED: "BIDS_RECEIVED",
608
608
  BID_SELECTED: "BID_SELECTED",
609
- USEROP_SUBMITTED: "USEROP_SUBMITTED",
610
609
  FILLED: "FILLED",
611
610
  PARTIAL_FILL: "PARTIAL_FILL",
612
- PARTIAL_FILL_EXHAUSTED: "PARTIAL_FILL_EXHAUSTED",
611
+ EXPIRED: "EXPIRED",
613
612
  FAILED: "FAILED"
614
613
  });
615
614
 
@@ -3837,6 +3836,9 @@ var chainConfigs = {
3837
3836
  UniversalRouter: "0x66a9893cc07d91d95644aedd05d03f95e1dba8af",
3838
3837
  UniswapV3Quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
3839
3838
  UniswapV4Quoter: "0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203",
3839
+ UniswapV4PositionManager: "0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e",
3840
+ UniswapV4PoolManager: "0x000000000004444c5dc75cB358380D2e3dE08A90",
3841
+ UniswapV4StateView: "0x7ffe42c4a5deea5b0fec41c94c136cf115597227",
3840
3842
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
3841
3843
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
3842
3844
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
@@ -3889,6 +3891,9 @@ var chainConfigs = {
3889
3891
  UniversalRouter: "0xd9C500DfF816a1Da21A48A732d3498Bf09dc9AEB",
3890
3892
  UniswapV3Quoter: "0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997",
3891
3893
  UniswapV4Quoter: "0xd0737C9762912dD34c3271197E362Aa736Df0926",
3894
+ UniswapV4PositionManager: "0x7a4a5c919ae2541aed11041a1aeee68f1287f95b",
3895
+ UniswapV4PoolManager: "0x28e2ea090877bf75740558f6bfb36a5ffee9e9df",
3896
+ UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
3892
3897
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
3893
3898
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
3894
3899
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
@@ -3943,6 +3948,9 @@ var chainConfigs = {
3943
3948
  UniversalRouter: "0xa51afafe0263b40edaef0df8781ea9aa03e381a3",
3944
3949
  UniswapV3Quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
3945
3950
  UniswapV4Quoter: "0x3972c00f7ed4885e145823eb7c655375d275a1c5",
3951
+ UniswapV4PositionManager: "0xd88f38f930b7952f2db2432cb002e7abbf3dd869",
3952
+ UniswapV4PoolManager: "0x360e68faccca8ca495c1b759fd9eee466db9fb32",
3953
+ UniswapV4StateView: "0x76fd297e2d437cd7f76d50f01afe6160f86e9990",
3946
3954
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
3947
3955
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
3948
3956
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
@@ -3999,7 +4007,11 @@ var chainConfigs = {
3999
4007
  UniswapV4Quoter: "0x0d5e0f971ed27fbff6c2837bf31316121532048d",
4000
4008
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
4001
4009
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
4002
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
4010
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
4011
+ AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
4012
+ UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
4013
+ UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
4014
+ UniswapV4StateView: "0xa3c0c9b65bad0b08107aa264b0f3db444b867a71"
4003
4015
  // Usdt0Oft: Not available on Base
4004
4016
  },
4005
4017
  rpcEnvKey: "BASE_MAINNET",
@@ -4051,6 +4063,9 @@ var chainConfigs = {
4051
4063
  UniversalRouter: "0x1095692a6237d83c6a72f3f5efedb9a670c49223",
4052
4064
  UniswapV3Quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
4053
4065
  UniswapV4Quoter: "0xb3d5c3dfc3a7aebff71895a7191796bffc2c81b9",
4066
+ UniswapV4PositionManager: "0x1ec2ebf4f37e7363fdfe3551602425af0b3ceef9",
4067
+ UniswapV4PoolManager: "0x67366782805870060151383f4bbff9dab53e5cd6",
4068
+ UniswapV4StateView: "0x5ea1bd7974c8a611cbab0bdcafcb1d9cc9b3ba5a",
4054
4069
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
4055
4070
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
4056
4071
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
@@ -4095,6 +4110,9 @@ var chainConfigs = {
4095
4110
  UniversalRouter: "0xef740bf23acae26f6492b10de645d6b98dc8eaf3",
4096
4111
  UniswapV3Quoter: "0x385a5cf5f83e99f7bb2852b6a19c3538b9fa7658",
4097
4112
  UniswapV4Quoter: "0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203",
4113
+ UniswapV4PositionManager: "0x4529a01c7a0410167c5740c487a8de60232617bf",
4114
+ UniswapV4PoolManager: "0x1f98400000000000000000000000000000000004",
4115
+ UniswapV4StateView: "0x86e8631a016f9068c3f085faf484ee3f5fdee8f2",
4098
4116
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
4099
4117
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
4100
4118
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
@@ -4200,7 +4218,9 @@ var chainConfigs = {
4200
4218
  UniswapV3Factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
4201
4219
  Calldispatcher: "0xC71251c8b3e7B02697A84363Eef6DcE8DfBdF333",
4202
4220
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
4203
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
4221
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
4222
+ UniswapV4PositionManager: "0x3c3ea4b57a46241e54610e5f022e5c45859a1017",
4223
+ UniswapV4PoolManager: "0x9a13f98cb987694c9f086b1f5eb990eea8264ec3"
4204
4224
  },
4205
4225
  defaultRpcUrl: "https://mainnet.optimism.io",
4206
4226
  consensusStateId: "ETH0",
@@ -4256,7 +4276,9 @@ var chainConfigs = {
4256
4276
  addresses: {
4257
4277
  TokenGateway: "0xCe304770236f39F9911BfCC51afBdfF3b8635718",
4258
4278
  Host: "0x7F0165140D0f3251c8f6465e94E9d12C7FD40711",
4259
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
4279
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
4280
+ UniswapV4PositionManager: "0x1b35d13a2e2528f192637f14b05f0dc0e7deb566",
4281
+ UniswapV4PoolManager: "0x360e68faccca8ca495c1b759fd9eee466db9fb32"
4260
4282
  },
4261
4283
  defaultRpcUrl: "https://rpc.soneium.org",
4262
4284
  consensusStateId: "ETH0",
@@ -4450,6 +4472,9 @@ var ChainConfigService = class {
4450
4472
  getUniswapRouterV2Address(chain) {
4451
4473
  return this.getConfig(chain)?.addresses.UniswapRouter02 ?? "0x";
4452
4474
  }
4475
+ getAerodromeRouterAddress(chain) {
4476
+ return this.getConfig(chain)?.addresses.AerodromeRouter ?? "0x";
4477
+ }
4453
4478
  getUniswapV2FactoryAddress(chain) {
4454
4479
  return this.getConfig(chain)?.addresses.UniswapV2Factory ?? "0x";
4455
4480
  }
@@ -4465,6 +4490,15 @@ var ChainConfigService = class {
4465
4490
  getUniswapV4QuoterAddress(chain) {
4466
4491
  return this.getConfig(chain)?.addresses.UniswapV4Quoter ?? "0x";
4467
4492
  }
4493
+ getUniswapV4PositionManagerAddress(chain) {
4494
+ return this.getConfig(chain)?.addresses.UniswapV4PositionManager ?? "0x";
4495
+ }
4496
+ getUniswapV4PoolManagerAddress(chain) {
4497
+ return this.getConfig(chain)?.addresses.UniswapV4PoolManager ?? "0x";
4498
+ }
4499
+ getUniswapV4StateViewAddress(chain) {
4500
+ return this.getConfig(chain)?.addresses.UniswapV4StateView ?? "0x";
4501
+ }
4468
4502
  getPermit2Address(chain) {
4469
4503
  return this.getConfig(chain)?.addresses.Permit2 ?? "0x";
4470
4504
  }
@@ -5548,6 +5582,7 @@ var EvmChain = class _EvmChain {
5548
5582
  });
5549
5583
  this.chainConfigService = new ChainConfigService();
5550
5584
  }
5585
+ params;
5551
5586
  publicClient;
5552
5587
  chainConfigService;
5553
5588
  /**
@@ -6148,6 +6183,7 @@ var HttpRpcClient = class {
6148
6183
  constructor(url) {
6149
6184
  this.url = url;
6150
6185
  }
6186
+ url;
6151
6187
  /**
6152
6188
  * Make an RPC call over HTTP
6153
6189
  * @param method - The RPC method name
@@ -6185,6 +6221,7 @@ var SubstrateChain = class _SubstrateChain {
6185
6221
  const httpUrl = replaceWebsocketWithHttp(url);
6186
6222
  this.rpcClient = new HttpRpcClient(httpUrl);
6187
6223
  }
6224
+ params;
6188
6225
  /*
6189
6226
  * api: The Polkadot API instance for the Substrate chain.
6190
6227
  */
@@ -6702,6 +6739,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
6702
6739
  this.substratePrivateKey = substratePrivateKey;
6703
6740
  this.ownsConnection = ownsConnection;
6704
6741
  }
6742
+ api;
6743
+ substratePrivateKey;
6744
+ ownsConnection;
6705
6745
  /** Cached result of whether the node exposes intents_* RPC methods */
6706
6746
  hasIntentsRpc = null;
6707
6747
  /**
@@ -6979,6 +7019,7 @@ var TronChain = class _TronChain {
6979
7019
  this.evm = evm;
6980
7020
  this.tronWebInstance = new TronWeb({ fullHost: params.rpcUrl });
6981
7021
  }
7022
+ params;
6982
7023
  evm;
6983
7024
  tronWebInstance;
6984
7025
  /**
@@ -7519,6 +7560,7 @@ var SubstrateHttpRpc = class {
7519
7560
  constructor(url) {
7520
7561
  this.url = url;
7521
7562
  }
7563
+ url;
7522
7564
  async call(method, params = []) {
7523
7565
  const body = JSON.stringify({
7524
7566
  jsonrpc: "2.0",
@@ -7563,6 +7605,7 @@ var PolkadotHubChain = class _PolkadotHubChain {
7563
7605
  this.evm = evm;
7564
7606
  this.substrateRpc = new SubstrateHttpRpc(replaceWebsocketWithHttp(params.substrateRpcUrl));
7565
7607
  }
7608
+ params;
7566
7609
  evm;
7567
7610
  substrateRpc;
7568
7611
  static fromParams(params) {
@@ -12631,7 +12674,9 @@ var BundlerMethod = {
12631
12674
  /** Estimates gas limits for a UserOperation before submission. */
12632
12675
  ETH_ESTIMATE_USER_OPERATION_GAS: "eth_estimateUserOperationGas",
12633
12676
  /** Pimlico-specific method to fetch recommended EIP-1559 gas prices for UserOperations. */
12634
- PIMLICO_GET_USER_OPERATION_GAS_PRICE: "pimlico_getUserOperationGasPrice"
12677
+ PIMLICO_GET_USER_OPERATION_GAS_PRICE: "pimlico_getUserOperationGasPrice",
12678
+ /** Alchemy (Rundler) method to fetch recommended priority fee for UserOperations. */
12679
+ RUNDLER_MAX_PRIORITY_FEE_PER_GAS: "rundler_maxPriorityFeePerGas"
12635
12680
  };
12636
12681
 
12637
12682
  // src/protocols/intents/CryptoUtils.ts
@@ -12652,6 +12697,7 @@ var CryptoUtils = class {
12652
12697
  constructor(ctx) {
12653
12698
  this.ctx = ctx;
12654
12699
  }
12700
+ ctx;
12655
12701
  /**
12656
12702
  * Computes an EIP-712 domain separator for a given contract.
12657
12703
  *
@@ -15187,6 +15233,7 @@ var OrderPlacer = class {
15187
15233
  constructor(ctx) {
15188
15234
  this.ctx = ctx;
15189
15235
  }
15236
+ ctx;
15190
15237
  /**
15191
15238
  * Bidirectional async generator that orchestrates order placement.
15192
15239
  *
@@ -15264,66 +15311,40 @@ var OrderPlacer = class {
15264
15311
  return { order, receipt };
15265
15312
  }
15266
15313
  };
15267
-
15268
- // src/protocols/intents/OrderExecutor.ts
15269
15314
  var USED_USEROPS_STORAGE_KEY = (commitment) => `used-userops:${commitment.toLowerCase()}`;
15270
15315
  var OrderExecutor = class {
15271
- /**
15272
- * @param ctx - Shared IntentsV2 context providing the destination chain
15273
- * client, coprocessor, bundler URL, and storage adapters.
15274
- * @param bidManager - Handles bid validation, sorting, simulation, and
15275
- * UserOperation submission.
15276
- * @param crypto - Crypto utilities used to compute UserOperation hashes for
15277
- * deduplication.
15278
- */
15279
15316
  constructor(ctx, bidManager, crypto) {
15280
15317
  this.ctx = ctx;
15281
15318
  this.bidManager = bidManager;
15282
15319
  this.crypto = crypto;
15283
15320
  }
15321
+ ctx;
15322
+ bidManager;
15323
+ crypto;
15284
15324
  /**
15285
- * Async generator that executes an intent order by polling for bids and
15286
- * submitting UserOperations until the order is filled, partially exhausted,
15287
- * or an unrecoverable error occurs.
15288
- *
15289
- * **Status progression (cross-chain orders):**
15290
- * `AWAITING_BIDS` → `BIDS_RECEIVED` → `BID_SELECTED` → `USEROP_SUBMITTED`
15291
- * then terminates (settlement is confirmed off-chain via Hyperbridge).
15292
- *
15293
- * **Status progression (same-chain orders):**
15294
- * `AWAITING_BIDS` → `BIDS_RECEIVED` → `BID_SELECTED` → `USEROP_SUBMITTED`
15295
- * → (`FILLED` | `PARTIAL_FILL`)* → (`FILLED` | `PARTIAL_FILL_EXHAUSTED`)
15296
- *
15297
- * **Error statuses:** `FAILED` (fatal, no fills) or `PARTIAL_FILL_EXHAUSTED`
15298
- * (deadline reached or no new bids after at least one partial fill).
15299
- *
15300
- * @param options - Execution parameters including the placed order, its
15301
- * session private key, bid collection settings, and poll interval.
15302
- * @yields {@link IntentOrderStatusUpdate} objects describing each stage.
15303
- * @throws Never throws directly; all errors are reported as `FAILED` yields.
15325
+ * Sleeps until the order's block deadline is reached, then yields EXPIRED.
15326
+ * Uses the chain's block time to calculate the sleep duration.
15304
15327
  */
15305
- async *executeIntentOrder(options) {
15306
- const {
15307
- order,
15308
- sessionPrivateKey,
15309
- minBids = 1,
15310
- bidTimeoutMs = 6e4,
15311
- pollIntervalMs = DEFAULT_POLL_INTERVAL,
15312
- solver
15313
- } = options;
15314
- const commitment = order.id;
15315
- const isSameChain = order.source === order.destination;
15316
- if (!this.ctx.intentsCoprocessor) {
15317
- yield { status: "FAILED", error: "IntentsCoprocessor required for order execution" };
15318
- return;
15319
- }
15320
- if (!this.ctx.bundlerUrl) {
15321
- yield { status: "FAILED", error: "Bundler URL not configured" };
15322
- return;
15328
+ async *deadlineStream(deadline, commitment) {
15329
+ const client = this.ctx.dest.client;
15330
+ const blockTimeSeconds = client.chain?.blockTime ?? 2;
15331
+ while (true) {
15332
+ const currentBlock = await client.getBlockNumber();
15333
+ if (currentBlock >= deadline) break;
15334
+ const blocksRemaining = Number(deadline - currentBlock);
15335
+ const sleepMs = blocksRemaining * blockTimeSeconds * 1e3;
15336
+ await sleep(sleepMs);
15323
15337
  }
15338
+ yield {
15339
+ status: "EXPIRED",
15340
+ commitment,
15341
+ error: "Order deadline reached"
15342
+ };
15343
+ }
15344
+ /** Loads the persisted deduplication set of already-submitted UserOp hashes for a given order commitment. */
15345
+ async loadUsedUserOps(commitment) {
15324
15346
  const usedUserOps = /* @__PURE__ */ new Set();
15325
- const storageKey = USED_USEROPS_STORAGE_KEY(commitment);
15326
- const persisted = await this.ctx.usedUserOpsStorage.getItem(storageKey);
15347
+ const persisted = await this.ctx.usedUserOpsStorage.getItem(USED_USEROPS_STORAGE_KEY(commitment));
15327
15348
  if (persisted) {
15328
15349
  try {
15329
15350
  const parsed = JSON.parse(persisted);
@@ -15333,83 +15354,210 @@ var OrderExecutor = class {
15333
15354
  } catch {
15334
15355
  }
15335
15356
  }
15336
- const persistUsedUserOps = async () => {
15337
- await this.ctx.usedUserOpsStorage.setItem(storageKey, JSON.stringify([...usedUserOps]));
15338
- };
15357
+ return usedUserOps;
15358
+ }
15359
+ /** Persists the deduplication set of UserOp hashes to storage. */
15360
+ async persistUsedUserOps(commitment, usedUserOps) {
15361
+ await this.ctx.usedUserOpsStorage.setItem(
15362
+ USED_USEROPS_STORAGE_KEY(commitment),
15363
+ JSON.stringify([...usedUserOps])
15364
+ );
15365
+ }
15366
+ /**
15367
+ * Creates a closure that computes the deduplication hash key for a
15368
+ * UserOperation, pre-bound to the order's destination chain and entry point.
15369
+ */
15370
+ createUserOpHasher(order) {
15339
15371
  const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(hexToString(order.destination));
15340
15372
  const chainId = BigInt(
15341
15373
  this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15342
15374
  );
15343
- const userOpHashKey = (userOp) => this.crypto.computeUserOpHash(userOp, entryPointAddress, chainId);
15375
+ return (userOp) => this.crypto.computeUserOpHash(userOp, entryPointAddress, chainId);
15376
+ }
15377
+ /**
15378
+ * Fetches bids from the coprocessor for a given order commitment.
15379
+ * If a preferred solver is configured and the solver lock has not expired,
15380
+ * only bids from that solver are returned.
15381
+ */
15382
+ async fetchBids(params) {
15383
+ const { commitment, solver, solverLockStartTime } = params;
15384
+ const fetchedBids = await this.ctx.intentsCoprocessor.getBidsForOrder(commitment);
15385
+ if (solver) {
15386
+ const { address, timeoutMs } = solver;
15387
+ const solverLockActive = Date.now() - solverLockStartTime < timeoutMs;
15388
+ return solverLockActive ? fetchedBids.filter((bid) => bid.userOp.sender.toLowerCase() === address.toLowerCase()) : fetchedBids;
15389
+ }
15390
+ return fetchedBids;
15391
+ }
15392
+ /**
15393
+ * Selects the best bid from the provided candidates, submits the
15394
+ * UserOperation, and persists the dedup entry to prevent resubmission.
15395
+ */
15396
+ async submitBid(params) {
15397
+ const { order, freshBids, sessionPrivateKey, commitment, usedUserOps, userOpHashKey } = params;
15398
+ const result = await this.bidManager.selectBid(order, freshBids, sessionPrivateKey);
15399
+ usedUserOps.add(userOpHashKey(result.userOp));
15400
+ await this.persistUsedUserOps(commitment, usedUserOps);
15401
+ return result;
15402
+ }
15403
+ /**
15404
+ * Processes a fill result and returns updated fill accumulators,
15405
+ * the status update to yield (if any), and whether the order is
15406
+ * fully satisfied.
15407
+ */
15408
+ processFillResult(result, commitment, targetAssets, totalFilledAssets, remainingAssets) {
15409
+ if (result.fillStatus === "full") {
15410
+ totalFilledAssets = targetAssets.map((a) => ({ token: a.token, amount: a.amount }));
15411
+ remainingAssets = targetAssets.map((a) => ({ token: a.token, amount: 0n }));
15412
+ return {
15413
+ update: {
15414
+ status: "FILLED",
15415
+ commitment,
15416
+ userOpHash: result.userOpHash,
15417
+ selectedSolver: result.solverAddress,
15418
+ transactionHash: result.txnHash,
15419
+ totalFilledAssets,
15420
+ remainingAssets
15421
+ },
15422
+ done: true,
15423
+ totalFilledAssets,
15424
+ remainingAssets
15425
+ };
15426
+ }
15427
+ if (result.fillStatus === "partial") {
15428
+ const filledAssets = result.filledAssets ?? [];
15429
+ totalFilledAssets = totalFilledAssets.map((a) => {
15430
+ const filled = filledAssets.find((f) => f.token === a.token);
15431
+ return filled ? { token: a.token, amount: a.amount + filled.amount } : { ...a };
15432
+ });
15433
+ remainingAssets = targetAssets.map((target) => {
15434
+ const filled = totalFilledAssets.find((a) => a.token === target.token);
15435
+ const filledAmt = filled?.amount ?? 0n;
15436
+ return {
15437
+ token: target.token,
15438
+ amount: filledAmt >= target.amount ? 0n : target.amount - filledAmt
15439
+ };
15440
+ });
15441
+ const fullyFilled = remainingAssets.every((a) => a.amount === 0n);
15442
+ return {
15443
+ update: fullyFilled ? {
15444
+ status: "FILLED",
15445
+ commitment,
15446
+ userOpHash: result.userOpHash,
15447
+ selectedSolver: result.solverAddress,
15448
+ transactionHash: result.txnHash,
15449
+ totalFilledAssets,
15450
+ remainingAssets
15451
+ } : {
15452
+ status: "PARTIAL_FILL",
15453
+ commitment,
15454
+ userOpHash: result.userOpHash,
15455
+ selectedSolver: result.solverAddress,
15456
+ transactionHash: result.txnHash,
15457
+ filledAssets,
15458
+ totalFilledAssets,
15459
+ remainingAssets
15460
+ },
15461
+ done: fullyFilled,
15462
+ totalFilledAssets,
15463
+ remainingAssets
15464
+ };
15465
+ }
15466
+ return { update: null, done: false, totalFilledAssets, remainingAssets };
15467
+ }
15468
+ /**
15469
+ * Executes an intent order by racing bid polling against the order's
15470
+ * block deadline. Yields status updates at each lifecycle stage.
15471
+ *
15472
+ * **Same-chain:** `AWAITING_BIDS` → `BIDS_RECEIVED` → `BID_SELECTED`
15473
+ * → (`FILLED` | `PARTIAL_FILL`)* → (`FILLED` | `EXPIRED`)
15474
+ *
15475
+ * **Cross-chain:** `AWAITING_BIDS` → `BIDS_RECEIVED` → `BID_SELECTED`
15476
+ * (terminates — settlement is confirmed async via Hyperbridge)
15477
+ */
15478
+ async *executeOrder(options) {
15479
+ const { order, sessionPrivateKey, minBids = 1, pollIntervalMs = DEFAULT_POLL_INTERVAL, solver } = options;
15480
+ const commitment = order.id;
15481
+ const isSameChain = order.source === order.destination;
15482
+ if (!this.ctx.intentsCoprocessor) {
15483
+ yield { status: "FAILED", error: "IntentsCoprocessor required for order execution" };
15484
+ return;
15485
+ }
15486
+ if (!this.ctx.bundlerUrl) {
15487
+ yield { status: "FAILED", error: "Bundler URL not configured" };
15488
+ return;
15489
+ }
15490
+ const usedUserOps = await this.loadUsedUserOps(commitment);
15491
+ const userOpHashKey = this.createUserOpHasher(order);
15344
15492
  const targetAssets = order.output.assets.map((a) => ({ token: a.token, amount: a.amount }));
15345
15493
  let totalFilledAssets = order.output.assets.map((a) => ({ token: a.token, amount: 0n }));
15346
15494
  let remainingAssets = order.output.assets.map((a) => ({ token: a.token, amount: a.amount }));
15495
+ const executionStream = this.executionStream({
15496
+ order,
15497
+ sessionPrivateKey,
15498
+ commitment,
15499
+ minBids,
15500
+ pollIntervalMs,
15501
+ solver,
15502
+ usedUserOps,
15503
+ userOpHashKey,
15504
+ targetAssets,
15505
+ totalFilledAssets,
15506
+ remainingAssets
15507
+ });
15508
+ const deadlineTimeout = this.deadlineStream(order.deadline, commitment);
15509
+ const combined = mergeRace(deadlineTimeout, executionStream);
15510
+ for await (const update of combined) {
15511
+ yield update;
15512
+ if (update.status === "EXPIRED" || update.status === "FILLED") return;
15513
+ if (update.status === "BID_SELECTED" && !isSameChain) return;
15514
+ }
15515
+ }
15516
+ /**
15517
+ * Core execution loop that polls for bids, submits UserOperations,
15518
+ * and tracks fill progress. Yields between each poll iteration so
15519
+ * that `mergeRace` can interleave the deadline stream.
15520
+ */
15521
+ async *executionStream(params) {
15522
+ const {
15523
+ order,
15524
+ sessionPrivateKey,
15525
+ commitment,
15526
+ minBids,
15527
+ pollIntervalMs,
15528
+ solver,
15529
+ usedUserOps,
15530
+ userOpHashKey,
15531
+ targetAssets
15532
+ } = params;
15533
+ let { totalFilledAssets, remainingAssets } = params;
15534
+ const solverLockStartTime = Date.now();
15535
+ yield { status: "AWAITING_BIDS", commitment, totalFilledAssets, remainingAssets };
15347
15536
  try {
15348
15537
  while (true) {
15349
- const currentBlock = await this.ctx.dest.client.getBlockNumber();
15350
- if (currentBlock >= order.deadline) {
15351
- const isPartiallyFilled = totalFilledAssets.some((a) => a.amount > 0n);
15352
- const deadlineError = `Order deadline reached (block ${currentBlock} >= ${order.deadline})`;
15353
- if (isPartiallyFilled) {
15354
- yield {
15355
- status: "PARTIAL_FILL_EXHAUSTED",
15356
- commitment,
15357
- totalFilledAssets,
15358
- remainingAssets,
15359
- error: deadlineError
15360
- };
15361
- } else {
15362
- yield { status: "FAILED", commitment, error: deadlineError };
15363
- }
15364
- return;
15365
- }
15366
- yield { status: "AWAITING_BIDS", commitment, totalFilledAssets, remainingAssets };
15367
- const startTime = Date.now();
15368
- let bids = [];
15369
- let solverLockExpired = false;
15370
- while (Date.now() - startTime < bidTimeoutMs) {
15371
- try {
15372
- const fetchedBids = await this.ctx.intentsCoprocessor.getBidsForOrder(commitment);
15373
- if (solver) {
15374
- const { address, timeoutMs } = solver;
15375
- const solverLockActive = Date.now() - startTime < timeoutMs;
15376
- if (!solverLockActive) solverLockExpired = true;
15377
- bids = solverLockActive ? fetchedBids.filter((bid) => bid.userOp.sender.toLowerCase() === address.toLowerCase()) : fetchedBids;
15378
- } else {
15379
- bids = fetchedBids;
15380
- }
15381
- if (bids.length >= minBids) {
15382
- break;
15383
- }
15384
- } catch {
15385
- }
15538
+ let freshBids;
15539
+ try {
15540
+ const bids = await this.fetchBids({ commitment, solver, solverLockStartTime });
15541
+ freshBids = bids.filter((bid) => !usedUserOps.has(userOpHashKey(bid.userOp)));
15542
+ } catch {
15386
15543
  await sleep(pollIntervalMs);
15544
+ continue;
15387
15545
  }
15388
- const freshBids = bids.filter((bid) => {
15389
- const key = userOpHashKey(bid.userOp);
15390
- return !usedUserOps.has(key);
15391
- });
15392
- if (freshBids.length === 0) {
15393
- const isPartiallyFilled = totalFilledAssets.some((a) => a.amount > 0n);
15394
- const solverClause = solver && !solverLockExpired ? ` for requested solver ${solver.address}` : "";
15395
- const noBidsError = isPartiallyFilled ? `No new bids${solverClause} after partial fill` : `No new bids${solverClause} available within ${bidTimeoutMs}ms timeout`;
15396
- if (isPartiallyFilled) {
15397
- yield {
15398
- status: "PARTIAL_FILL_EXHAUSTED",
15399
- commitment,
15400
- totalFilledAssets,
15401
- remainingAssets,
15402
- error: noBidsError
15403
- };
15404
- } else {
15405
- yield { status: "FAILED", commitment, error: noBidsError };
15406
- }
15407
- return;
15546
+ if (freshBids.length < minBids) {
15547
+ await sleep(pollIntervalMs);
15548
+ continue;
15408
15549
  }
15409
15550
  yield { status: "BIDS_RECEIVED", commitment, bidCount: freshBids.length, bids: freshBids };
15410
- let result;
15551
+ let submitResult;
15411
15552
  try {
15412
- result = await this.bidManager.selectBid(order, freshBids, sessionPrivateKey);
15553
+ submitResult = await this.submitBid({
15554
+ order,
15555
+ freshBids,
15556
+ sessionPrivateKey,
15557
+ commitment,
15558
+ usedUserOps,
15559
+ userOpHashKey
15560
+ });
15413
15561
  } catch (err) {
15414
15562
  yield {
15415
15563
  status: "FAILED",
@@ -15418,76 +15566,29 @@ var OrderExecutor = class {
15418
15566
  remainingAssets,
15419
15567
  error: `Failed to select bid and submit: ${err instanceof Error ? err.message : String(err)}`
15420
15568
  };
15421
- return;
15569
+ await sleep(pollIntervalMs);
15570
+ continue;
15422
15571
  }
15423
- const usedKey = userOpHashKey(result.userOp);
15424
- usedUserOps.add(usedKey);
15425
- await persistUsedUserOps();
15426
15572
  yield {
15427
15573
  status: "BID_SELECTED",
15428
15574
  commitment,
15429
- selectedSolver: result.solverAddress,
15430
- userOpHash: result.userOpHash,
15431
- userOp: result.userOp
15575
+ selectedSolver: submitResult.solverAddress,
15576
+ userOpHash: submitResult.userOpHash,
15577
+ userOp: submitResult.userOp,
15578
+ transactionHash: submitResult.txnHash
15432
15579
  };
15433
- yield {
15434
- status: "USEROP_SUBMITTED",
15580
+ const fill = this.processFillResult(
15581
+ submitResult,
15435
15582
  commitment,
15436
- userOpHash: result.userOpHash,
15437
- selectedSolver: result.solverAddress,
15438
- transactionHash: result.txnHash
15439
- };
15440
- if (!isSameChain) {
15441
- return;
15442
- }
15443
- if (result.fillStatus === "full") {
15444
- totalFilledAssets = targetAssets.map((a) => ({ token: a.token, amount: a.amount }));
15445
- remainingAssets = targetAssets.map((a) => ({ token: a.token, amount: 0n }));
15446
- yield {
15447
- status: "FILLED",
15448
- commitment,
15449
- userOpHash: result.userOpHash,
15450
- selectedSolver: result.solverAddress,
15451
- transactionHash: result.txnHash,
15452
- totalFilledAssets,
15453
- remainingAssets
15454
- };
15455
- return;
15456
- }
15457
- if (result.fillStatus === "partial") {
15458
- const filledAssets = result.filledAssets ?? [];
15459
- for (const filled of filledAssets) {
15460
- const entry = totalFilledAssets.find((a) => a.token === filled.token);
15461
- if (entry) entry.amount += filled.amount;
15462
- }
15463
- remainingAssets = targetAssets.map((target) => {
15464
- const filled = totalFilledAssets.find((a) => a.token === target.token);
15465
- const filledAmt = filled?.amount ?? 0n;
15466
- return { token: target.token, amount: filledAmt >= target.amount ? 0n : target.amount - filledAmt };
15467
- });
15468
- const fullyFilled = remainingAssets.every((a) => a.amount === 0n);
15469
- if (fullyFilled) {
15470
- yield {
15471
- status: "FILLED",
15472
- commitment,
15473
- userOpHash: result.userOpHash,
15474
- selectedSolver: result.solverAddress,
15475
- transactionHash: result.txnHash,
15476
- totalFilledAssets,
15477
- remainingAssets
15478
- };
15479
- return;
15480
- }
15481
- yield {
15482
- status: "PARTIAL_FILL",
15483
- commitment,
15484
- userOpHash: result.userOpHash,
15485
- selectedSolver: result.solverAddress,
15486
- transactionHash: result.txnHash,
15487
- filledAssets,
15488
- totalFilledAssets,
15489
- remainingAssets
15490
- };
15583
+ targetAssets,
15584
+ totalFilledAssets,
15585
+ remainingAssets
15586
+ );
15587
+ totalFilledAssets = fill.totalFilledAssets;
15588
+ remainingAssets = fill.remainingAssets;
15589
+ if (fill.update) {
15590
+ yield fill.update;
15591
+ if (fill.done) return;
15491
15592
  }
15492
15593
  }
15493
15594
  } catch (err) {
@@ -15507,6 +15608,7 @@ var OrderCanceller = class {
15507
15608
  constructor(ctx) {
15508
15609
  this.ctx = ctx;
15509
15610
  }
15611
+ ctx;
15510
15612
  /**
15511
15613
  * Returns both the native token cost and the relayer fee for cancelling an
15512
15614
  * order. Frontends can use `relayerFee` to approve the ERC-20 spend before
@@ -15976,6 +16078,8 @@ var BidManager = class {
15976
16078
  this.ctx = ctx;
15977
16079
  this.crypto = crypto;
15978
16080
  }
16081
+ ctx;
16082
+ crypto;
15979
16083
  /**
15980
16084
  * Constructs a signed `PackedUserOperation` that a solver can submit to the
15981
16085
  * Hyperbridge coprocessor as a bid to fill an order.
@@ -18055,6 +18159,8 @@ var GasEstimator = class {
18055
18159
  this.ctx = ctx;
18056
18160
  this.crypto = crypto;
18057
18161
  }
18162
+ ctx;
18163
+ crypto;
18058
18164
  /**
18059
18165
  * Estimates the gas cost for a solver to fill the given order and returns
18060
18166
  * a structured estimate with individual gas components and total costs in
@@ -18089,11 +18195,13 @@ var GasEstimator = class {
18089
18195
  const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(destStateMachineId);
18090
18196
  const chainId = BigInt(Number.parseInt(destStateMachineId.split("-")[1]));
18091
18197
  const totalEthValue = order.output.assets.filter((output) => bytes32ToBytes20(output.token) === ADDRESS_ZERO2).reduce((sum, output) => sum + output.amount, 0n);
18092
- const [sourceFeeToken, destFeeToken, gasPrice] = await Promise.all([
18198
+ const [sourceFeeToken, destFeeToken, gasPrice, latestBlock] = await Promise.all([
18093
18199
  getFeeToken(this.ctx, this.ctx.source.config.stateMachineId, this.ctx.source),
18094
18200
  getFeeToken(this.ctx, this.ctx.dest.config.stateMachineId, this.ctx.dest),
18095
- this.ctx.dest.client.getGasPrice()
18201
+ this.ctx.dest.client.getGasPrice(),
18202
+ this.ctx.dest.client.getBlock({ blockTag: "latest" })
18096
18203
  ]);
18204
+ const baseFeePerGas = latestBlock.baseFeePerGas ?? gasPrice;
18097
18205
  const feeTokenAsBytes32 = bytes20ToBytes32(destFeeToken.address);
18098
18206
  const assetsForOverrides = [...order.output.assets];
18099
18207
  if (!assetsForOverrides.some((asset) => asset.token.toLowerCase() === feeTokenAsBytes32.toLowerCase())) {
@@ -18141,6 +18249,7 @@ var GasEstimator = class {
18141
18249
  if (this.ctx.bundlerUrl) {
18142
18250
  try {
18143
18251
  const callData = this.crypto.encodeERC7821Execute([
18252
+ ...params.prependCalls ?? [],
18144
18253
  { target: intentGatewayV2Address, value: totalNativeValue, data: fillOrderCalldata }
18145
18254
  ]);
18146
18255
  const accountGasLimits = this.crypto.packGasLimits(100000n, callGasLimit);
@@ -18182,7 +18291,9 @@ var GasEstimator = class {
18182
18291
  sessionSignature
18183
18292
  ]);
18184
18293
  const bundlerUserOp = this.crypto.prepareBundlerCall(preliminaryUserOp);
18185
- const isPimlico = this.ctx.bundlerUrl.toLowerCase().includes("pimlico.io");
18294
+ const bundlerUrlLower = this.ctx.bundlerUrl.toLowerCase();
18295
+ const isPimlico = bundlerUrlLower.includes("pimlico.io");
18296
+ const isAlchemy = bundlerUrlLower.includes("alchemy.com");
18186
18297
  const bundlerRequests = [
18187
18298
  {
18188
18299
  method: BundlerMethod.ETH_ESTIMATE_USER_OPERATION_GAS,
@@ -18195,14 +18306,24 @@ var GasEstimator = class {
18195
18306
  params: []
18196
18307
  });
18197
18308
  }
18309
+ if (isAlchemy) {
18310
+ bundlerRequests.push({
18311
+ method: BundlerMethod.RUNDLER_MAX_PRIORITY_FEE_PER_GAS,
18312
+ params: []
18313
+ });
18314
+ }
18198
18315
  let gasEstimate;
18199
18316
  let pimlicoGasPrices = null;
18317
+ let alchemyMaxPriorityFee = null;
18200
18318
  try {
18201
18319
  const batchResults = await this.crypto.sendBundlerBatch(bundlerRequests);
18202
18320
  gasEstimate = batchResults[0];
18203
18321
  if (isPimlico && batchResults.length > 1) {
18204
18322
  pimlicoGasPrices = batchResults[1];
18205
18323
  }
18324
+ if (isAlchemy && batchResults.length > 1) {
18325
+ alchemyMaxPriorityFee = batchResults[1];
18326
+ }
18206
18327
  } catch {
18207
18328
  gasEstimate = await this.crypto.sendBundler(
18208
18329
  BundlerMethod.ETH_ESTIMATE_USER_OPERATION_GAS,
@@ -18221,6 +18342,14 @@ var GasEstimator = class {
18221
18342
  maxPriorityFeePerGas = pimMaxPriorityFeePerGas + pimMaxPriorityFeePerGas * BigInt(priorityFeeBumpPercent) / 100n;
18222
18343
  }
18223
18344
  }
18345
+ if (alchemyMaxPriorityFee) {
18346
+ const rundlerPriorityFee = BigInt(alchemyMaxPriorityFee);
18347
+ const isArbitrum = chainId === 42161n;
18348
+ const alchemyPrioBump = isArbitrum ? 0n : 25n;
18349
+ maxPriorityFeePerGas = rundlerPriorityFee + rundlerPriorityFee * alchemyPrioBump / 100n;
18350
+ const bufferedBaseFee = baseFeePerGas + baseFeePerGas * 50n / 100n;
18351
+ maxFeePerGas = bufferedBaseFee + maxPriorityFeePerGas;
18352
+ }
18224
18353
  } catch (e) {
18225
18354
  console.warn("Bundler gas estimation failed, using fallback values:", e);
18226
18355
  }
@@ -18453,6 +18582,7 @@ var OrderStatusChecker = class {
18453
18582
  constructor(ctx) {
18454
18583
  this.ctx = ctx;
18455
18584
  }
18585
+ ctx;
18456
18586
  /**
18457
18587
  * Checks if a V2 order has been filled by reading the commitment storage slot on the destination chain.
18458
18588
  *
@@ -18634,7 +18764,7 @@ var IntentGateway = class _IntentGateway {
18634
18764
  * The caller must sign the transaction and pass it back via `gen.next(signedTx)`.
18635
18765
  * 3. Yields `ORDER_PLACED` with the finalised order and transaction hash once
18636
18766
  * the `OrderPlaced` event is confirmed.
18637
- * 4. Delegates to {@link OrderExecutor.executeIntentOrder} and forwards all
18767
+ * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
18638
18768
  * subsequent status updates until the order is filled, exhausted, or fails.
18639
18769
  *
18640
18770
  * @param order - The order to place and execute. `order.fees` may be 0; it
@@ -18645,7 +18775,6 @@ var IntentGateway = class _IntentGateway {
18645
18775
  * - `maxPriorityFeePerGasBumpPercent` — bump % for the priority fee estimate (default 8).
18646
18776
  * - `maxFeePerGasBumpPercent` — bump % for the max fee estimate (default 10).
18647
18777
  * - `minBids` — minimum bids to collect before selecting (default 1).
18648
- * - `bidTimeoutMs` — how long to poll for bids before giving up (default 60 000 ms).
18649
18778
  * - `pollIntervalMs` — interval between bid-polling attempts.
18650
18779
  * @yields {@link IntentOrderStatusUpdate} at each lifecycle stage.
18651
18780
  * @throws If the `placeOrder` generator behaves unexpectedly, or if gas
@@ -18678,11 +18807,10 @@ var IntentGateway = class _IntentGateway {
18678
18807
  }
18679
18808
  const { order: finalizedOrder, receipt: placementReceipt } = placeOrderSecond.value;
18680
18809
  yield { status: "ORDER_PLACED", order: finalizedOrder, receipt: placementReceipt };
18681
- for await (const status of this.orderExecutor.executeIntentOrder({
18810
+ for await (const status of this.orderExecutor.executeOrder({
18682
18811
  order: finalizedOrder,
18683
18812
  sessionPrivateKey,
18684
18813
  minBids: options?.minBids,
18685
- bidTimeoutMs: options?.bidTimeoutMs,
18686
18814
  pollIntervalMs: options?.pollIntervalMs,
18687
18815
  solver: options?.solver
18688
18816
  })) {
@@ -18690,6 +18818,52 @@ var IntentGateway = class _IntentGateway {
18690
18818
  }
18691
18819
  return;
18692
18820
  }
18821
+ /**
18822
+ * Validates that an order has the minimum fields required for post-placement
18823
+ * resume (i.e. it was previously placed and has an on-chain identity).
18824
+ *
18825
+ * @throws If `order.id` or `order.session` is missing or zero-valued.
18826
+ */
18827
+ assertOrderCanResume(order) {
18828
+ if (!order.id) {
18829
+ throw new Error("Cannot resume execution without order.id");
18830
+ }
18831
+ if (!order.session || order.session === ADDRESS_ZERO2) {
18832
+ throw new Error("Cannot resume execution without order.session");
18833
+ }
18834
+ }
18835
+ /**
18836
+ * Resumes execution of a previously placed order.
18837
+ *
18838
+ * Use this method after an app restart or crash to pick up where
18839
+ * {@link execute} left off. The order must already be placed on-chain
18840
+ * (i.e. it must have a valid `id` and `session`).
18841
+ *
18842
+ * Internally delegates to {@link OrderExecutor.executeOrder} and
18843
+ * yields the same status updates as the execution phase of {@link execute}:
18844
+ * `AWAITING_BIDS`, `BIDS_RECEIVED`, `BID_SELECTED`,
18845
+ * `FILLED`, `PARTIAL_FILL`, `EXPIRED`, or `FAILED`.
18846
+ *
18847
+ * Callers may check {@link isOrderFilled} or {@link isOrderRefunded} before
18848
+ * calling this method to avoid resuming an already-terminal order.
18849
+ *
18850
+ * @param order - A previously placed order with a valid `id` and `session`.
18851
+ * @param options - Optional tuning parameters for bid collection and execution.
18852
+ * @yields {@link IntentOrderStatusUpdate} at each execution stage.
18853
+ * @throws If the order is missing required fields for resumption.
18854
+ */
18855
+ async *resume(order, options) {
18856
+ this.assertOrderCanResume(order);
18857
+ for await (const status of this.orderExecutor.executeOrder({
18858
+ order,
18859
+ sessionPrivateKey: options?.sessionPrivateKey,
18860
+ minBids: options?.minBids,
18861
+ pollIntervalMs: options?.pollIntervalMs,
18862
+ solver: options?.solver
18863
+ })) {
18864
+ yield status;
18865
+ }
18866
+ }
18693
18867
  /**
18694
18868
  * Returns both the native token cost and the relayer fee for cancelling an
18695
18869
  * order. Use `relayerFee` to approve the ERC-20 spend before submitting.