@hyperbridge/sdk 2.8.13 → 2.8.16

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.
@@ -2,16 +2,16 @@ import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { TextDecoder as TextDecoder$1, TextEncoder as TextEncoder$1 } from 'util';
4
4
  import { createConsola, LogLevels } from 'consola';
5
- import { defineChain, keccak256, toHex, createPublicClient, http, hexToBytes, toFunctionSelector, bytesToHex, encodeFunctionData, erc20Abi, bytesToBigInt, pad, toBytes, numberToBytes, decodeAbiParameters, getAddress, encodeAbiParameters, isHex, isAddress, maxUint256, parseAbiParameters, encodePacked, parseAbiItem, concat, decodeFunctionData, hexToString as hexToString$1, decodeEventLog, parseEventLogs, parseUnits, concatHex, zeroAddress, decodeFunctionResult, formatUnits } from 'viem';
5
+ import { defineChain, keccak256, toHex, parseAbi, createPublicClient, http, hexToBytes, toFunctionSelector, bytesToHex, encodeFunctionData, erc20Abi, bytesToBigInt, pad, toBytes, numberToBytes, getAddress, encodeAbiParameters, isHex, isAddress, maxUint256, parseAbiParameters, encodePacked, parseAbiItem, concat, decodeFunctionData, decodeAbiParameters, hexToString as hexToString$1, decodeEventLog, parseEventLogs, parseUnits, concatHex, formatUnits, toEventSelector } from 'viem';
6
6
  import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimism, polygonAmoy, polygon, base, arbitrum, bsc, mainnet, sepolia, gnosisChiado, bscTestnet, tron, unichain } from 'viem/chains';
7
- import { TronWeb } from 'tronweb';
7
+ import { base58Decode, keccakAsU8a, decodeAddress, keccakAsHex, xxhashAsU8a, blake2AsU8a } from '@polkadot/util-crypto';
8
8
  import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
9
9
  import { match } from 'ts-pattern';
10
10
  import { WsProvider, ApiPromise, Keyring, HttpProvider } from '@polkadot/api';
11
11
  import { Struct, Vector, u8, Bytes, Enum, Tuple, _void, u64, u32, Option, bool, u128 } from 'scale-ts';
12
- import { xxhashAsU8a, keccakAsU8a, decodeAddress, keccakAsHex, blake2AsU8a } from '@polkadot/util-crypto';
13
- import { u8aToHex, u8aConcat, hexToU8a, isHex as isHex$1 } from '@polkadot/util';
12
+ import { hexToU8a, u8aToHex, u8aConcat, isHex as isHex$1 } from '@polkadot/util';
14
13
  import PQueue from 'p-queue';
14
+ import { TronWeb } from 'tronweb';
15
15
  import { hasWindow, isNode, env } from 'std-env';
16
16
  import mergeRace from '@async-generator/merge-race';
17
17
  import { GraphQLClient } from 'graphql-request';
@@ -269,6 +269,7 @@ var OrderStatus = /* @__PURE__ */ ((OrderStatus2) => {
269
269
  OrderStatus2["FILLED"] = "FILLED";
270
270
  OrderStatus2["REDEEMED"] = "REDEEMED";
271
271
  OrderStatus2["REFUNDED"] = "REFUNDED";
272
+ OrderStatus2["CANCELLED"] = "CANCELLED";
272
273
  return OrderStatus2;
273
274
  })(OrderStatus || {});
274
275
  var TeleportStatus = /* @__PURE__ */ ((TeleportStatus2) => {
@@ -283,6 +284,8 @@ var RequestKind = /* @__PURE__ */ ((RequestKind2) => {
283
284
  RequestKind2[RequestKind2["UpdateParams"] = 2] = "UpdateParams";
284
285
  RequestKind2[RequestKind2["SweepDust"] = 3] = "SweepDust";
285
286
  RequestKind2[RequestKind2["RefundEscrow"] = 4] = "RefundEscrow";
287
+ RequestKind2[RequestKind2["Execute"] = 5] = "Execute";
288
+ RequestKind2[RequestKind2["RedeemEscrowPartial"] = 6] = "RedeemEscrowPartial";
286
289
  return RequestKind2;
287
290
  })(RequestKind || {});
288
291
  var IntentOrderStatus = Object.freeze({
@@ -295,6 +298,7 @@ var IntentOrderStatus = Object.freeze({
295
298
  BID_SELECTED: "BID_SELECTED",
296
299
  FILLED: "FILLED",
297
300
  PARTIAL_FILL: "PARTIAL_FILL",
301
+ CANCELLED: "CANCELLED",
298
302
  EXPIRED: "EXPIRED",
299
303
  FAILED: "FAILED"
300
304
  });
@@ -2477,7 +2481,12 @@ var ABI2 = [
2477
2481
  ];
2478
2482
  var handlerV2_default = { ABI: ABI2 };
2479
2483
  function tronAddress(base58) {
2480
- return `0x${TronWeb.address.toHex(base58).slice(2)}`;
2484
+ const decoded = base58Decode(base58);
2485
+ if (decoded.length !== 25 || decoded[0] !== 65) {
2486
+ throw new Error(`Not a Tron base58 address: ${base58}`);
2487
+ }
2488
+ const hex = Array.from(decoded.slice(1, 21), (byte) => byte.toString(16).padStart(2, "0")).join("");
2489
+ return `0x${hex}`;
2481
2490
  }
2482
2491
  var Chains = /* @__PURE__ */ ((Chains2) => {
2483
2492
  Chains2["BSC_CHAPEL"] = "EVM-97";
@@ -2564,11 +2573,13 @@ var chainConfigs = {
2564
2573
  WETH: "0xae13d989dac2f0debff460ac112a837c89baa7cd",
2565
2574
  DAI: "0x1938165569a5463327fb206be06d8d9253aa06b7",
2566
2575
  USDC: "0xA801da100bF16D07F668F4A49E1f71fc54D05177",
2567
- USDT: "0xc043f483373072f7f27420d6e7d7ad269c018e18"
2576
+ USDT: "0xc043f483373072f7f27420d6e7d7ad269c018e18",
2577
+ cNGN: "0x2bbbd701cfc25d37f18127e51df0933566d5778a"
2568
2578
  },
2569
2579
  tokenDecimals: {
2570
2580
  USDC: 18,
2571
- USDT: 18
2581
+ USDT: 18,
2582
+ cNGN: 6
2572
2583
  },
2573
2584
  tokenStorageSlots: {
2574
2585
  USDC: { balanceSlot: 1, allowanceSlot: 2 },
@@ -2585,7 +2596,7 @@ var chainConfigs = {
2585
2596
  UniswapV3Quoter: "0x0000000000000000000000000000000000000000",
2586
2597
  UniswapV4Quoter: "0x0000000000000000000000000000000000000000",
2587
2598
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2588
- SolverAccount: "0x110C7E1814c923127469Ca8939a12dAa70B96a17"
2599
+ SolverAccount: "0x153DB990FE3b761B54ad71D0f1A0987dF11740DB"
2589
2600
  },
2590
2601
  rpcEnvKey: "BSC_CHAPEL",
2591
2602
  defaultRpcUrl: "https://bnb-testnet.api.onfinality.io/public",
@@ -2711,7 +2722,7 @@ var chainConfigs = {
2711
2722
  },
2712
2723
  addresses: {
2713
2724
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2714
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
2725
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
2715
2726
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2716
2727
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2717
2728
  UniswapRouter02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
@@ -2778,7 +2789,7 @@ var chainConfigs = {
2778
2789
  },
2779
2790
  addresses: {
2780
2791
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2781
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
2792
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
2782
2793
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2783
2794
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2784
2795
  UniswapRouter02: "0x10ED43C718714eb63d5aA57B78B54704E256024E",
@@ -2839,7 +2850,7 @@ var chainConfigs = {
2839
2850
  },
2840
2851
  addresses: {
2841
2852
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2842
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
2853
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
2843
2854
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2844
2855
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2845
2856
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2913,7 +2924,7 @@ var chainConfigs = {
2913
2924
  },
2914
2925
  addresses: {
2915
2926
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2916
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
2927
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
2917
2928
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2918
2929
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2919
2930
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2938,7 +2949,6 @@ var chainConfigs = {
2938
2949
  consensusStateId: "ETH0",
2939
2950
  coingeckoId: "base",
2940
2951
  layerZeroEid: 30184,
2941
- uniswapV4Pools: [{ tokens: ["USDC", "cNGN"], fee: 1500, tickSpacing: 30 }],
2942
2952
  erc4626Vaults: [
2943
2953
  { label: "Aave stataUSDC", address: "0xC768c589647798a6EE01A91FdE98EF2ed046DBD6", asset: "USDC" },
2944
2954
  { label: "Yield Bearing cNGN", address: "0xa82A3531021317240Fb32E67f9c7bC091F737D3b", asset: "cNGN" }
@@ -2988,7 +2998,7 @@ var chainConfigs = {
2988
2998
  },
2989
2999
  addresses: {
2990
3000
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2991
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
3001
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
2992
3002
  TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E",
2993
3003
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2994
3004
  UniswapRouter02: "0xd2f9496824951D5237cC71245D659E48d0d5f9E8",
@@ -3063,11 +3073,13 @@ var chainConfigs = {
3063
3073
  //wmatic, change it to wpol
3064
3074
  DAI: "0x0000000000000000000000000000000000000000",
3065
3075
  USDC: "0xBE97E73126D66188d72fbF99029126D0340a7f18",
3066
- USDT: "0x0000000000000000000000000000000000000000"
3076
+ USDT: "0x0000000000000000000000000000000000000000",
3077
+ cNGN: "0xe4ff5d2ae65c10f530c000215029919961e8a758"
3067
3078
  },
3068
3079
  tokenDecimals: {
3069
3080
  USDC: 18,
3070
- USDT: 18
3081
+ USDT: 18,
3082
+ cNGN: 6
3071
3083
  },
3072
3084
  tokenStorageSlots: {
3073
3085
  USDT: { balanceSlot: 0, allowanceSlot: 1 },
@@ -3080,7 +3092,7 @@ var chainConfigs = {
3080
3092
  Calldispatcher: "0x876F1891982E260026630c233A4897160A281Fb8",
3081
3093
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
3082
3094
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
3083
- SolverAccount: "0x110C7E1814c923127469Ca8939a12dAa70B96a17"
3095
+ SolverAccount: "0x153DB990FE3b761B54ad71D0f1A0987dF11740DB"
3084
3096
  },
3085
3097
  rpcEnvKey: "POLYGON_AMOY",
3086
3098
  defaultRpcUrl: "https://rpc-amoy.polygon.technology",
@@ -3104,7 +3116,7 @@ var chainConfigs = {
3104
3116
  },
3105
3117
  addresses: {
3106
3118
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3107
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
3119
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
3108
3120
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3109
3121
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3110
3122
  UniswapRouter02: "0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2",
@@ -3139,7 +3151,7 @@ var chainConfigs = {
3139
3151
  },
3140
3152
  addresses: {
3141
3153
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3142
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
3154
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
3143
3155
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3144
3156
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3145
3157
  UniswapRouter02: "0xB2e26652e4BAd1e56055A051f922E06760cA0BFE",
@@ -3295,7 +3307,7 @@ var chainConfigs = {
3295
3307
  },
3296
3308
  addresses: {
3297
3309
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3298
- SolverAccount: "0x7cb55539d1144F62422099c3FA3405092022c88C",
3310
+ SolverAccount: "0xd5535d4DeB17F050e52B6efda2fDe00435f39279",
3299
3311
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3300
3312
  Calldispatcher: "0xE2C7e576E26E0bE7aC97c6fE925bcDAbD87c4bEd"
3301
3313
  },
@@ -3342,7 +3354,12 @@ var ABI3 = [
3342
3354
  type: "constructor",
3343
3355
  inputs: [
3344
3356
  {
3345
- name: "owner",
3357
+ name: "intrinsic",
3358
+ type: "address",
3359
+ internalType: "address"
3360
+ },
3361
+ {
3362
+ name: "extrinsic",
3346
3363
  type: "address",
3347
3364
  internalType: "address"
3348
3365
  }
@@ -3441,8 +3458,8 @@ var ABI3 = [
3441
3458
  },
3442
3459
  {
3443
3460
  name: "",
3444
- type: "address",
3445
- internalType: "address"
3461
+ type: "uint256",
3462
+ internalType: "uint256"
3446
3463
  }
3447
3464
  ],
3448
3465
  outputs: [
@@ -3456,20 +3473,31 @@ var ABI3 = [
3456
3473
  },
3457
3474
  {
3458
3475
  type: "function",
3459
- name: "_owner",
3460
- inputs: [],
3476
+ name: "_partialFills",
3477
+ inputs: [
3478
+ {
3479
+ name: "",
3480
+ type: "bytes32",
3481
+ internalType: "bytes32"
3482
+ },
3483
+ {
3484
+ name: "",
3485
+ type: "uint256",
3486
+ internalType: "uint256"
3487
+ }
3488
+ ],
3461
3489
  outputs: [
3462
3490
  {
3463
3491
  name: "",
3464
- type: "address",
3465
- internalType: "address"
3492
+ type: "uint256",
3493
+ internalType: "uint256"
3466
3494
  }
3467
3495
  ],
3468
3496
  stateMutability: "view"
3469
3497
  },
3470
3498
  {
3471
3499
  type: "function",
3472
- name: "_partialFills",
3500
+ name: "_protocolFees",
3473
3501
  inputs: [
3474
3502
  {
3475
3503
  name: "",
@@ -3478,19 +3506,31 @@ var ABI3 = [
3478
3506
  },
3479
3507
  {
3480
3508
  name: "",
3481
- type: "bytes32",
3482
- internalType: "bytes32"
3509
+ type: "uint256",
3510
+ internalType: "uint256"
3483
3511
  }
3484
3512
  ],
3485
3513
  outputs: [
3486
3514
  {
3487
- name: "",
3515
+ name: "amount",
3516
+ type: "uint256",
3517
+ internalType: "uint256"
3518
+ },
3519
+ {
3520
+ name: "committed",
3488
3521
  type: "uint256",
3489
3522
  internalType: "uint256"
3490
3523
  }
3491
3524
  ],
3492
3525
  stateMutability: "view"
3493
3526
  },
3527
+ {
3528
+ type: "function",
3529
+ name: "acceptOwnership",
3530
+ inputs: [],
3531
+ outputs: [],
3532
+ stateMutability: "nonpayable"
3533
+ },
3494
3534
  {
3495
3535
  type: "function",
3496
3536
  name: "calculateCommitmentSlotHash",
@@ -3700,6 +3740,19 @@ var ABI3 = [
3700
3740
  ],
3701
3741
  stateMutability: "view"
3702
3742
  },
3743
+ {
3744
+ type: "function",
3745
+ name: "extrinsicModule",
3746
+ inputs: [],
3747
+ outputs: [
3748
+ {
3749
+ name: "",
3750
+ type: "address",
3751
+ internalType: "address"
3752
+ }
3753
+ ],
3754
+ stateMutability: "view"
3755
+ },
3703
3756
  {
3704
3757
  type: "function",
3705
3758
  name: "fillOrder",
@@ -3862,6 +3915,23 @@ var ABI3 = [
3862
3915
  internalType: "uint256"
3863
3916
  }
3864
3917
  ]
3918
+ },
3919
+ {
3920
+ name: "inputs",
3921
+ type: "tuple[]",
3922
+ internalType: "struct TokenInfo[]",
3923
+ components: [
3924
+ {
3925
+ name: "token",
3926
+ type: "bytes32",
3927
+ internalType: "bytes32"
3928
+ },
3929
+ {
3930
+ name: "amount",
3931
+ type: "uint256",
3932
+ internalType: "uint256"
3933
+ }
3934
+ ]
3865
3935
  }
3866
3936
  ]
3867
3937
  }
@@ -3887,51 +3957,63 @@ var ABI3 = [
3887
3957
  name: "initialize",
3888
3958
  inputs: [
3889
3959
  {
3890
- name: "p",
3960
+ name: "init",
3891
3961
  type: "tuple",
3892
- internalType: "struct Params",
3962
+ internalType: "struct InitParams",
3893
3963
  components: [
3894
3964
  {
3895
- name: "host",
3896
- type: "address",
3897
- internalType: "address"
3898
- },
3899
- {
3900
- name: "dispatcher",
3901
- type: "address",
3902
- internalType: "address"
3903
- },
3904
- {
3905
- name: "solverSelection",
3906
- type: "bool",
3907
- internalType: "bool"
3965
+ name: "params",
3966
+ type: "tuple",
3967
+ internalType: "struct Params",
3968
+ components: [
3969
+ {
3970
+ name: "host",
3971
+ type: "address",
3972
+ internalType: "address"
3973
+ },
3974
+ {
3975
+ name: "dispatcher",
3976
+ type: "address",
3977
+ internalType: "address"
3978
+ },
3979
+ {
3980
+ name: "solverSelection",
3981
+ type: "bool",
3982
+ internalType: "bool"
3983
+ },
3984
+ {
3985
+ name: "surplusShareBps",
3986
+ type: "uint256",
3987
+ internalType: "uint256"
3988
+ },
3989
+ {
3990
+ name: "protocolFeeBps",
3991
+ type: "uint256",
3992
+ internalType: "uint256"
3993
+ },
3994
+ {
3995
+ name: "priceOracle",
3996
+ type: "address",
3997
+ internalType: "address"
3998
+ }
3999
+ ]
3908
4000
  },
3909
4001
  {
3910
- name: "surplusShareBps",
3911
- type: "uint256",
3912
- internalType: "uint256"
4002
+ name: "peerChains",
4003
+ type: "bytes[]",
4004
+ internalType: "bytes[]"
3913
4005
  },
3914
4006
  {
3915
- name: "protocolFeeBps",
3916
- type: "uint256",
3917
- internalType: "uint256"
4007
+ name: "relayer",
4008
+ type: "address",
4009
+ internalType: "address"
3918
4010
  },
3919
4011
  {
3920
- name: "priceOracle",
4012
+ name: "owner",
3921
4013
  type: "address",
3922
4014
  internalType: "address"
3923
4015
  }
3924
4016
  ]
3925
- },
3926
- {
3927
- name: "peerChains",
3928
- type: "bytes[]",
3929
- internalType: "bytes[]"
3930
- },
3931
- {
3932
- name: "relayer",
3933
- type: "address",
3934
- internalType: "address"
3935
4017
  }
3936
4018
  ],
3937
4019
  outputs: [],
@@ -3956,12 +4038,25 @@ var ABI3 = [
3956
4038
  ],
3957
4039
  stateMutability: "view"
3958
4040
  },
4041
+ {
4042
+ type: "function",
4043
+ name: "intrinsicModule",
4044
+ inputs: [],
4045
+ outputs: [
4046
+ {
4047
+ name: "",
4048
+ type: "address",
4049
+ internalType: "address"
4050
+ }
4051
+ ],
4052
+ stateMutability: "view"
4053
+ },
3959
4054
  {
3960
4055
  type: "function",
3961
4056
  name: "migrate",
3962
4057
  inputs: [
3963
4058
  {
3964
- name: "relayer",
4059
+ name: "owner_",
3965
4060
  type: "address",
3966
4061
  internalType: "address"
3967
4062
  }
@@ -4036,7 +4131,7 @@ var ABI3 = [
4036
4131
  name: "onGetResponse",
4037
4132
  inputs: [
4038
4133
  {
4039
- name: "incoming",
4134
+ name: "",
4040
4135
  type: "tuple",
4041
4136
  internalType: "struct IncomingGetResponse",
4042
4137
  components: [
@@ -4251,6 +4346,19 @@ var ABI3 = [
4251
4346
  outputs: [],
4252
4347
  stateMutability: "nonpayable"
4253
4348
  },
4349
+ {
4350
+ type: "function",
4351
+ name: "owner",
4352
+ inputs: [],
4353
+ outputs: [
4354
+ {
4355
+ name: "",
4356
+ type: "address",
4357
+ internalType: "address"
4358
+ }
4359
+ ],
4360
+ stateMutability: "view"
4361
+ },
4254
4362
  {
4255
4363
  type: "function",
4256
4364
  name: "params",
@@ -4296,6 +4404,39 @@ var ABI3 = [
4296
4404
  ],
4297
4405
  stateMutability: "view"
4298
4406
  },
4407
+ {
4408
+ type: "function",
4409
+ name: "pause",
4410
+ inputs: [],
4411
+ outputs: [],
4412
+ stateMutability: "nonpayable"
4413
+ },
4414
+ {
4415
+ type: "function",
4416
+ name: "paused",
4417
+ inputs: [],
4418
+ outputs: [
4419
+ {
4420
+ name: "",
4421
+ type: "bool",
4422
+ internalType: "bool"
4423
+ }
4424
+ ],
4425
+ stateMutability: "view"
4426
+ },
4427
+ {
4428
+ type: "function",
4429
+ name: "pendingOwner",
4430
+ inputs: [],
4431
+ outputs: [
4432
+ {
4433
+ name: "",
4434
+ type: "address",
4435
+ internalType: "address"
4436
+ }
4437
+ ],
4438
+ stateMutability: "view"
4439
+ },
4299
4440
  {
4300
4441
  type: "function",
4301
4442
  name: "placeOrder",
@@ -4551,6 +4692,13 @@ var ABI3 = [
4551
4692
  ],
4552
4693
  stateMutability: "view"
4553
4694
  },
4695
+ {
4696
+ type: "function",
4697
+ name: "renounceOwnership",
4698
+ inputs: [],
4699
+ outputs: [],
4700
+ stateMutability: "nonpayable"
4701
+ },
4554
4702
  {
4555
4703
  type: "function",
4556
4704
  name: "select",
@@ -4589,10 +4737,10 @@ var ABI3 = [
4589
4737
  },
4590
4738
  {
4591
4739
  type: "function",
4592
- name: "setRelayer",
4740
+ name: "transferOwnership",
4593
4741
  inputs: [
4594
4742
  {
4595
- name: "relayer",
4743
+ name: "newOwner",
4596
4744
  type: "address",
4597
4745
  internalType: "address"
4598
4746
  }
@@ -4602,19 +4750,8 @@ var ABI3 = [
4602
4750
  },
4603
4751
  {
4604
4752
  type: "function",
4605
- name: "upgradeToAndCall",
4606
- inputs: [
4607
- {
4608
- name: "newImplementation",
4609
- type: "address",
4610
- internalType: "address"
4611
- },
4612
- {
4613
- name: "data",
4614
- type: "bytes",
4615
- internalType: "bytes"
4616
- }
4617
- ],
4753
+ name: "unpause",
4754
+ inputs: [],
4618
4755
  outputs: [],
4619
4756
  stateMutability: "nonpayable"
4620
4757
  },
@@ -4760,6 +4897,12 @@ var ABI3 = [
4760
4897
  indexed: true,
4761
4898
  internalType: "bytes32"
4762
4899
  },
4900
+ {
4901
+ name: "solver",
4902
+ type: "address",
4903
+ indexed: false,
4904
+ internalType: "address"
4905
+ },
4763
4906
  {
4764
4907
  name: "tokens",
4765
4908
  type: "tuple[]",
@@ -4997,39 +5140,77 @@ var ABI3 = [
4997
5140
  },
4998
5141
  {
4999
5142
  type: "event",
5000
- name: "ParamsUpdated",
5143
+ name: "OwnershipTransferStarted",
5001
5144
  inputs: [
5002
5145
  {
5003
- name: "previous",
5004
- type: "tuple",
5005
- indexed: false,
5006
- internalType: "struct Params",
5007
- components: [
5008
- {
5009
- name: "host",
5010
- type: "address",
5011
- internalType: "address"
5012
- },
5013
- {
5014
- name: "dispatcher",
5015
- type: "address",
5016
- internalType: "address"
5017
- },
5018
- {
5019
- name: "solverSelection",
5020
- type: "bool",
5021
- internalType: "bool"
5022
- },
5023
- {
5024
- name: "surplusShareBps",
5025
- type: "uint256",
5026
- internalType: "uint256"
5027
- },
5028
- {
5029
- name: "protocolFeeBps",
5030
- type: "uint256",
5031
- internalType: "uint256"
5032
- },
5146
+ name: "previousOwner",
5147
+ type: "address",
5148
+ indexed: true,
5149
+ internalType: "address"
5150
+ },
5151
+ {
5152
+ name: "newOwner",
5153
+ type: "address",
5154
+ indexed: true,
5155
+ internalType: "address"
5156
+ }
5157
+ ],
5158
+ anonymous: false
5159
+ },
5160
+ {
5161
+ type: "event",
5162
+ name: "OwnershipTransferred",
5163
+ inputs: [
5164
+ {
5165
+ name: "previousOwner",
5166
+ type: "address",
5167
+ indexed: true,
5168
+ internalType: "address"
5169
+ },
5170
+ {
5171
+ name: "newOwner",
5172
+ type: "address",
5173
+ indexed: true,
5174
+ internalType: "address"
5175
+ }
5176
+ ],
5177
+ anonymous: false
5178
+ },
5179
+ {
5180
+ type: "event",
5181
+ name: "ParamsUpdated",
5182
+ inputs: [
5183
+ {
5184
+ name: "previous",
5185
+ type: "tuple",
5186
+ indexed: false,
5187
+ internalType: "struct Params",
5188
+ components: [
5189
+ {
5190
+ name: "host",
5191
+ type: "address",
5192
+ internalType: "address"
5193
+ },
5194
+ {
5195
+ name: "dispatcher",
5196
+ type: "address",
5197
+ internalType: "address"
5198
+ },
5199
+ {
5200
+ name: "solverSelection",
5201
+ type: "bool",
5202
+ internalType: "bool"
5203
+ },
5204
+ {
5205
+ name: "surplusShareBps",
5206
+ type: "uint256",
5207
+ internalType: "uint256"
5208
+ },
5209
+ {
5210
+ name: "protocolFeeBps",
5211
+ type: "uint256",
5212
+ internalType: "uint256"
5213
+ },
5033
5214
  {
5034
5215
  name: "priceOracle",
5035
5216
  type: "address",
@@ -5135,46 +5316,73 @@ var ABI3 = [
5135
5316
  },
5136
5317
  {
5137
5318
  type: "event",
5138
- name: "RelayerUpdated",
5319
+ name: "Paused",
5139
5320
  inputs: [
5140
5321
  {
5141
- name: "previous",
5322
+ name: "account",
5142
5323
  type: "address",
5143
5324
  indexed: false,
5144
5325
  internalType: "address"
5326
+ }
5327
+ ],
5328
+ anonymous: false
5329
+ },
5330
+ {
5331
+ type: "event",
5332
+ name: "ProtocolFeeRefunded",
5333
+ inputs: [
5334
+ {
5335
+ name: "commitment",
5336
+ type: "bytes32",
5337
+ indexed: true,
5338
+ internalType: "bytes32"
5145
5339
  },
5146
5340
  {
5147
- name: "current",
5341
+ name: "token",
5148
5342
  type: "address",
5149
- indexed: false,
5343
+ indexed: true,
5150
5344
  internalType: "address"
5345
+ },
5346
+ {
5347
+ name: "amount",
5348
+ type: "uint256",
5349
+ indexed: false,
5350
+ internalType: "uint256"
5151
5351
  }
5152
5352
  ],
5153
5353
  anonymous: false
5154
5354
  },
5155
5355
  {
5156
5356
  type: "event",
5157
- name: "Upgraded",
5357
+ name: "RelayerUpdated",
5158
5358
  inputs: [
5159
5359
  {
5160
- name: "implementation",
5360
+ name: "previous",
5361
+ type: "address",
5362
+ indexed: false,
5363
+ internalType: "address"
5364
+ },
5365
+ {
5366
+ name: "current",
5161
5367
  type: "address",
5162
- indexed: true,
5368
+ indexed: false,
5163
5369
  internalType: "address"
5164
5370
  }
5165
5371
  ],
5166
5372
  anonymous: false
5167
5373
  },
5168
5374
  {
5169
- type: "error",
5170
- name: "AddressEmptyCode",
5375
+ type: "event",
5376
+ name: "Unpaused",
5171
5377
  inputs: [
5172
5378
  {
5173
- name: "target",
5379
+ name: "account",
5174
5380
  type: "address",
5381
+ indexed: false,
5175
5382
  internalType: "address"
5176
5383
  }
5177
- ]
5384
+ ],
5385
+ anonymous: false
5178
5386
  },
5179
5387
  {
5180
5388
  type: "error",
@@ -5210,28 +5418,17 @@ var ABI3 = [
5210
5418
  },
5211
5419
  {
5212
5420
  type: "error",
5213
- name: "ERC1967InvalidImplementation",
5214
- inputs: [
5215
- {
5216
- name: "implementation",
5217
- type: "address",
5218
- internalType: "address"
5219
- }
5220
- ]
5221
- },
5222
- {
5223
- type: "error",
5224
- name: "ERC1967NonPayable",
5421
+ name: "EnforcedPause",
5225
5422
  inputs: []
5226
5423
  },
5227
5424
  {
5228
5425
  type: "error",
5229
- name: "Expired",
5426
+ name: "ExpectedPause",
5230
5427
  inputs: []
5231
5428
  },
5232
5429
  {
5233
5430
  type: "error",
5234
- name: "FailedCall",
5431
+ name: "Expired",
5235
5432
  inputs: []
5236
5433
  },
5237
5434
  {
@@ -5274,11 +5471,43 @@ var ABI3 = [
5274
5471
  name: "NotInitializing",
5275
5472
  inputs: []
5276
5473
  },
5474
+ {
5475
+ type: "error",
5476
+ name: "OwnableInvalidOwner",
5477
+ inputs: [
5478
+ {
5479
+ name: "owner",
5480
+ type: "address",
5481
+ internalType: "address"
5482
+ }
5483
+ ]
5484
+ },
5485
+ {
5486
+ type: "error",
5487
+ name: "OwnableUnauthorizedAccount",
5488
+ inputs: [
5489
+ {
5490
+ name: "account",
5491
+ type: "address",
5492
+ internalType: "address"
5493
+ }
5494
+ ]
5495
+ },
5277
5496
  {
5278
5497
  type: "error",
5279
5498
  name: "PartialFillNotAllowed",
5280
5499
  inputs: []
5281
5500
  },
5501
+ {
5502
+ type: "error",
5503
+ name: "RateBelowOrder",
5504
+ inputs: []
5505
+ },
5506
+ {
5507
+ type: "error",
5508
+ name: "RateFillTooSmall",
5509
+ inputs: []
5510
+ },
5282
5511
  {
5283
5512
  type: "error",
5284
5513
  name: "ReentrancyGuardReentrantCall",
@@ -5506,9 +5735,6 @@ var ChainConfigService = class {
5506
5735
  getUniswapV4StateViewAddress(chain) {
5507
5736
  return this.getConfig(chain)?.addresses.UniswapV4StateView ?? "0x";
5508
5737
  }
5509
- getUniswapV4PoolConfigs(chain) {
5510
- return this.getConfig(chain)?.uniswapV4Pools ?? [];
5511
- }
5512
5738
  getPermit2Address(chain) {
5513
5739
  return this.getConfig(chain)?.addresses.Permit2 ?? "0x";
5514
5740
  }
@@ -8245,9 +8471,7 @@ Failed HTTP Request: ${JSON.stringify(
8245
8471
  };
8246
8472
 
8247
8473
  // src/chains/intentsCoprocessor.ts
8248
- var SYSTEM_EVENTS_KEY = u8aToHex(u8aConcat(xxhashAsU8a("System", 128), xxhashAsU8a("Events", 128)));
8249
8474
  var OFFCHAIN_BID_PREFIX = new TextEncoder().encode("intents::bid::");
8250
- var OFFCHAIN_PHANTOM_PREFIX = new TextEncoder().encode("intents::phantom::order::");
8251
8475
  var HYPERBRIDGE_TYPES_BUNDLE = {
8252
8476
  spec: {
8253
8477
  nexus: { hasher: keccakAsU8a },
@@ -8257,11 +8481,7 @@ var HYPERBRIDGE_TYPES_BUNDLE = {
8257
8481
  var BASE_TIP = 1000000000n;
8258
8482
  var HTTP_CONNECT_TIMEOUT_MS = 2e4;
8259
8483
  var INCLUSION_TIMEOUT_MS = 2e4;
8260
- var PHANTOM_POLL_INTERVAL_MS = 15e3;
8261
- var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
8262
8484
  var DEFAULT_RPC_MAX_RPS = 8;
8263
- var DEFAULT_MAX_BLOCKS_PER_POLL = 10;
8264
- var MAX_RATE_LIMIT_BACKOFF_TICKS = 8;
8265
8485
  var rpcLimiters = /* @__PURE__ */ new Map();
8266
8486
  function limiterFor(httpUrl) {
8267
8487
  const key = new URL(httpUrl).origin;
@@ -8277,45 +8497,6 @@ function configuredRpcMaxRps() {
8277
8497
  const parsed = raw === void 0 ? Number.NaN : Number(raw);
8278
8498
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_RPC_MAX_RPS;
8279
8499
  }
8280
- function isRateLimited(err) {
8281
- const message = err instanceof Error ? err.message : String(err);
8282
- return message.includes("[429]") || /too many requests/i.test(message);
8283
- }
8284
- function isMethodUnavailable(err) {
8285
- const message = err instanceof Error ? err.message : String(err);
8286
- const code = err?.code;
8287
- return code === -32601 || /method not found|unsafe to be called externally/i.test(message);
8288
- }
8289
- var EventDecodeError = class extends Error {
8290
- };
8291
- function phantomOrdersFrom(records) {
8292
- if (records == null || typeof records[Symbol.iterator] !== "function") {
8293
- throw new EventDecodeError(`Expected a decoded event vector, got ${typeof records}`);
8294
- }
8295
- const orders = [];
8296
- for (const record of records) {
8297
- if (typeof record !== "object" || record === null || !("event" in record)) {
8298
- throw new EventDecodeError(
8299
- "system.events did not decode to event records \u2014 the storage entry's metadata was probably not carried through, leaving the value as raw bytes"
8300
- );
8301
- }
8302
- const { event } = record;
8303
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8304
- const [commitment, chain, createdAt, legs] = event.data;
8305
- orders.push({
8306
- commitment: commitment.toHex(),
8307
- chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8308
- createdAt: createdAt.toNumber(),
8309
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
8310
- legs: legs.map((leg) => ({
8311
- tokenA: leg.tokenA.toHex(),
8312
- tokenB: leg.tokenB.toHex(),
8313
- standardAmount: BigInt(leg.standardAmount.toString())
8314
- }))
8315
- });
8316
- }
8317
- return orders;
8318
- }
8319
8500
  function rejectAfter(ms, message) {
8320
8501
  return new Promise((_resolve, reject) => {
8321
8502
  const timer = setTimeout(() => reject(new Error(message)), ms);
@@ -8387,19 +8568,16 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8387
8568
  ownsConnection;
8388
8569
  /** Cached result of whether the node exposes intents_* RPC methods */
8389
8570
  hasIntentsRpc = null;
8390
- /** The pallet's phantom timings, read once. Cleared on failure so the read retries. */
8391
- phantomTimingsRead = null;
8392
8571
  /** The HTTP-backed api, connected on first use. Cleared after a failed attempt so it retries. */
8393
8572
  httpApi = null;
8394
- /** Last runtime version read from the node, for {@link confirmedRuntimeVersion} to compare against. */
8395
- lastRuntimeVersion;
8396
- /** Set once the node refuses `state_queryStorage`, so the poll stops asking for it. */
8397
- rangeQueryUnavailable = false;
8398
8573
  // Serialises every extrinsic submission on this instance's substrate account. All submit/retract
8399
- // methods funnel through signAndSendExtrinsic, each using the API's auto-nonce; fired in parallel
8400
- // (bids for orders on different chains, or several phantom orders in one interval) they would grab
8401
- // the same nonce and all but one would fail. Concurrency 1 sequences them.
8574
+ // methods funnel through signAndSendExtrinsic; fired in parallel (bids for orders on different
8575
+ // chains, or several bids on one order) they would grab the same nonce and all but one would
8576
+ // fail. Concurrency 1 sequences them, and {@link nextNonce} keeps consecutive submissions off
8577
+ // each other's nonce even while the first is still in the pool.
8402
8578
  submissionQueue = new PQueue({ concurrency: 1 });
8579
+ /** The highest nonce this instance has put into the pool, so the next submission clears it. */
8580
+ lastPooledNonce;
8403
8581
  /**
8404
8582
  * Creates and connects an IntentsCoprocessor to a Hyperbridge node.
8405
8583
  * This creates and manages its own API connection.
@@ -8461,10 +8639,10 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8461
8639
  * The HTTP api is always created here, so it is always ours to close.
8462
8640
  */
8463
8641
  async disconnect() {
8464
- const http4 = this.httpApi;
8642
+ const http3 = this.httpApi;
8465
8643
  this.httpApi = null;
8466
- if (http4) {
8467
- await http4.then((api) => api.disconnect()).catch(() => {
8644
+ if (http3) {
8645
+ await http3.then((api) => api.disconnect()).catch(() => {
8468
8646
  });
8469
8647
  }
8470
8648
  if (this.ownsConnection) {
@@ -8478,9 +8656,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8478
8656
  *
8479
8657
  * The connection attempt is bounded on both sides. `isReadyOrError` rejects on a failed
8480
8658
  * handshake, where plain `isReady` would simply never resolve, and the timeout covers an
8481
- * endpoint that accepts the request and then goes quiet. An unbounded wait here would hang the
8482
- * poll tick awaiting it, which is precisely the silent stall that polling exists to avoid. A
8483
- * failed attempt is not cached, so the next call tries again.
8659
+ * endpoint that accepts the request and then goes quiet, so a caller awaiting it never hangs
8660
+ * silently. A failed attempt is not cached, so the next call tries again.
8484
8661
  */
8485
8662
  async http() {
8486
8663
  if (!this.httpApi) {
@@ -8489,11 +8666,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8489
8666
  // Response cache off (third argument, capacity 0). polkadot-js caches every request that
8490
8667
  // names a block hash — `chain_getHeader(hash)`, `state_getRuntimeVersion(hash)`, storage
8491
8668
  // reads at a hash — by storing the request promise itself, a rejected one included, for a
8492
- // 30s TTL that every hit refreshes. The phantom poll retries the block it failed on with
8493
- // identical parameters every tick, so one reset connection became the same rejection
8494
- // replayed from memory on every tick, faster than the TTL could lapse, and the node never
8495
- // saw a second request. The cache bought nothing here anyway: the poll reads each block
8496
- // once, and `api.at(hash)` reuses registries at the api layer regardless.
8669
+ // 30s TTL that every hit refreshes. A caller retrying a failed read with identical
8670
+ // parameters would get the same rejection replayed from memory for as long as it kept
8671
+ // retrying inside the TTL, and the node would never see a second request.
8497
8672
  // Concurrent calls are coalesced into one JSON-RPC batch request, and every request
8498
8673
  // to this endpoint is paced by a bucket shared with any other coprocessor in this
8499
8674
  // process pointed at the same host — the limit is the server's, and it counts
@@ -8568,7 +8743,12 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8568
8743
  return { success: false, error: err instanceof Error ? err.message : String(err) };
8569
8744
  }
8570
8745
  }
8571
- return await this.sendExtrinsicWithRetries(build(this.api), maxRetries, timeoutMs);
8746
+ return await this.sendExtrinsicWithRetries(
8747
+ build(this.api),
8748
+ maxRetries,
8749
+ timeoutMs,
8750
+ await this.nextNonce(this.api)
8751
+ );
8572
8752
  });
8573
8753
  return result ?? { success: false, error: "Submission queue returned no result" };
8574
8754
  }
@@ -8587,7 +8767,10 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8587
8767
  */
8588
8768
  async sendViaHttp(api, build) {
8589
8769
  try {
8590
- const hash = await build(api).signAndSend(this.getKeyPair(), { tip: BASE_TIP });
8770
+ const nonce = await this.nextNonce(api);
8771
+ const options = nonce === void 0 ? { tip: BASE_TIP } : { tip: BASE_TIP, nonce };
8772
+ const hash = await build(api).signAndSend(this.getKeyPair(), options);
8773
+ if (nonce !== void 0) this.lastPooledNonce = nonce;
8591
8774
  return { success: false, pending: true, extrinsicHash: hash.toHex() };
8592
8775
  } catch (err) {
8593
8776
  return this.classifySubmissionError(err instanceof Error ? err : new Error(String(err)));
@@ -8600,7 +8783,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8600
8783
  * Two kinds of failure are retried, and the difference is the nonce.
8601
8784
  *
8602
8785
  * An attempt that verifiably went nowhere (rejected before the pool, dropped, invalid) leaves
8603
- * the account nonce free, so the next attempt simply re-signs with the auto-nonce.
8786
+ * the account nonce free, so the next attempt simply re-signs with the pool-aware nonce.
8604
8787
  *
8605
8788
  * An attempt that reached the pool and was still there when the watch timed out (`stalled`) is
8606
8789
  * retried as a *replacement*: the same nonce it was signed with, and double the tip. Substrate's
@@ -8619,16 +8802,19 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8619
8802
  * copy is in flight and its outcome is unknown here, so the `pending` result goes back to the
8620
8803
  * caller to confirm later.
8621
8804
  */
8622
- async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
8805
+ async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs, initialNonce) {
8623
8806
  const keyPair = this.getKeyPair();
8624
8807
  let attempt = 0;
8625
- let nonce;
8808
+ let nonce = initialNonce;
8626
8809
  let stalled;
8627
8810
  while (attempt < maxRetries) {
8628
8811
  const currentTip = BASE_TIP * BigInt(2 ** attempt);
8629
8812
  attempt++;
8630
8813
  try {
8631
8814
  const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs, nonce);
8815
+ if (nonce !== void 0 && (result.success || result.pending || result.stalled)) {
8816
+ this.lastPooledNonce = Math.max(nonce, this.lastPooledNonce ?? nonce);
8817
+ }
8632
8818
  if (result.success || result.error?.includes("Dispatch error")) {
8633
8819
  return result;
8634
8820
  }
@@ -8688,6 +8874,35 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8688
8874
  * replacement rather than a second extrinsic queued behind the first. Left undefined on the
8689
8875
  * first attempt, where the api's auto-nonce is correct.
8690
8876
  */
8877
+ /**
8878
+ * The nonce the next submission signs with, or undefined when the node cannot be asked.
8879
+ *
8880
+ * Submissions are serialised, but a watch gives up while its extrinsic is still in the pool
8881
+ * (`stalled`, reported as `pending`), and a pooled extrinsic does not advance the account's
8882
+ * on-chain nonce. An extrinsic signed against on-chain state would then take the nonce of the
8883
+ * one ahead of it and bounce off it (1013/1014) instead of queueing behind it.
8884
+ *
8885
+ * `system_accountNextIndex` is the only pool-aware source here. `signAndSend`'s own `nonce: -1`
8886
+ * is not: on a runtime carrying `AccountNonceApi`, which Hyperbridge does, polkadot-js reads the
8887
+ * nonce through that runtime call and never sees the pool.
8888
+ *
8889
+ * The RPC covers the ready queue, so anything of this instance's that has left it — retried,
8890
+ * queued as future, or simply not counted yet — is covered by `lastPooledNonce` instead, which
8891
+ * is the highest nonce this instance has put into the pool. The two are combined rather than
8892
+ * trusted separately, so a burst never reuses a nonce and a restart still picks up the chain's.
8893
+ */
8894
+ async nextNonce(api) {
8895
+ try {
8896
+ const accountNextIndex = api.rpc?.system?.accountNextIndex;
8897
+ if (!accountNextIndex) return void 0;
8898
+ const next = await accountNextIndex(this.getKeyPair().address);
8899
+ const fromPool = Number(next.toString());
8900
+ if (!Number.isFinite(fromPool)) return void 0;
8901
+ return this.lastPooledNonce === void 0 ? fromPool : Math.max(fromPool, this.lastPooledNonce + 1);
8902
+ } catch {
8903
+ return void 0;
8904
+ }
8905
+ }
8691
8906
  async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs, nonce) {
8692
8907
  return new Promise((resolve) => {
8693
8908
  let resolved = false;
@@ -8730,25 +8945,10 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8730
8945
  } else if (result.status.isInBlock || result.status.isFinalized) {
8731
8946
  resolved = true;
8732
8947
  clearTimeout(timeoutId);
8733
- const interrupted = result.events.find(
8734
- ({ event }) => event.section === "utility" && event.method === "BatchInterrupted"
8735
- );
8736
- if (interrupted) {
8737
- const [indexCodec, dispatchError] = interrupted.event.data;
8738
- if (Number(indexCodec.toString()) === 0) {
8739
- resolve({
8740
- success: false,
8741
- error: `Dispatch error: ${this.describeDispatchError(dispatchError)}`
8742
- });
8743
- return;
8744
- }
8745
- }
8746
8948
  resolve({
8747
8949
  success: true,
8748
8950
  blockHash: (result.status.isInBlock ? result.status.asInBlock : result.status.asFinalized).toHex(),
8749
- extrinsicHash: extrinsic.hash.toHex(),
8750
- // Carried so a batch caller can attribute each item's outcome.
8751
- events: result.events
8951
+ extrinsicHash: extrinsic.hash.toHex()
8752
8952
  });
8753
8953
  }
8754
8954
  }).then((unsub) => {
@@ -8769,13 +8969,19 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8769
8969
  /**
8770
8970
  * Submits a bid to Hyperbridge's pallet-intents
8771
8971
  *
8972
+ * A filler can hold several bids on one order, one per price it offers, and `bid` tells them
8973
+ * apart: submitting again under an identifier the filler already holds replaces that bid alone.
8974
+ * By convention it is `keccak256` of the UserOp's `callData`, which is also what gives each bid
8975
+ * its own nonce key on the `SolverAccount` (see `CryptoUtils.bidId`).
8976
+ *
8772
8977
  * @param commitment - The order commitment hash (bytes32)
8773
8978
  * @param userOp - The encoded PackedUserOperation as hex string
8979
+ * @param bid - Identifies this bid among the filler's bids on the order (bytes32)
8774
8980
  * @returns BidSubmissionResult with success status and block/extrinsic hash
8775
8981
  */
8776
- async submitBid(commitment, userOp) {
8982
+ async submitBid(commitment, userOp, bid) {
8777
8983
  try {
8778
- return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.placeBid(commitment, userOp));
8984
+ return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.placeBid(commitment, bid, userOp));
8779
8985
  } catch (error) {
8780
8986
  return {
8781
8987
  success: false,
@@ -8789,46 +8995,12 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8789
8995
  * Use this to remove unused quotes and claim back deposited BRIDGE tokens.
8790
8996
  *
8791
8997
  * @param commitment - The order commitment hash (bytes32)
8998
+ * @param bid - Which of the filler's bids on the order to retract (bytes32)
8792
8999
  * @returns BidSubmissionResult with success status and block/extrinsic hash
8793
9000
  */
8794
- async retractBid(commitment) {
8795
- try {
8796
- return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.retractBid(commitment));
8797
- } catch (error) {
8798
- return {
8799
- success: false,
8800
- error: error instanceof Error ? error.message : "Unknown error"
8801
- };
8802
- }
8803
- }
8804
- /**
8805
- * Places a new bid and retracts a previous one in a single transaction via utility.batch.
8806
- *
8807
- * The new bid is the primary operation, so `placeBid` MUST run first. `utility.batch` is
8808
- * non-atomic: a failing call interrupts the batch (via a BatchInterrupted event) without
8809
- * reverting the calls that already succeeded. Placing first guarantees the new bid lands even
8810
- * when the retraction then fails — which it routinely does, because a previous commitment's bid
8811
- * may already be gone (or was itself never placed), making `retractBid` return `BidNotFound`.
8812
- *
8813
- * Ordering retraction first (the previous behaviour) caused a self-sustaining cascade: a
8814
- * `BidNotFound` on the leading retract skipped the trailing `placeBid`, so the current bid never
8815
- * landed, so the *next* interval's retract of that never-placed commitment also failed, and so
8816
- * on — silently, because the batch extrinsic itself reports success. The deposit reclaim is
8817
- * best-effort; landing the bid is not.
8818
- *
8819
- * @param retractCommitment - The order commitment of the bid to retract (bytes32)
8820
- * @param bidCommitment - The order commitment of the new bid (bytes32)
8821
- * @param userOp - The encoded PackedUserOperation as hex string
8822
- * @returns BidSubmissionResult with success status and block/extrinsic hash
8823
- */
8824
- async submitBidWithRetraction(retractCommitment, bidCommitment, userOp) {
9001
+ async retractBid(commitment, bid) {
8825
9002
  try {
8826
- return await this.signAndSendExtrinsic(
8827
- (api) => api.tx.utility.batch([
8828
- api.tx.intentsCoprocessor.placeBid(bidCommitment, userOp),
8829
- api.tx.intentsCoprocessor.retractBid(retractCommitment)
8830
- ])
8831
- );
9003
+ return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.retractBid(commitment, bid));
8832
9004
  } catch (error) {
8833
9005
  return {
8834
9006
  success: false,
@@ -8836,89 +9008,6 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8836
9008
  };
8837
9009
  }
8838
9010
  }
8839
- /**
8840
- * Places every phantom bid of one interval in a single extrinsic, retracting each chain's
8841
- * previous bid alongside it.
8842
- *
8843
- * The pallet registers one phantom order per configured chain in the same block, so this is the
8844
- * whole interval's set. Submitting them one at a time costs a block per chain: submissions are
8845
- * serialised on the account nonce and each waits for inclusion, so the last chain's bid is many
8846
- * blocks behind the first, against a bid window measured in tens of blocks. Batched, every bid
8847
- * lands in the same block.
8848
- *
8849
- * Uses `utility.force_batch`, not `utility.batch`. `batch` stops at the first failing call, so
8850
- * one rejected bid — a closed window, a duplicate, an insufficient deposit — would silently
8851
- * drop every bid after it. `force_batch` runs them all and reports each outcome. It needs no
8852
- * special origin: any signed account may call it, exactly like `batch`.
8853
- *
8854
- * @param bids - The bids to place; an empty list is a no-op
8855
- * @returns Per-bid outcomes, in the order given
8856
- */
8857
- async submitPhantomBids(bids) {
8858
- if (bids.length === 0) return { bids: [] };
8859
- const placeIndexByBid = [];
8860
- let callCount = 0;
8861
- for (const bid of bids) {
8862
- placeIndexByBid.push(callCount);
8863
- callCount += bid.retractCommitment ? 2 : 1;
8864
- }
8865
- const outcome = await this.signAndSendExtrinsic(
8866
- (api) => api.tx.utility.forceBatch(
8867
- bids.flatMap((bid) => {
8868
- const calls = [api.tx.intentsCoprocessor.placeBid(bid.commitment, bid.userOp)];
8869
- if (bid.retractCommitment) {
8870
- calls.push(api.tx.intentsCoprocessor.retractBid(bid.retractCommitment));
8871
- }
8872
- return calls;
8873
- })
8874
- )
8875
- );
8876
- if (!outcome.success) {
8877
- return {
8878
- bids: bids.map((bid) => ({ commitment: bid.commitment, success: false, error: outcome.error })),
8879
- pending: outcome.pending,
8880
- extrinsicHash: outcome.extrinsicHash,
8881
- error: outcome.error
8882
- };
8883
- }
8884
- const items = this.readForceBatchItems(outcome.events ?? [], callCount);
8885
- return {
8886
- bids: bids.map((bid, index) => {
8887
- const error = items.errors[placeIndexByBid[index]];
8888
- return { commitment: bid.commitment, success: !error, error };
8889
- }),
8890
- blockHash: outcome.blockHash,
8891
- extrinsicHash: outcome.extrinsicHash,
8892
- error: items.error
8893
- };
8894
- }
8895
- /**
8896
- * Reads one outcome per call out of a force_batch's events.
8897
- *
8898
- * `ItemFailed` carries no index — pallet-utility emits exactly one `ItemCompleted` or
8899
- * `ItemFailed` per call, in call order, so the k-th item event belongs to call k.
8900
- *
8901
- * A count that does not match the calls submitted means the events are not the ones assumed
8902
- * here, and every attribution after the discrepancy would be off by one. The bids are then
8903
- * reported as placed: a bid wrongly recorded as landed is retracted next interval and the
8904
- * retraction harmlessly fails with `BidNotFound`, whereas one wrongly recorded as failed is
8905
- * never retracted at all and leaves its deposit reserved.
8906
- */
8907
- readForceBatchItems(events, callCount) {
8908
- const errors = [];
8909
- for (const { event } of events) {
8910
- if (event.section !== "utility") continue;
8911
- if (event.method === "ItemCompleted") errors.push(void 0);
8912
- else if (event.method === "ItemFailed") errors.push(this.describeDispatchError(event.data[0]));
8913
- }
8914
- if (errors.length !== callCount) {
8915
- return {
8916
- errors: new Array(callCount).fill(void 0),
8917
- error: `force_batch reported ${errors.length} item events for ${callCount} calls; outcomes not attributed`
8918
- };
8919
- }
8920
- return { errors };
8921
- }
8922
9011
  /** Renders a DispatchError as `pallet::Error`, falling back to its raw form. */
8923
9012
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
8924
9013
  describeDispatchError(dispatchError) {
@@ -8937,10 +9026,11 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8937
9026
  */
8938
9027
  async getBidStorageEntries(commitment) {
8939
9028
  const api = await this.http();
8940
- const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
9029
+ const entries = await api.query.intentsCoprocessor.orderBids.entries(commitment);
8941
9030
  return entries.map(([storageKey, depositValue]) => ({
8942
9031
  commitment,
8943
9032
  filler: storageKey.args[1].toString(),
9033
+ bid: storageKey.args[2].toHex(),
8944
9034
  deposit: BigInt(depositValue.toString())
8945
9035
  }));
8946
9036
  }
@@ -8972,7 +9062,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8972
9062
  return result.map((entry) => {
8973
9063
  const userOp = decodeUserOpScale(entry.user_op);
8974
9064
  const filler = new Keyring({ type: "sr25519" }).encodeAddress(hexToU8a(entry.filler));
8975
- return { filler, userOp, deposit: 0n };
9065
+ return { filler, bid: entry.bid, userOp, deposit: 0n };
8976
9066
  });
8977
9067
  }
8978
9068
  /**
@@ -8981,19 +9071,20 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8981
9071
  */
8982
9072
  async getBidsViaStorage(commitment) {
8983
9073
  const api = await this.http();
8984
- const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
9074
+ const entries = await api.query.intentsCoprocessor.orderBids.entries(commitment);
8985
9075
  if (entries.length === 0) return [];
8986
9076
  const bidPromises = entries.map(async ([storageKey, depositValue]) => {
8987
9077
  try {
8988
9078
  const filler = storageKey.args[1].toString();
9079
+ const bid = storageKey.args[2].toHex();
8989
9080
  const deposit = BigInt(depositValue.toString());
8990
- const offchainKey = this.buildOffchainBidKey(commitment, filler);
9081
+ const offchainKey = this.buildOffchainBidKey(commitment, filler, bid);
8991
9082
  const offchainKeyHex = u8aToHex(offchainKey);
8992
9083
  const offchainResult = await api.rpc.offchain.localStorageGet("PERSISTENT", offchainKeyHex);
8993
9084
  if (!offchainResult || offchainResult.isNone) return null;
8994
9085
  const bidData = offchainResult.unwrap().toHex();
8995
9086
  const decoded = this.decodeBid(bidData);
8996
- return { filler: decoded.filler, userOp: decoded.userOp, deposit };
9087
+ return { filler: decoded.filler, bid, userOp: decoded.userOp, deposit };
8997
9088
  } catch {
8998
9089
  return null;
8999
9090
  }
@@ -9009,411 +9100,31 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
9009
9100
  const userOp = decodeUserOpScale(userOpHex);
9010
9101
  return { filler, userOp };
9011
9102
  }
9012
- /** Builds offchain storage key: "intents::bid::" + commitment + filler */
9013
- buildOffchainBidKey(commitment, filler) {
9014
- return u8aConcat(OFFCHAIN_BID_PREFIX, hexToU8a(commitment), decodeAddress(filler));
9103
+ /** Builds offchain storage key: "intents::bid::" + commitment + filler + bid */
9104
+ buildOffchainBidKey(commitment, filler, bid) {
9105
+ return u8aConcat(OFFCHAIN_BID_PREFIX, hexToU8a(commitment), decodeAddress(filler), hexToU8a(bid));
9015
9106
  }
9016
- /**
9017
- * Fetches the ABI-encoded phantom order from offchain storage and decodes it
9018
- * into an `Order` object. The pallet writes the order bytes under the key
9019
- * `intents::phantom::order::<commitment>` when it calls `on_initialize`.
9020
- *
9021
- * Returns `null` if the key is absent (e.g. the node is not an offchain worker
9022
- * or the commitment has expired and been cleared).
9023
- */
9024
- async fetchPhantomOrder(commitment) {
9025
- const key = u8aConcat(OFFCHAIN_PHANTOM_PREFIX, hexToU8a(commitment));
9026
- const api = await this.http();
9027
- const result = await api.rpc.offchain.localStorageGet("PERSISTENT", u8aToHex(key));
9028
- if (!result || result.isNone) return null;
9029
- const rawHex = result.unwrap().toHex();
9030
- if (rawHex === "0x" || rawHex === "0x00") return null;
9031
- const placeOrderAbi = IntentGatewayV2_default.ABI.find((item) => item.type === "function" && item.name === "placeOrder");
9032
- const orderType = placeOrderAbi?.inputs?.[0];
9033
- if (!orderType) return null;
9034
- const [decoded] = decodeAbiParameters([orderType], rawHex);
9035
- const d = decoded;
9036
- const textDecoder = new TextDecoder();
9037
- return {
9038
- id: commitment,
9039
- user: d.user,
9040
- source: textDecoder.decode(hexToBytes(d.source)),
9041
- destination: textDecoder.decode(hexToBytes(d.destination)),
9042
- deadline: d.deadline,
9043
- nonce: d.nonce,
9044
- fees: d.fees,
9045
- session: d.session,
9046
- predispatch: {
9047
- assets: d.predispatch.assets.map((a) => ({
9048
- token: a.token,
9049
- amount: a.amount
9050
- })),
9051
- call: d.predispatch.call
9052
- },
9053
- inputs: d.inputs.map((i) => ({
9054
- token: i.token,
9055
- amount: i.amount
9056
- })),
9057
- output: {
9058
- beneficiary: d.output.beneficiary,
9059
- assets: d.output.assets.map((a) => ({
9060
- token: a.token,
9061
- amount: a.amount
9062
- })),
9063
- call: d.output.call
9064
- }
9065
- };
9107
+ };
9108
+ var TronChain = class _TronChain {
9109
+ constructor(params, evm) {
9110
+ this.params = params;
9111
+ this.evm = evm;
9112
+ this.tronWebInstance = new TronWeb({ fullHost: params.rpcUrl });
9066
9113
  }
9114
+ params;
9115
+ evm;
9116
+ tronWebInstance;
9067
9117
  /**
9068
- * Reads the PhantomOrderRegistered events emitted in a single block.
9069
- *
9070
- * Costs two RPCs per block when `knownVersion` is supplied and four without it, which is why the
9071
- * poll goes to the trouble of establishing one. `api.at(hash)` has to work out which metadata to
9072
- * decode the block against, and with nothing to go on it fetches the header and then the runtime
9073
- * version at its parent — every block, forever. Its cheaper paths are a registry already pinned
9074
- * to this exact hash (only ever the previous block's) or one matching a version the caller
9075
- * names, so naming the version is the only way out. See `getBlockRegistry` in
9076
- * `@polkadot/api/base/Init`; the `getUpgradeVersion` shortcut that would otherwise skip the
9077
- * lookup only covers chains hardcoded in `@polkadot/types-known`, which Hyperbridge is not.
9118
+ * Creates a `TronChain` instance from a fully-specified config object.
9078
9119
  *
9079
- * @param knownVersion - the runtime version this block is known to run, if the caller has
9080
- * established one. Passing a version the block does not actually run decodes it against the
9081
- * wrong metadata, so this is for callers that have checked, not a place to pass a guess.
9082
- */
9083
- async getPhantomOrdersInBlock(blockNumber, knownVersion) {
9084
- const api = await this.http();
9085
- const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
9086
- return await this.getPhantomOrdersAtHash(blockHash.toHex(), knownVersion);
9087
- }
9088
- /**
9089
- * The same read, for a caller that already holds the block's hash.
9120
+ * This is the only public way to construct a `TronChain` — the constructor is private.
9090
9121
  *
9091
- * Split out so the poll can fetch a whole range's hashes in one concurrent wave — which the
9092
- * provider coalesces into a single batched request — and then read each block's events knowing
9093
- * its hash. `chain_getBlockHash` is the half of the pair that parallelises safely: it takes no
9094
- * historic block hash, so it never triggers polkadot-js's per-hash registry resolution, and
9095
- * concurrent calls cannot race each other's registry state.
9122
+ * @param params - Tron chain configuration
9123
+ * @returns A `TronChain` instance
9096
9124
  */
9097
- async getPhantomOrdersAtHash(blockHash, knownVersion) {
9098
- const api = await this.http();
9099
- const apiAt = await api.at(blockHash, knownVersion);
9100
- return phantomOrdersFrom(await apiAt.query.system.events());
9101
- }
9102
- /**
9103
- * Every block's phantom orders across a whole range, in one `state_queryStorage` call.
9104
- *
9105
- * This is the cheap path: the request cost of a scan stops depending on how many blocks it
9106
- * covers. The events key is the only key queried, and both bounds are block hashes the caller
9107
- * already holds.
9108
- *
9109
- * Two properties of the RPC shape the result.
9110
- *
9111
- * It returns *diffs*: `query_storage_unfiltered` in `sc-rpc` pushes a change set for a block only
9112
- * when the value differs from the previous block in the range (`has_changed`, and the set is
9113
- * dropped when empty), so a block whose events encode byte-for-byte identically to its
9114
- * predecessor's is simply absent. That happens on a quiet chain, where consecutive blocks carry
9115
- * nothing but the timestamp inherent's `ExtrinsicSuccess`. It is safe here because an absent
9116
- * block provably carries no phantom orders: a `PhantomOrderRegistered` commitment is derived from
9117
- * the block number (`phantom_order_commitment`), so a block that registered orders can never
9118
- * encode identically to any other block. Absent therefore means "same as the previous block",
9119
- * and the previous block having orders would contradict that.
9120
- *
9121
- * And it is gated by `--rpc-methods` (`check_if_safe` in `sc-rpc`), which answers a denied call
9122
- * with `Method not found`. The node this reads from must already run unsafe RPC to serve
9123
- * `offchain_localStorageGet` for the orders themselves, so this is normally available; the poll
9124
- * falls back to reading block by block when it is not.
9125
- *
9126
- * @returns one entry per block the node reported a change for, in ascending block order.
9127
- */
9128
- async getPhantomOrdersInRange(fromBlockHash, toBlockHash) {
9129
- const api = await this.http();
9130
- const changeSets = await api.rpc.state.queryStorage.raw(
9131
- [SYSTEM_EVENTS_KEY],
9132
- fromBlockHash,
9133
- toBlockHash
9134
- );
9135
- return changeSets.map((changeSet) => {
9136
- const value = changeSet?.changes?.find(([key]) => key === SYSTEM_EVENTS_KEY)?.[1];
9137
- if (!value) return [];
9138
- return phantomOrdersFrom(api.registry.createType("Vec<EventRecord>", value));
9139
- });
9140
- }
9141
- /**
9142
- * Polls for newly registered phantom orders, invoking the callback once per block that carries
9143
- * any, with all of that block's orders.
9144
- *
9145
- * Per block rather than per order because that is how the pallet writes them: one order per
9146
- * configured chain, all registered in the same `on_initialize`. Delivering them together lets a
9147
- * caller bid on the whole interval in one extrinsic (see {@link submitPhantomBids}) instead of
9148
- * one per chain.
9149
- *
9150
- * Each tick reads the current head and scans every block between the last one processed and that
9151
- * head, so the block cursor — not the connection — determines what has been seen. This replaced a
9152
- * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
9153
- * the transport but does not reliably re-establish storage subscriptions, and anything emitted
9154
- * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
9155
- * meant silently missed bids.
9156
- *
9157
- * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
9158
- * cannot drop them, because the cursor only advances past a block whose events were actually
9159
- * read. Recovery replays the backlog.
9160
- *
9161
- * Every read here goes over HTTP, never the websocket. Polling is a sequence of independent
9162
- * one-shot requests with no state to lose between them, which is exactly what a stateless
9163
- * transport does well: a request either answers or fails loudly on this tick, instead of a
9164
- * socket that looks alive while delivering nothing. It also means a websocket outage does not
9165
- * pause phantom bidding at all — the two transports fail independently.
9166
- *
9167
- * What the cadence does *not* describe is the request rate, which is what rate limiters police.
9168
- * A tick costs four requests whatever the range covers — the head, the runtime version, the two
9169
- * bounding block hashes as one batched request, and one `state_queryStorage` for every block's
9170
- * events — and they go out back-to-back, so an interval well under any per-second limit could
9171
- * still arrive as a burst over it. Three things keep that in bounds: the provider coalesces
9172
- * concurrent calls into one request and paces requests through the endpoint's token bucket (see
9173
- * `http`), and `maxBlocksPerPoll` bounds the range. A 429 that gets through anyway backs the
9174
- * poll off for a doubling number of ticks, so a limiter that is already shedding load is not
9175
- * handed the next window's budget in rejections too.
9176
- *
9177
- * Where the node will not serve `state_queryStorage` the poll reads block by block instead, at
9178
- * three requests plus one per block; see {@link scanRangeAtOnce}.
9179
- *
9180
- * Returns a function that stops polling.
9181
- */
9182
- pollPhantomOrders(callback, options = {}) {
9183
- const {
9184
- intervalMs,
9185
- maxBlocksPerPoll = DEFAULT_MAX_BLOCKS_PER_POLL,
9186
- onError,
9187
- onSkip
9188
- } = options;
9189
- let cursor = null;
9190
- let inFlight = false;
9191
- let stopped = false;
9192
- let backoffTicks = 0;
9193
- let backoffLength = 0;
9194
- const tick = async () => {
9195
- if (inFlight || stopped) return;
9196
- if (backoffTicks > 0) {
9197
- backoffTicks -= 1;
9198
- return;
9199
- }
9200
- inFlight = true;
9201
- try {
9202
- const api = await this.http();
9203
- const head = (await api.rpc.chain.getHeader()).number.toNumber();
9204
- if (cursor === null) {
9205
- cursor = Math.max(head - 1, -1);
9206
- } else {
9207
- const { bidWindowBlocks, intervalBlocks } = await this.phantomTimings();
9208
- if (head - cursor > bidWindowBlocks + Math.max(intervalBlocks, bidWindowBlocks)) {
9209
- const from = cursor + 1;
9210
- cursor = Math.max(head - 1 - bidWindowBlocks, -1);
9211
- onSkip?.({ from, to: cursor, head });
9212
- }
9213
- }
9214
- if (head <= cursor) return;
9215
- const knownVersion = await this.confirmedRuntimeVersion();
9216
- const to = Math.min(head, cursor + maxBlocksPerPoll);
9217
- const ranged = await this.scanRangeAtOnce(api, cursor + 1, to, knownVersion, onError);
9218
- if (ranged) {
9219
- for (const orders of ranged) {
9220
- if (stopped) return;
9221
- if (orders.length > 0) callback(orders);
9222
- }
9223
- cursor = to;
9224
- backoffLength = 0;
9225
- return;
9226
- }
9227
- const numbers = [];
9228
- for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) numbers.push(blockNumber);
9229
- const hashes = await Promise.allSettled(
9230
- numbers.map((blockNumber) => api.rpc.chain.getBlockHash(blockNumber))
9231
- );
9232
- for (let index = 0; index < numbers.length; index++) {
9233
- if (stopped) return;
9234
- const hash = hashes[index];
9235
- if (hash.status === "rejected") throw hash.reason;
9236
- const orders = await this.getPhantomOrdersAtHash(hash.value.toHex(), knownVersion);
9237
- if (orders.length > 0) callback(orders);
9238
- cursor = numbers[index];
9239
- }
9240
- backoffLength = 0;
9241
- } catch (err) {
9242
- if (isRateLimited(err)) {
9243
- backoffLength = Math.min(backoffLength === 0 ? 1 : backoffLength * 2, MAX_RATE_LIMIT_BACKOFF_TICKS);
9244
- backoffTicks = backoffLength;
9245
- }
9246
- onError?.(err);
9247
- } finally {
9248
- inFlight = false;
9249
- }
9250
- };
9251
- void tick();
9252
- let timer = null;
9253
- const startTimer = (ms) => {
9254
- if (stopped) return;
9255
- timer = setInterval(() => void tick(), ms);
9256
- };
9257
- if (intervalMs !== void 0) startTimer(intervalMs);
9258
- else void this.phantomPollIntervalMs().then(startTimer);
9259
- return () => {
9260
- stopped = true;
9261
- if (timer) clearInterval(timer);
9262
- };
9263
- }
9264
- /**
9265
- * The Hyperbridge head, over HTTP like every other read here.
9266
- *
9267
- * Exposed for callers that have to know how old something is: a phantom order carries the block
9268
- * it was registered at, and only against the head does that become "still biddable" or "long
9269
- * expired".
9270
- */
9271
- async latestBlockNumber() {
9272
- const api = await this.http();
9273
- return (await api.rpc.chain.getHeader()).number.toNumber();
9274
- }
9275
- /**
9276
- * The pallet's phantom timings, read from chain state.
9277
- *
9278
- * Both are governance-settable and neither is derivable: on Nexus today the window is 15 while
9279
- * the runtime constant behind it is 25, so anything hard-coded is wrong in one direction or the
9280
- * other — too tight and live orders are dropped, too loose and bids are sent into a closed
9281
- * window for the pallet to reject.
9282
- *
9283
- * Read once per instance and cached, because a governance change to either is rare and a read
9284
- * per poll tick would be a request per tick forever. The cost is that a change is picked up on
9285
- * the next restart rather than immediately. A failed read is not cached, so it retries.
9286
- */
9287
- async phantomTimings() {
9288
- if (!this.phantomTimingsRead) {
9289
- this.phantomTimingsRead = this.readPhantomTimings().catch((err) => {
9290
- this.phantomTimingsRead = null;
9291
- throw err;
9292
- });
9293
- }
9294
- return this.phantomTimingsRead;
9295
- }
9296
- async readPhantomTimings() {
9297
- const api = await this.http();
9298
- const [window, interval] = await Promise.all([
9299
- api.query.intentsCoprocessor.phantomBidWindow(),
9300
- api.query.intentsCoprocessor.phantomOrderInterval()
9301
- ]);
9302
- const stored = Number(window.toString());
9303
- return {
9304
- bidWindowBlocks: stored === 0 ? Number(api.consts.intentsCoprocessor.phantomOrderBidWindowBlocks.toString()) : stored,
9305
- intervalBlocks: Number(interval.toString())
9306
- };
9307
- }
9308
- /**
9309
- * A whole range of blocks in one `state_queryStorage` call, or `null` when that is not available
9310
- * and the caller should read block by block.
9311
- *
9312
- * Two conditions have to hold, and both are about decoding rather than the range itself.
9313
- *
9314
- * The version must be confirmed for this tick — an upgrade inside the range means blocks decode
9315
- * against different metadata, and one call cannot do that.
9316
- *
9317
- * And that confirmed version must still be the one the api's own registry was built for.
9318
- * `state_queryStorage` declares no historic block hash, so rpc-core skips its registry swap and
9319
- * decodes the reply against the default registry — fixed at connect, with no
9320
- * `subscribeRuntimeVersion` on an HTTP api to refresh it. After an upgrade the two diverge, and
9321
- * the per-block path takes over for good: `api.at(hash, version)` resolves, and builds, the right
9322
- * registry. That costs a restart to get the cheap path back, which is the correct direction to
9323
- * fail in.
9324
- */
9325
- async scanRangeAtOnce(api, from, to, knownVersion, onError) {
9326
- if (this.rangeQueryUnavailable || !knownVersion) return null;
9327
- const registryVersion = api.runtimeVersion?.specVersion;
9328
- if (!registryVersion || !knownVersion.specVersion.eq(registryVersion)) return null;
9329
- const [fromHash, toHash] = from === to ? await Promise.all([api.rpc.chain.getBlockHash(from)]).then(([only]) => [only, only]) : await Promise.all([api.rpc.chain.getBlockHash(from), api.rpc.chain.getBlockHash(to)]);
9330
- try {
9331
- return await this.getPhantomOrdersInRange(fromHash.toHex(), toHash.toHex());
9332
- } catch (err) {
9333
- if (isMethodUnavailable(err)) {
9334
- this.rangeQueryUnavailable = true;
9335
- return null;
9336
- }
9337
- if (err instanceof EventDecodeError) {
9338
- this.rangeQueryUnavailable = true;
9339
- onError?.(err);
9340
- return null;
9341
- }
9342
- throw err;
9343
- }
9344
- }
9345
- /**
9346
- * The runtime version this tick's blocks may be decoded against, or `undefined` when that cannot
9347
- * be established and each block must resolve its own.
9348
- *
9349
- * Naming a version to `api.at` is what removes two of the four RPCs a block scan costs, and it
9350
- * is only sound while the version is actually the block's. Getting that wrong is not a loud
9351
- * failure: events decoded against the wrong metadata come back as a shape the scan does not
9352
- * recognise, so the block reads as carrying no phantom orders and the cursor advances past it —
9353
- * exactly the silent miss the block cursor exists to rule out.
9354
- *
9355
- * So the version is read fresh each tick and only used when it matches the previous reading.
9356
- * `specVersion` only ever increases, and this read happens *after* the head read, so two equal
9357
- * readings mean no upgrade landed anywhere in between — and therefore none in the range about to
9358
- * be scanned. A reading that differs means an upgrade landed inside the range: that tick falls
9359
- * back to per-block resolution, which is exact, and the version is used from the next tick on
9360
- * once it has been seen twice.
9361
- *
9362
- * The gap this leaves is a backlog reaching back past an upgrade, whose oldest blocks predate
9363
- * even the previous reading. Recovering from an outage that long means those bid windows closed
9364
- * many upgrades ago, so nothing is lost that was still winnable.
9365
- *
9366
- * A version that cannot be read at all yields `undefined` rather than an error: the scan is
9367
- * about to make the same request against the same endpoint and is the better place to report it.
9368
- */
9369
- async confirmedRuntimeVersion() {
9370
- let api;
9371
- let current;
9372
- try {
9373
- api = await this.http();
9374
- current = await api.rpc.state.getRuntimeVersion();
9375
- } catch {
9376
- return void 0;
9377
- }
9378
- const previous = this.lastRuntimeVersion ?? api.runtimeVersion;
9379
- this.lastRuntimeVersion = current;
9380
- if (!previous?.specVersion || !previous?.specName) return void 0;
9381
- return current.specVersion.eq(previous.specVersion) && current.specName.eq(previous.specName) ? current : void 0;
9382
- }
9383
- /**
9384
- * The poll cadence for the runtime this instance is connected to: Gargantua polls every block,
9385
- * everything else every 15s. Falls back to the slower cadence if the runtime cannot be read,
9386
- * since an unreachable node is the poll's problem to report, not the cadence lookup's.
9387
- */
9388
- async phantomPollIntervalMs() {
9389
- try {
9390
- const specName = (await this.http()).runtimeVersion.specName.toString();
9391
- return specName === "gargantua" ? GARGANTUA_PHANTOM_POLL_INTERVAL_MS : PHANTOM_POLL_INTERVAL_MS;
9392
- } catch {
9393
- return PHANTOM_POLL_INTERVAL_MS;
9394
- }
9395
- }
9396
- };
9397
- var TronChain = class _TronChain {
9398
- constructor(params, evm) {
9399
- this.params = params;
9400
- this.evm = evm;
9401
- this.tronWebInstance = new TronWeb({ fullHost: params.rpcUrl });
9402
- }
9403
- params;
9404
- evm;
9405
- tronWebInstance;
9406
- /**
9407
- * Creates a `TronChain` instance from a fully-specified config object.
9408
- *
9409
- * This is the only public way to construct a `TronChain` — the constructor is private.
9410
- *
9411
- * @param params - Tron chain configuration
9412
- * @returns A `TronChain` instance
9413
- */
9414
- static async fromParams(params) {
9415
- const evm = await EvmChain.create(params.rpcUrl, params.bundlerUrl);
9416
- return new _TronChain(params, evm);
9125
+ static async fromParams(params) {
9126
+ const evm = await EvmChain.create(params.rpcUrl, params.bundlerUrl);
9127
+ return new _TronChain(params, evm);
9417
9128
  }
9418
9129
  // -------------------------------------------------------------------------
9419
9130
  // Public accessors
@@ -10476,8 +10187,8 @@ var PharosChain = class _PharosChain {
10476
10187
  };
10477
10188
  async function generateRootWithProof(postRequest, treeSize) {
10478
10189
  const { generate_root_with_proof: generate_root_with_proof2 } = await load_ckb_mmr();
10479
- const { commitment: hash, encodePacked: encodePacked4 } = postRequestCommitment(postRequest);
10480
- const result = JSON.parse(generate_root_with_proof2(hexToBytes(encodePacked4), treeSize));
10190
+ const { commitment: hash, encodePacked: encodePacked5 } = postRequestCommitment(postRequest);
10191
+ const result = JSON.parse(generate_root_with_proof2(hexToBytes(encodePacked5), treeSize));
10481
10192
  const { root, proof, mmr_size, keccak_hash_calldata } = result;
10482
10193
  if (keccak_hash_calldata !== hash) {
10483
10194
  console.log("keccak_hash", keccak_hash_calldata);
@@ -10694,7 +10405,8 @@ async function retryPromise(operation, retryConfig) {
10694
10405
  return await operation();
10695
10406
  } catch (error) {
10696
10407
  if (shouldRetry && !shouldRetry(error)) throw error;
10697
- logger.trace(`Retrying(${i}) > ${logMessage}`);
10408
+ const detail = error instanceof Error ? error.message.split("\n")[0] : String(error);
10409
+ logger.trace(`Retrying(${i}) > ${logMessage}: ${detail.slice(0, 200)}`);
10698
10410
  lastError = error;
10699
10411
  if (i === retryConfig.maxRetries - 1) break;
10700
10412
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
@@ -12740,92 +12452,6 @@ query OrderStatus($commitment: String!) {
12740
12452
  }
12741
12453
  }
12742
12454
  }`;
12743
- var LATEST_PHANTOM_ORDER_PRICE_SNAPSHOT = `
12744
- query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
12745
- phantomOrderPriceSnapshots(
12746
- filter: {
12747
- and: [
12748
- { tokenA: { equalTo: $tokenA } }
12749
- { tokenB: { equalTo: $tokenB } }
12750
- { medianPrice: { isNull: false } }
12751
- ]
12752
- }
12753
- orderBy: BLOCK_NUMBER_DESC
12754
- first: 1
12755
- ) {
12756
- nodes {
12757
- commitment
12758
- tokenA
12759
- tokenB
12760
- standardAmount
12761
- blockNumber
12762
- medianPrice
12763
- lowestPrice
12764
- highestPrice
12765
- bidCount
12766
- snapshotTime
12767
- }
12768
- }
12769
- }`;
12770
- var AVAILABLE_LIQUIDITY = `
12771
- query AvailableLiquidity(
12772
- $poolId: String!
12773
- $sourceChain: String!
12774
- $destinationChain: String!
12775
- $direction: String!
12776
- ) {
12777
- poolChainLiquidities(
12778
- filter: {
12779
- and: [
12780
- { poolId: { equalToInsensitive: $poolId } }
12781
- { chain: { equalTo: $destinationChain } }
12782
- { direction: { equalTo: $direction } }
12783
- ]
12784
- }
12785
- first: 1
12786
- ) {
12787
- nodes {
12788
- depth
12789
- bidCount
12790
- unrestrictedDepth
12791
- unrestrictedBidCount
12792
- lastUpdatedAt
12793
- }
12794
- }
12795
- poolRoutes(
12796
- filter: {
12797
- and: [
12798
- { poolId: { equalToInsensitive: $poolId } }
12799
- { sourceChain: { equalTo: $sourceChain } }
12800
- { chain: { equalTo: $destinationChain } }
12801
- { direction: { equalTo: $direction } }
12802
- ]
12803
- }
12804
- first: 1
12805
- ) {
12806
- nodes {
12807
- depth
12808
- bidCount
12809
- lastUpdatedAt
12810
- }
12811
- }
12812
- }`;
12813
- var BUY_AND_SELL_RATES = `
12814
- query GetLiquidityPoolRate($poolId: String!) {
12815
- liquidityPools(
12816
- first: 1
12817
- filter: { id: { equalToInsensitive: $poolId } }
12818
- ) {
12819
- nodes {
12820
- id
12821
- token0Symbol
12822
- token1Symbol
12823
- sellRate
12824
- buyRate
12825
- lastUpdatedAt
12826
- }
12827
- }
12828
- }`;
12829
12455
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
12830
12456
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
12831
12457
  tokenGatewayAssetTeleportedV2s(
@@ -12887,29 +12513,6 @@ async function queryAssetTeleported(params) {
12887
12513
  function queryGetRequest(params) {
12888
12514
  return _queryGetRequestInternal(params);
12889
12515
  }
12890
- async function _queryLatestPhantomOrderPriceSnapshot(params) {
12891
- const response = await params.queryClient.request(
12892
- LATEST_PHANTOM_ORDER_PRICE_SNAPSHOT,
12893
- {
12894
- tokenA: params.tokenA.toLowerCase(),
12895
- tokenB: params.tokenB.toLowerCase()
12896
- }
12897
- );
12898
- const snapshot = response.phantomOrderPriceSnapshots.nodes[0];
12899
- if (!snapshot?.medianPrice) return;
12900
- return {
12901
- commitment: snapshot.commitment,
12902
- tokenA: snapshot.tokenA,
12903
- tokenB: snapshot.tokenB,
12904
- standardAmount: BigInt(snapshot.standardAmount),
12905
- blockNumber: BigInt(snapshot.blockNumber),
12906
- medianPrice: BigInt(snapshot.medianPrice),
12907
- lowestPrice: snapshot.lowestPrice === null ? void 0 : BigInt(snapshot.lowestPrice),
12908
- highestPrice: snapshot.highestPrice === null ? void 0 : BigInt(snapshot.highestPrice),
12909
- bidCount: snapshot.bidCount,
12910
- snapshotTime: new Date(snapshot.snapshotTime)
12911
- };
12912
- }
12913
12516
  async function _queryTokenGatewayAssetTeleportedInternal(params) {
12914
12517
  const { commitmentHash, queryClient: client, logger = DEFAULT_LOGGER } = params;
12915
12518
  const response = await retryPromise(
@@ -16183,18 +15786,39 @@ var CryptoUtils = class _CryptoUtils {
16183
15786
  }
16184
15787
  /**
16185
15788
  * Derives the ERC-4337 nonce key that binds a bid UserOperation to its
16186
- * order and session key: the lower 192 bits of
16187
- * `keccak256(commitment ‖ sessionKey)`. `SolverAccount` rejects bid
16188
- * operations whose nonce key differs — this is what lets the solver sign
16189
- * the plain userOpHash while staying committed to the order and to the
16190
- * session key it bid against.
15789
+ * order, session key and calldata: the lower 192 bits of
15790
+ * `keccak256(commitment ‖ sessionKey ‖ keccak256(callData))`. `SolverAccount`
15791
+ * rejects bid operations whose nonce key differs — this is what lets the
15792
+ * solver sign the plain userOpHash while staying committed to the order and
15793
+ * to the session key it bid against.
15794
+ *
15795
+ * The calldata gives every bid its own key. A solver bidding several prices
15796
+ * on one order signs one op per price, and each is sequence 0 of its own key,
15797
+ * so they execute independently rather than in a sequence where one bid that
15798
+ * is never selected blocks the rest.
16191
15799
  *
16192
15800
  * @param commitment - The order commitment (`order.id`).
16193
15801
  * @param sessionKey - The order's session key address (`order.session`).
15802
+ * @param callData - The UserOperation's `callData`.
16194
15803
  * @returns The 192-bit nonce key as a bigint (pass to `EntryPoint.getNonce`).
16195
15804
  */
16196
- static bidNonceKey(commitment, sessionKey) {
16197
- return BigInt(keccak256(encodePacked(["bytes32", "address"], [commitment, sessionKey]))) & (1n << 192n) - 1n;
15805
+ static bidNonceKey(commitment, sessionKey, callData) {
15806
+ return BigInt(
15807
+ keccak256(
15808
+ encodePacked(["bytes32", "address", "bytes32"], [commitment, sessionKey, keccak256(callData)])
15809
+ )
15810
+ ) & (1n << 192n) - 1n;
15811
+ }
15812
+ /**
15813
+ * The identifier Hyperbridge files a bid under: `keccak256` of the UserOp's
15814
+ * `callData`. It is the same hash {@link bidNonceKey} takes, so a bid's
15815
+ * identifier and its nonce key both follow from the op itself, and one
15816
+ * solver's bids on one order are kept apart for the same reason on both sides.
15817
+ *
15818
+ * @param callData - The UserOperation's `callData`.
15819
+ */
15820
+ static bidId(callData) {
15821
+ return keccak256(callData);
16198
15822
  }
16199
15823
  /**
16200
15824
  * Builds the EIP-712 typed-data payload whose digest is the EntryPoint v0.8
@@ -16753,9 +16377,98 @@ var OrderPlacer = class {
16753
16377
  return { order: finalizedOrder, receipt };
16754
16378
  }
16755
16379
  };
16380
+ var TOKEN_KEYED_GATEWAY_ABI = parseAbi([
16381
+ "function _orders(bytes32 commitment, address token) view returns (uint256)",
16382
+ "function _partialFills(bytes32 commitment, bytes32 token) view returns (uint256)"
16383
+ ]);
16384
+ var keyingByClient = /* @__PURE__ */ new WeakMap();
16385
+ async function readLegEscrow(client, gateway, commitment, index, token) {
16386
+ return readKeyed(
16387
+ client,
16388
+ gateway,
16389
+ (keying) => keying === "leg" ? client.readContract({
16390
+ address: gateway,
16391
+ abi: ABI3,
16392
+ functionName: "_orders",
16393
+ args: [commitment, BigInt(index)]
16394
+ }) : client.readContract({
16395
+ address: gateway,
16396
+ abi: TOKEN_KEYED_GATEWAY_ABI,
16397
+ functionName: "_orders",
16398
+ args: [commitment, bytes32ToBytes20(token)]
16399
+ })
16400
+ );
16401
+ }
16402
+ async function readLegPartialFill(client, gateway, commitment, index, token) {
16403
+ return readKeyed(
16404
+ client,
16405
+ gateway,
16406
+ (keying) => keying === "leg" ? client.readContract({
16407
+ address: gateway,
16408
+ abi: ABI3,
16409
+ functionName: "_partialFills",
16410
+ args: [commitment, BigInt(index)]
16411
+ }) : client.readContract({
16412
+ address: gateway,
16413
+ abi: TOKEN_KEYED_GATEWAY_ABI,
16414
+ functionName: "_partialFills",
16415
+ args: [commitment, token]
16416
+ })
16417
+ );
16418
+ }
16419
+ async function readKeyed(client, gateway, read) {
16420
+ let known = keyingByClient.get(client);
16421
+ if (!known) {
16422
+ known = /* @__PURE__ */ new Map();
16423
+ keyingByClient.set(client, known);
16424
+ }
16425
+ const key = gateway.toLowerCase();
16426
+ const first = known.get(key) ?? "leg";
16427
+ try {
16428
+ const value = await read(first);
16429
+ known.set(key, first);
16430
+ return value;
16431
+ } catch (error) {
16432
+ if (!isRevert(error)) throw error;
16433
+ const second = first === "leg" ? "token" : "leg";
16434
+ try {
16435
+ const value = await read(second);
16436
+ known.set(key, second);
16437
+ return value;
16438
+ } catch (secondError) {
16439
+ throw isRevert(secondError) ? error : secondError;
16440
+ }
16441
+ }
16442
+ }
16443
+ function isRevert(error) {
16444
+ let current = error;
16445
+ while (current && typeof current === "object") {
16446
+ const item = current;
16447
+ if (item.name === "ContractFunctionZeroDataError") return true;
16448
+ if (item.code === 3) return true;
16449
+ if (!item.cause || typeof item.cause !== "object") {
16450
+ return /^(?:execution reverted\b|VM Exception while processing transaction:\s*revert\b|function selector was not recognized\b)/i.test(
16451
+ item.message ?? ""
16452
+ );
16453
+ }
16454
+ current = item.cause;
16455
+ }
16456
+ return false;
16457
+ }
16458
+ var PARTIAL_FILLS_SLOT = 11n;
16459
+ function partialFillSlot(commitment, index) {
16460
+ const inner = keccak256(encodePacked(["bytes32", "uint256"], [commitment, PARTIAL_FILLS_SLOT]));
16461
+ return keccak256(encodePacked(["uint256", "bytes32"], [BigInt(index), inner]));
16462
+ }
16756
16463
 
16757
16464
  // src/protocols/intents/OrderExecutor.ts
16758
16465
  var USED_USEROPS_STORAGE_KEY = (commitment) => `used-userops:${commitment.toLowerCase()}`;
16466
+ function remainingAfter(targets, filled) {
16467
+ return targets.map((target, index) => {
16468
+ const credited = filled[index]?.amount ?? 0n;
16469
+ return { token: target.token, amount: credited >= target.amount ? 0n : target.amount - credited };
16470
+ });
16471
+ }
16759
16472
  var OrderExecutor = class {
16760
16473
  constructor(ctx, bidManager) {
16761
16474
  this.ctx = ctx;
@@ -16805,6 +16518,38 @@ var OrderExecutor = class {
16805
16518
  JSON.stringify([...usedUserOps])
16806
16519
  );
16807
16520
  }
16521
+ /**
16522
+ * Reads the order's state on its destination chain into `totalFilledAssets`: the output credited
16523
+ * per leg from `_partialFills`, never below what is already counted, so a lagging RPC node cannot
16524
+ * take back a fill this executor has seen. Other solvers can fill the order too, so the chain, not
16525
+ * this executor's own fills, says how much is left.
16526
+ *
16527
+ * Returns the terminal update once `_filled` is set: `FILLED` when every leg is complete,
16528
+ * `CANCELLED` otherwise. `_filled` is read first because progress cannot change after it is set,
16529
+ * so a finalized order is never paired with progress from before its last fill.
16530
+ */
16531
+ async syncWithDestination(order, commitment, targetAssets, totalFilledAssets) {
16532
+ const client = this.ctx.dest.client;
16533
+ const gateway = this.ctx.dest.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination));
16534
+ const finalizer = await client.readContract({
16535
+ address: gateway,
16536
+ abi: ABI3,
16537
+ functionName: "_filled",
16538
+ args: [commitment]
16539
+ });
16540
+ const filled = await Promise.all(
16541
+ order.output.assets.map(async (asset, index) => {
16542
+ const credited = await readLegPartialFill(client, gateway, commitment, index, asset.token);
16543
+ const counted = totalFilledAssets[index]?.amount ?? 0n;
16544
+ return { token: asset.token, amount: credited > counted ? credited : counted };
16545
+ })
16546
+ );
16547
+ const remainingAssets = remainingAfter(targetAssets, filled);
16548
+ if (/^0x0{40}$/i.test(finalizer)) return { totalFilledAssets: filled, remainingAssets };
16549
+ const progress = { commitment, totalFilledAssets: filled, remainingAssets };
16550
+ const final = remainingAssets.every((asset) => asset.amount === 0n) ? { status: "FILLED", selectedSolver: finalizer, ...progress } : { status: "CANCELLED", ...progress };
16551
+ return { totalFilledAssets: filled, remainingAssets, final };
16552
+ }
16808
16553
  /**
16809
16554
  * Creates a closure that computes the deduplication hash key for a
16810
16555
  * UserOperation, pre-bound to the order's destination chain and entry point.
@@ -16863,12 +16608,12 @@ var OrderExecutor = class {
16863
16608
  }
16864
16609
  if (result.fillStatus === "partial") {
16865
16610
  const filledAssets = result.filledAssets ?? [];
16866
- totalFilledAssets = totalFilledAssets.map((a) => {
16867
- const filled = filledAssets.find((f) => f.token === a.token);
16611
+ totalFilledAssets = totalFilledAssets.map((a, index) => {
16612
+ const filled = filledAssets[index];
16868
16613
  return filled ? { token: a.token, amount: a.amount + filled.amount } : { ...a };
16869
16614
  });
16870
- remainingAssets = targetAssets.map((target) => {
16871
- const filled = totalFilledAssets.find((a) => a.token === target.token);
16615
+ remainingAssets = targetAssets.map((target, index) => {
16616
+ const filled = totalFilledAssets[index];
16872
16617
  const filledAmt = filled?.amount ?? 0n;
16873
16618
  return {
16874
16619
  token: target.token,
@@ -16934,8 +16679,27 @@ var OrderExecutor = class {
16934
16679
  const usedUserOps = await this.loadUsedUserOps(commitment);
16935
16680
  const userOpHashKey = this.createUserOpHasher(order);
16936
16681
  const targetAssets = order.output.assets.map((a) => ({ token: a.token, amount: a.amount }));
16937
- let totalFilledAssets = order.output.assets.map((a) => ({ token: a.token, amount: 0n }));
16938
- let remainingAssets = order.output.assets.map((a) => ({ token: a.token, amount: a.amount }));
16682
+ let initial;
16683
+ try {
16684
+ initial = await this.syncWithDestination(
16685
+ order,
16686
+ commitment,
16687
+ targetAssets,
16688
+ order.output.assets.map((a) => ({ token: a.token, amount: 0n }))
16689
+ );
16690
+ } catch (err) {
16691
+ yield {
16692
+ status: "FAILED",
16693
+ commitment,
16694
+ error: `Could not read the order on its destination chain: ${err instanceof Error ? err.message : String(err)}`
16695
+ };
16696
+ return;
16697
+ }
16698
+ if (initial.final) {
16699
+ yield initial.final;
16700
+ return;
16701
+ }
16702
+ const { totalFilledAssets, remainingAssets } = initial;
16939
16703
  const executionStream = this.executionStream({
16940
16704
  order,
16941
16705
  sessionPrivateKey,
@@ -16967,7 +16731,7 @@ var OrderExecutor = class {
16967
16731
  if (done) return;
16968
16732
  const fed = yield value;
16969
16733
  if (value.status === "BIDS_RECEIVED") input = fed;
16970
- if (value.status === "EXPIRED" || value.status === "FILLED") return;
16734
+ if (value.status === "EXPIRED" || value.status === "FILLED" || value.status === "CANCELLED") return;
16971
16735
  }
16972
16736
  } finally {
16973
16737
  console.log(`[OrderExecutor] Tearing down streams for commitment=${commitment}`);
@@ -17026,18 +16790,29 @@ var OrderExecutor = class {
17026
16790
  }
17027
16791
  while (true) {
17028
16792
  let freshBids;
16793
+ let final;
17029
16794
  try {
17030
- const bids2 = await this.fetchBids({ commitment, solver, solverLockStartTime });
17031
- freshBids = bids2.filter(isFreshBid);
16795
+ ;
16796
+ ({ totalFilledAssets, remainingAssets, final } = await this.syncWithDestination(
16797
+ order,
16798
+ commitment,
16799
+ targetAssets,
16800
+ totalFilledAssets
16801
+ ));
16802
+ freshBids = final ? [] : (await this.fetchBids({ commitment, solver, solverLockStartTime })).filter(isFreshBid);
17032
16803
  } catch {
17033
16804
  await sleep(pollIntervalMs);
17034
16805
  continue;
17035
16806
  }
16807
+ if (final) {
16808
+ yield final;
16809
+ return;
16810
+ }
17036
16811
  if (freshBids.length === 0) {
17037
16812
  await sleep(pollIntervalMs);
17038
16813
  continue;
17039
16814
  }
17040
- const bids = this.bidManager.buildBids(order, freshBids, sessionPrivateKey);
16815
+ const bids = this.bidManager.buildBids(order, freshBids, sessionPrivateKey).filter((bid) => servesOpenLeg(bid, remainingAssets));
17041
16816
  if (bids.length === 0) {
17042
16817
  await sleep(pollIntervalMs);
17043
16818
  continue;
@@ -17080,6 +16855,10 @@ var OrderExecutor = class {
17080
16855
  }
17081
16856
  }
17082
16857
  };
16858
+ function servesOpenLeg(bid, remainingAssets) {
16859
+ if (bid.outputs.length !== remainingAssets.length) return true;
16860
+ return bid.outputs.some((output, leg) => output.amount > 0n && remainingAssets[leg].amount > 0n);
16861
+ }
17083
16862
  var OrderCanceller = class _OrderCanceller {
17084
16863
  /**
17085
16864
  * @param ctx - Shared IntentsV2 context providing the source and destination
@@ -17118,8 +16897,9 @@ var OrderCanceller = class _OrderCanceller {
17118
16897
  /**
17119
16898
  * Quotes the native token cost of a source-initiated cross-chain cancellation.
17120
16899
  *
17121
- * Constructs a mock ISMP GET request for the destination commitment slot and
17122
- * calls `quoteNative` on the source host to obtain the dispatch fee.
16900
+ * Constructs the ISMP GET request the source gateway will dispatch, one
16901
+ * `_partialFills` key per leg, and calls `quoteNative` on the source host to
16902
+ * obtain the dispatch fee.
17123
16903
  * Returns 0 for same-chain orders (no ISMP call needed).
17124
16904
  *
17125
16905
  * @param order - The order to quote.
@@ -17133,21 +16913,16 @@ var OrderCanceller = class _OrderCanceller {
17133
16913
  }
17134
16914
  const height = order.deadline + 1n;
17135
16915
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
17136
- const slotHash = await this.ctx.dest.client.readContract({
17137
- abi: ABI3,
17138
- address: destIntentGateway,
17139
- functionName: "calculateCommitmentSlotHash",
17140
- args: [order.id]
17141
- });
17142
- const key = concatHex([destIntentGateway, slotHash]);
17143
- const context = encodeWithdrawalRequest(order, order.user);
16916
+ const commitment = this.orderId(order);
16917
+ const keys = order.inputs.map((_, index) => concatHex([destIntentGateway, partialFillSlot(commitment, index)]));
16918
+ const context = cancelFromSourceContext(order, commitment);
17144
16919
  const getRequest = {
17145
16920
  source: sourceStateMachine,
17146
16921
  dest: destStateMachine,
17147
16922
  from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
17148
16923
  nonce: await this.ctx.source.getHostNonce(),
17149
16924
  height,
17150
- keys: [key],
16925
+ keys,
17151
16926
  timeoutTimestamp: 0n,
17152
16927
  context
17153
16928
  };
@@ -17172,10 +16947,13 @@ var OrderCanceller = class _OrderCanceller {
17172
16947
  *
17173
16948
  * @param order - The order to cancel.
17174
16949
  * @param indexerClient - Indexer client used to stream ISMP request status
17175
- * updates and query state-machine heights.
17176
- * @param options - Choose the initiation side. Defaults to source-side cancellation.
16950
+ * updates and query state-machine heights. The direct same-chain route does
16951
+ * not access it.
16952
+ * @param options - Choose the initiation side. Defaults to source-side
16953
+ * cancellation. Same-chain orders always use the direct local route.
17177
16954
  * @yields {@link CancelEvent} objects describing each stage of the
17178
- * cancellation lifecycle.
16955
+ * cancellation lifecycle. The caller signs or broadcasts the yielded
16956
+ * transaction, so its signer may differ from `order.user` after expiry.
17179
16957
  */
17180
16958
  async *cancelOrder(order, indexerClient, options = {}) {
17181
16959
  const sourceStateMachine = normalizeStateMachineId(order.source);
@@ -17209,9 +16987,12 @@ var OrderCanceller = class _OrderCanceller {
17209
16987
  * Async generator that cancels an order by initiating the cancel from the
17210
16988
  * source chain.
17211
16989
  *
17212
- * **Same-chain path:** encodes a direct `cancelOrder` call, yields
17213
- * `AWAITING_CANCEL_TRANSACTION`, broadcasts the signed transaction, and
17214
- * yields `CANCELLATION_COMPLETE` after confirming the `EscrowRefunded` event.
16990
+ * **Same-chain path:** encodes a direct `cancelOrder` call with zero cancel
16991
+ * options, yields `AWAITING_CANCEL_TRANSACTION`, accepts either a signed raw
16992
+ * transaction or an already-broadcast hash, and yields `CANCELLATION_COMPLETE`
16993
+ * after confirming the `EscrowRefunded` event. The contract authorizes the
16994
+ * order user through the deadline and any caller strictly after it; the SDK
16995
+ * preserves `order.user` in calldata so a third party cannot redirect escrow.
17215
16996
  *
17216
16997
  * **Cross-chain path:**
17217
16998
  * 1. Fetches (or resumes from cache) a destination finalization proof.
@@ -17521,9 +17302,9 @@ var OrderCanceller = class _OrderCanceller {
17521
17302
  }
17522
17303
  }
17523
17304
  /**
17524
- * Polls for a finalized destination-chain state proof that demonstrates
17525
- * the order commitment slot is unset (i.e. the order was not filled before
17526
- * the deadline).
17305
+ * Polls for a finalized destination-chain state proof of every leg's
17306
+ * `_partialFills` slot, i.e. how much of the order was filled before the
17307
+ * deadline.
17527
17308
  *
17528
17309
  * Waits until the latest Hyperbridge-tracked state-machine height exceeds
17529
17310
  * `order.deadline` (or the last failed probe height) before attempting to
@@ -17552,14 +17333,9 @@ var OrderCanceller = class _OrderCanceller {
17552
17333
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
17553
17334
  this.ctx.dest.config.stateMachineId
17554
17335
  );
17555
- const orderId = this.orderId(order);
17556
- const slotHash = await this.ctx.dest.client.readContract({
17557
- abi: ABI3,
17558
- address: intentGatewayV2Address,
17559
- functionName: "calculateCommitmentSlotHash",
17560
- args: [orderId]
17561
- });
17562
- const proofHex = await this.ctx.dest.queryStateProof(latestHeight, [slotHash], intentGatewayV2Address);
17336
+ const commitment = this.orderId(order);
17337
+ const slots = order.inputs.map((_, index) => partialFillSlot(commitment, index));
17338
+ const proofHex = await this.ctx.dest.queryStateProof(latestHeight, slots, intentGatewayV2Address);
17563
17339
  const proof = {
17564
17340
  consensusStateId: this.ctx.dest.config.consensusStateId,
17565
17341
  height: latestHeight,
@@ -17757,118 +17533,135 @@ var OrderCanceller = class _OrderCanceller {
17757
17533
  return feeInDestFeeToken * 1005n / 1000n;
17758
17534
  }
17759
17535
  };
17760
- var FILL_ORDER_V1_ABI = [
17761
- {
17762
- type: "function",
17763
- name: "fillOrder",
17764
- stateMutability: "payable",
17765
- outputs: [],
17766
- inputs: [
17767
- ABI3.find((e) => e.type === "function" && e.name === "fillOrder").inputs[0],
17536
+ function cancelFromSourceContext(order, commitment) {
17537
+ return encodeAbiParameters(
17538
+ [
17539
+ { type: "bytes32" },
17540
+ { type: "bytes32" },
17768
17541
  {
17769
- name: "options",
17770
- type: "tuple",
17771
- internalType: "struct FillOptions",
17542
+ type: "tuple[]",
17772
17543
  components: [
17773
- { name: "relayerFee", type: "uint256", internalType: "uint256" },
17774
- { name: "nativeDispatchFee", type: "uint256", internalType: "uint256" },
17775
- {
17776
- name: "outputs",
17777
- type: "tuple[]",
17778
- internalType: "struct TokenInfo[]",
17779
- components: [
17780
- { name: "token", type: "bytes32", internalType: "bytes32" },
17781
- { name: "amount", type: "uint256", internalType: "uint256" }
17782
- ]
17783
- }
17544
+ { name: "token", type: "bytes32" },
17545
+ { name: "amount", type: "uint256" }
17784
17546
  ]
17785
- }
17547
+ },
17548
+ { type: "uint256[]" }
17549
+ ],
17550
+ [
17551
+ commitment,
17552
+ bytes20ToBytes32(order.user),
17553
+ order.inputs.map((input) => ({ token: normalizeAddressForEvmBytes32(input.token), amount: input.amount })),
17554
+ order.output.assets.map((asset) => asset.amount)
17786
17555
  ]
17556
+ );
17557
+ }
17558
+ var FILL_ORDER_SELECTOR = "0x68ddf058";
17559
+ var SUPPORTED_INTENTS_VERSION = 3n;
17560
+ var CONTRACT_VERSION_ABI = [
17561
+ {
17562
+ type: "function",
17563
+ name: "version",
17564
+ stateMutability: "view",
17565
+ inputs: [],
17566
+ outputs: [{ name: "", type: "uint64" }]
17787
17567
  }
17788
17568
  ];
17789
- var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
17790
- var LEGACY_FILL_OPTIONS_IMPLEMENTATIONS = /* @__PURE__ */ new Set([
17791
- // The pre-validUntil IntentGatewayV2 implementation. One entry covers every chain: the
17792
- // protocol contracts are CREATE2-deployed, so this is the implementation address on all
17793
- // of them (confirmed with the maintainers).
17794
- "0x976b268b06f545c4a2bf44866aa2465bd8b3c67d"
17795
- ]);
17796
- var CHAINS_WITHOUT_VALID_UNTIL = /* @__PURE__ */ new Set([
17797
- 97,
17798
- // BNB testnet
17799
- 10200,
17800
- // Gnosis Chiado
17801
- 80002,
17802
- // Polygon Amoy
17803
- 84532,
17804
- // Base Sepolia
17805
- 421614,
17806
- // Arbitrum Sepolia
17807
- 688689,
17808
- // Pharos testnet
17809
- 11155111,
17810
- // Sepolia
17811
- 11155420,
17812
- // Optimism Sepolia
17813
- 420420417
17814
- // Polkadot Hub Paseo
17815
- ]);
17816
- var knownV2Gateways = /* @__PURE__ */ new Set();
17817
- function resetFillOptionsVersionCache() {
17818
- knownV2Gateways.clear();
17569
+ async function readContractVersion(client, address) {
17570
+ try {
17571
+ return await client.readContract({ address, abi: CONTRACT_VERSION_ABI, functionName: "version" });
17572
+ } catch (error) {
17573
+ if (isRevert(error)) return void 0;
17574
+ throw error;
17575
+ }
17819
17576
  }
17820
- async function resolveImplementation(client, gateway) {
17821
- const slot = await client.getStorageAt({ address: gateway, slot: ERC1967_IMPLEMENTATION_SLOT });
17822
- if (!slot || slot.length < 66) return gateway;
17823
- const addr = `0x${slot.slice(-40)}`;
17824
- return /^0x0{40}$/.test(addr) ? gateway : addr;
17577
+ async function supportsRateFills(client, gateway) {
17578
+ return await readContractVersion(client, gateway) === SUPPORTED_INTENTS_VERSION;
17825
17579
  }
17826
- async function getFillOptionsVersion(client, gateway) {
17827
- const chainId = client.chain?.id;
17828
- if (chainId !== void 0 && CHAINS_WITHOUT_VALID_UNTIL.has(chainId)) return 1;
17829
- const key = gateway.toLowerCase();
17830
- if (knownV2Gateways.has(key)) return 2;
17831
- const implementation = await resolveImplementation(client, gateway);
17832
- if (LEGACY_FILL_OPTIONS_IMPLEMENTATIONS.has(implementation.toLowerCase())) return 1;
17833
- knownV2Gateways.add(key);
17834
- return 2;
17580
+ async function assertGatewayRelease(client, gateway) {
17581
+ const version = await readContractVersion(client, gateway);
17582
+ if (version === SUPPORTED_INTENTS_VERSION) return;
17583
+ throw new Error(
17584
+ version === void 0 ? `IntentGateway ${gateway} reports no version(); this SDK requires release ${SUPPORTED_INTENTS_VERSION}` : `IntentGateway ${gateway} reports release ${String(version)}; this SDK requires release ${SUPPORTED_INTENTS_VERSION}`
17585
+ );
17835
17586
  }
17836
- function encodeFillOrder(order, options, version) {
17837
- if (version === 2) {
17838
- return encodeFunctionData({
17839
- abi: ABI3,
17840
- functionName: "fillOrder",
17841
- args: [order, options]
17842
- });
17587
+ var CANONICAL_EVM_TOKEN = /^0x0{24}[0-9a-fA-F]{40}$/;
17588
+ function isCanonicalEvmToken(token) {
17589
+ return CANONICAL_EVM_TOKEN.test(token);
17590
+ }
17591
+ function validateFillQuotes(order, options) {
17592
+ const count = order.output.assets.length;
17593
+ if (!count || !Array.isArray(options.inputs) || options.inputs.length !== count || options.outputs.length !== count || order.inputs.length !== count) {
17594
+ throw new Error("Fill inputs and outputs must quote every order leg");
17595
+ }
17596
+ for (let i = 0; i < count; i++) {
17597
+ const input = options.inputs[i];
17598
+ const output = options.outputs[i];
17599
+ if (input.token.toLowerCase() !== order.inputs[i].token.toLowerCase() || output.token.toLowerCase() !== order.output.assets[i].token.toLowerCase() || !isCanonicalEvmToken(input.token) || !isCanonicalEvmToken(output.token)) {
17600
+ throw new Error("Fill quote tokens must match their order legs");
17601
+ }
17602
+ if (input.amount === 0n !== (output.amount === 0n)) {
17603
+ throw new Error("Fill quote input and output must both be zero or positive");
17604
+ }
17843
17605
  }
17844
- const { relayerFee, nativeDispatchFee, outputs } = options;
17606
+ }
17607
+ function encodeFillOrder(order, options) {
17608
+ validateFillQuotes(order, options);
17845
17609
  return encodeFunctionData({
17846
- abi: FILL_ORDER_V1_ABI,
17610
+ abi: ABI3,
17847
17611
  functionName: "fillOrder",
17848
- args: [order, { relayerFee, nativeDispatchFee, outputs }]
17612
+ args: [order, options]
17849
17613
  });
17850
17614
  }
17851
17615
  function decodeFillOrder(data) {
17852
17616
  try {
17853
17617
  const decoded = decodeFunctionData({ abi: ABI3, data });
17854
- if (decoded.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
17855
- return { order: decoded.args[0], options: decoded.args[1] };
17856
- }
17857
- } catch {
17858
- }
17859
- try {
17860
- const decoded = decodeFunctionData({ abi: FILL_ORDER_V1_ABI, data });
17861
- if (decoded.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
17862
- const legacy = decoded.args[1];
17863
- return { order: decoded.args[0], options: { ...legacy, validUntil: 0n } };
17864
- }
17618
+ if (decoded.functionName !== "fillOrder" || !decoded.args || decoded.args.length < 2) return null;
17619
+ const order = decoded.args[0];
17620
+ const options = decoded.args[1];
17621
+ validateFillQuotes(order, options);
17622
+ return { order, options };
17865
17623
  } catch {
17624
+ return null;
17866
17625
  }
17867
- return null;
17868
17626
  }
17869
- var BidImpl = class {
17870
- solverAddress;
17627
+ var ENTRY_POINT_EVENT_ABI = [
17628
+ { type: "event", name: "BeforeExecution", inputs: [] },
17629
+ {
17630
+ type: "event",
17631
+ name: "UserOperationEvent",
17632
+ inputs: [
17633
+ { name: "userOpHash", type: "bytes32", indexed: true },
17634
+ { name: "sender", type: "address", indexed: true },
17635
+ { name: "paymaster", type: "address", indexed: true },
17636
+ { name: "nonce", type: "uint256", indexed: false },
17637
+ { name: "success", type: "bool", indexed: false },
17638
+ { name: "actualGasCost", type: "uint256", indexed: false },
17639
+ { name: "actualGasUsed", type: "uint256", indexed: false }
17640
+ ]
17641
+ }
17642
+ ];
17643
+ function userOperationLogs(logs, entryPoint, userOpHash) {
17644
+ const [beforeExecution, userOperationEvent] = ENTRY_POINT_EVENT_ABI.map((event) => toEventSelector(event));
17645
+ const isBoundary = (log) => log.address.toLowerCase() === entryPoint.toLowerCase() && (log.topics[0] === beforeExecution || log.topics[0] === userOperationEvent);
17646
+ const end = logs.findIndex(
17647
+ (log) => isBoundary(log) && log.topics[0] === userOperationEvent && log.topics[1]?.toLowerCase() === userOpHash.toLowerCase()
17648
+ );
17649
+ if (end === -1) throw new Error(`UserOperation ${userOpHash} is not in the transaction`);
17650
+ const { args } = decodeEventLog({
17651
+ abi: ENTRY_POINT_EVENT_ABI,
17652
+ eventName: "UserOperationEvent",
17653
+ data: logs[end].data,
17654
+ topics: logs[end].topics
17655
+ });
17656
+ if (!args.success) throw new Error(`UserOperation ${userOpHash} reverted`);
17657
+ let start = end - 1;
17658
+ while (start >= 0 && !isBoundary(logs[start])) start--;
17659
+ return logs.slice(start + 1, end);
17660
+ }
17661
+ var BidImpl = class {
17662
+ solverAddress;
17871
17663
  outputs;
17664
+ inputs;
17872
17665
  relayerFee;
17873
17666
  nativeDispatchFee;
17874
17667
  userOp;
@@ -17891,6 +17684,7 @@ var BidImpl = class {
17891
17684
  this.sessionPrivateKey = params.sessionPrivateKey;
17892
17685
  this.solverAddress = params.fillerBid.userOp.sender;
17893
17686
  this.outputs = params.fillOptions.outputs;
17687
+ this.inputs = params.fillOptions.inputs;
17894
17688
  this.relayerFee = params.fillOptions.relayerFee;
17895
17689
  this.nativeDispatchFee = params.fillOptions.nativeDispatchFee;
17896
17690
  this.userOp = params.fillerBid.userOp;
@@ -18023,31 +17817,28 @@ var BidImpl = class {
18023
17817
  { maxRetries: 5, backoffMs: 2e3, logMessage: "Fetching user operation receipt" }
18024
17818
  );
18025
17819
  txnHash = receipt.receipt.transactionHash;
18026
- try {
18027
- const chainReceipt = await this.ctx.dest.client.waitForTransactionReceipt({
18028
- hash: txnHash,
18029
- confirmations: 1
18030
- });
18031
- const events = parseEventLogs({
18032
- abi: ABI3,
18033
- logs: chainReceipt.logs,
18034
- eventName: ["OrderFilled", "PartialFill"]
18035
- });
18036
- const matched = events.find((e) => {
18037
- if (e.eventName === "OrderFilled")
18038
- return e.args.commitment.toLowerCase() === commitment.toLowerCase();
18039
- if (e.eventName === "PartialFill")
18040
- return e.args.commitment.toLowerCase() === commitment.toLowerCase();
18041
- return false;
18042
- });
18043
- if (matched?.eventName === "OrderFilled") {
18044
- fillStatus = "full";
18045
- } else if (matched?.eventName === "PartialFill") {
18046
- fillStatus = "partial";
18047
- filledAssets = matched.args.outputs ?? [];
18048
- }
18049
- } catch {
18050
- throw new Error("Failed to determine fill status from logs");
17820
+ const chainReceipt = await this.ctx.dest.client.waitForTransactionReceipt({
17821
+ hash: txnHash,
17822
+ confirmations: 1
17823
+ });
17824
+ const gateway = this.ctx.dest.configService.getIntentGatewayAddress(
17825
+ normalizeStateMachineId(this.order.destination)
17826
+ );
17827
+ const fills = parseEventLogs({
17828
+ abi: ABI3,
17829
+ logs: userOperationLogs(chainReceipt.logs, entryPointAddress, userOpHash),
17830
+ eventName: ["OrderFilled", "PartialFill"]
17831
+ }).filter(
17832
+ (event) => event.address.toLowerCase() === gateway.toLowerCase() && event.args.commitment.toLowerCase() === commitment.toLowerCase()
17833
+ );
17834
+ if (fills.some((event) => event.eventName === "OrderFilled")) {
17835
+ fillStatus = "full";
17836
+ } else if (fills.length > 0) {
17837
+ fillStatus = "partial";
17838
+ filledAssets = fills[0].args.outputs.map(({ token }, index) => ({
17839
+ token,
17840
+ amount: fills.reduce((sum, event) => sum + (event.args.outputs[index]?.amount ?? 0n), 0n)
17841
+ }));
18051
17842
  }
18052
17843
  } catch (err) {
18053
17844
  throw new Error(`Failed to execute bid: ${err instanceof Error ? err.message : String(err)}`);
@@ -18114,6 +17905,20 @@ var BidManager = class {
18114
17905
  callData,
18115
17906
  paymasterAndData = "0x"
18116
17907
  } = options;
17908
+ const fills = (this.crypto.decodeERC7821Execute(callData) ?? []).filter(
17909
+ (call) => call.data.slice(0, 10).toLowerCase() === FILL_ORDER_SELECTOR
17910
+ );
17911
+ if (fills.length === 0) throw new Error("Bid calldata carries no fillOrder call");
17912
+ for (const call of fills) {
17913
+ if (!decodeFillOrder(call.data)) {
17914
+ throw new Error("Malformed fill quote; every leg requires inputs and outputs");
17915
+ }
17916
+ }
17917
+ const chain = normalizeStateMachineId(order.destination);
17918
+ const gateway = this.ctx.dest.configService.getIntentGatewayAddress(chain);
17919
+ if (!await supportsRateFills(this.ctx.dest.client, gateway)) {
17920
+ throw new Error("Fills are not supported by the destination gateway");
17921
+ }
18117
17922
  const chainId = BigInt(
18118
17923
  this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
18119
17924
  );
@@ -18131,10 +17936,10 @@ var BidManager = class {
18131
17936
  signature: "0x"
18132
17937
  };
18133
17938
  const nonceKey = BigInt(nonce) >> 64n;
18134
- const expectedKey = CryptoUtils.bidNonceKey(order.id, order.session);
17939
+ const expectedKey = CryptoUtils.bidNonceKey(order.id, order.session, callData);
18135
17940
  if (nonceKey !== expectedKey) {
18136
17941
  console.warn(
18137
- `[BidManager] bid nonce key does not bind the order commitment and session key; on-chain validation will fail (order=${order.id})`
17942
+ `[BidManager] bid nonce key does not bind the order commitment, session key and calldata; on-chain validation will fail (order=${order.id})`
18138
17943
  );
18139
17944
  }
18140
17945
  const solverSignature = await solverSigner.signTypedData(
@@ -18199,7 +18004,7 @@ var BidManager = class {
18199
18004
  return this.selectAndExecuteBest(order, this.buildBids(order, bids, sessionPrivateKey));
18200
18005
  }
18201
18006
  /**
18202
- * Autopilot bid selection: sorts the given bids by output value, simulates
18007
+ * Autopilot bid selection: sorts the given bids by rate, simulates
18203
18008
  * each in order and attempts execution. Any bid that fails simulation or
18204
18009
  * execution is skipped once so the next ranked bid can be attempted in the
18205
18010
  * same round. For consumers that do not need custom selection logic.
@@ -18254,35 +18059,58 @@ var BidManager = class {
18254
18059
  throw new Error("No bids passed simulation and execution");
18255
18060
  }
18256
18061
  /**
18257
- * Sorts a list of bids for the given order by output value.
18062
+ * Sorts bids by rate, best to worst, independently of how much each one fills.
18258
18063
  *
18259
- * Delegates to one of three strategies based on the order's output token
18260
- * composition:
18261
- * - Single output token: sort by offered amount descending.
18262
- * - All stable outputs (USDC/USDT): sort by normalised USD value descending.
18263
- * - Mixed outputs: sort by DEX-quoted USD value descending, with a raw-amount
18264
- * fallback if pricing fails.
18064
+ * A leg's rate is what the bid pays out for what it takes: `output / input` for
18065
+ * that leg. Every leg of an order trades the same pair, so a bid's rate is its
18066
+ * total output over its total input across the legs it quotes. Rates are
18067
+ * compared exactly, by cross-multiplication; equal rates keep arrival order.
18265
18068
  *
18266
- * Bids that cannot satisfy the order's token set are dropped.
18069
+ * A bid is dropped when it does not quote the order's legs one for one, names
18070
+ * another token on a leg, quotes an input without an output (or the reverse),
18071
+ * quotes no leg at all, or quotes a leg below the rate the order requires.
18267
18072
  *
18268
- * @param order - The placed order whose output spec drives sorting logic.
18073
+ * @param order - The placed order the bids compete to fill.
18269
18074
  * @param bids - Executable bids to sort (from {@link buildBids}).
18270
- * @returns Sorted array of `Bid` objects ready for simulation.
18075
+ * @returns The valid bids, best rate first, ready for simulation.
18271
18076
  */
18272
18077
  async sortBids(order, bids) {
18273
- const outputs = order.output.assets;
18274
- if (outputs.length <= 1) {
18275
- console.log(`[BidManager] Using single-output sorting (1 output asset)`);
18276
- return this.sortSingleOutput(bids, outputs[0]);
18277
- }
18278
- const chainId = this.ctx.dest.config.stateMachineId;
18279
- const allStables = outputs.every((o) => this.isStableToken(bytes32ToBytes20(o.token), chainId));
18280
- if (allStables) {
18281
- console.log(`[BidManager] Using all-stables sorting (${outputs.length} stable output assets)`);
18282
- return this.sortAllStables(bids, outputs, chainId);
18078
+ const legs = order.inputs.length;
18079
+ const ranked = [];
18080
+ for (const [index, bid] of bids.entries()) {
18081
+ if (bid.inputs.length !== legs || bid.outputs.length !== legs || order.output.assets.length !== legs) {
18082
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: does not quote the order's legs`);
18083
+ continue;
18084
+ }
18085
+ let input = 0n;
18086
+ let output = 0n;
18087
+ let reason = "";
18088
+ for (let leg = 0; leg < legs && !reason; leg++) {
18089
+ const take = bid.inputs[leg];
18090
+ const pay = bid.outputs[leg];
18091
+ const escrow = order.inputs[leg];
18092
+ const required = order.output.assets[leg];
18093
+ if (take.token.toLowerCase() !== escrow.token.toLowerCase()) reason = `input token mismatch on leg ${leg}`;
18094
+ else if (pay.token.toLowerCase() !== required.token.toLowerCase()) reason = `output token mismatch on leg ${leg}`;
18095
+ else if (take.amount < 0n || pay.amount < 0n || take.amount === 0n !== (pay.amount === 0n))
18096
+ reason = `no valid quote on leg ${leg}`;
18097
+ else if (pay.amount * escrow.amount < take.amount * required.amount) reason = `rate below the order on leg ${leg}`;
18098
+ input += take.amount;
18099
+ output += pay.amount;
18100
+ }
18101
+ if (!reason && input === 0n) reason = "quotes no leg";
18102
+ if (reason) {
18103
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: ${reason}`);
18104
+ continue;
18105
+ }
18106
+ ranked.push({ bid, input, output, index });
18283
18107
  }
18284
- console.log(`[BidManager] Using mixed-output sorting (${outputs.length} output assets, some non-stable)`);
18285
- return this.sortMixedOutputs(bids, outputs, chainId);
18108
+ ranked.sort((a, b) => {
18109
+ const left = a.output * b.input;
18110
+ const right = b.output * a.input;
18111
+ return left === right ? a.index - b.index : left > right ? -1 : 1;
18112
+ });
18113
+ return ranked.map(({ bid }) => bid);
18286
18114
  }
18287
18115
  /**
18288
18116
  * Extracts the `FillOptions` struct from a single bid's ERC-7821
@@ -18305,150 +18133,6 @@ var BidManager = class {
18305
18133
  }
18306
18134
  return null;
18307
18135
  }
18308
- /**
18309
- * Case A: single output token.
18310
- * Filter bids by token match only, sort descending by amount.
18311
- * Partial fill bids are allowed — the contract determines fill status.
18312
- */
18313
- sortSingleOutput(bids, requiredAsset) {
18314
- const requiredAmount = new Decimal2(requiredAsset.amount.toString());
18315
- console.log(
18316
- `[BidManager] sortSingleOutput: required token=${requiredAsset.token}, amount=${requiredAmount.toString()}`
18317
- );
18318
- const validBids = [];
18319
- for (const bid of bids) {
18320
- const bidOutput = bid.outputs[0];
18321
- const bidAmount = new Decimal2(bidOutput.amount.toString());
18322
- if (bidOutput.token.toLowerCase() !== requiredAsset.token.toLowerCase()) {
18323
- console.warn(
18324
- `[BidManager] Bid from solver=${bid.solverAddress} REJECTED: token mismatch (bid=${bidOutput.token}, required=${requiredAsset.token})`
18325
- );
18326
- continue;
18327
- }
18328
- if (bidAmount.lt(requiredAmount)) {
18329
- console.log(
18330
- `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidAmount.toString()}, required=${requiredAmount.toString()}, covers=${bidAmount.div(requiredAmount).mul(100).toFixed(2)}%)`
18331
- );
18332
- } else {
18333
- console.log(
18334
- `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: amount=${bidAmount.toString()} (surplus=${bidAmount.minus(requiredAmount).toString()})`
18335
- );
18336
- }
18337
- validBids.push({ bid, amount: bidOutput.amount });
18338
- }
18339
- validBids.sort((a, b) => {
18340
- const aAmt = new Decimal2(a.amount.toString());
18341
- const bAmt = new Decimal2(b.amount.toString());
18342
- return bAmt.comparedTo(aAmt);
18343
- });
18344
- return validBids.map(({ bid }) => bid);
18345
- }
18346
- /**
18347
- * Case B: all outputs are USDC/USDT.
18348
- * Sum normalised USD values (treating each stable as $1) and sort descending.
18349
- * Partial fill bids are allowed.
18350
- */
18351
- sortAllStables(bids, orderOutputs, chainId) {
18352
- const requiredUsd = this.computeStablesUsdValue(orderOutputs, chainId);
18353
- console.log(`[BidManager] sortAllStables: required USD value=${requiredUsd.toString()}`);
18354
- const validBids = [];
18355
- for (const bid of bids) {
18356
- const bidUsd = this.computeStablesUsdValue(bid.outputs, chainId);
18357
- if (bidUsd === null) {
18358
- console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: unable to compute USD value`);
18359
- continue;
18360
- }
18361
- if (bidUsd.lt(requiredUsd)) {
18362
- console.log(
18363
- `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
18364
- );
18365
- } else {
18366
- console.log(`[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: USD value=${bidUsd.toString()}`);
18367
- }
18368
- validBids.push({ bid, usdValue: bidUsd });
18369
- }
18370
- validBids.sort((a, b) => b.usdValue.comparedTo(a.usdValue));
18371
- return validBids.map(({ bid }) => bid);
18372
- }
18373
- /**
18374
- * Case C: mixed output tokens (at least one non-stable).
18375
- * Price every token via on-chain DEX quotes, fall back to raw amounts
18376
- * if pricing is unavailable. Partial fill bids are allowed.
18377
- */
18378
- async sortMixedOutputs(bids, orderOutputs, chainId) {
18379
- const requiredUsd = await this.computeOutputsUsdValue(orderOutputs, chainId);
18380
- if (requiredUsd === null) {
18381
- console.warn("[BidManager] sortMixedOutputs: output tokens unpriceable, falling back to raw-amount sort");
18382
- return this.sortByRawAmountFallback(bids, orderOutputs);
18383
- }
18384
- console.log(`[BidManager] sortMixedOutputs: required USD value=${requiredUsd.toString()}`);
18385
- const validBids = [];
18386
- for (const bid of bids) {
18387
- const bidUsd = await this.computeOutputsUsdValue(bid.outputs, chainId);
18388
- if (bidUsd === null) {
18389
- console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: unable to price mixed outputs`);
18390
- continue;
18391
- }
18392
- if (bidUsd.lt(requiredUsd)) {
18393
- console.log(
18394
- `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
18395
- );
18396
- } else {
18397
- console.log(
18398
- `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: mixed USD value=${bidUsd.toString()}`
18399
- );
18400
- }
18401
- validBids.push({ bid, usdValue: bidUsd });
18402
- }
18403
- validBids.sort((a, b) => b.usdValue.comparedTo(a.usdValue));
18404
- return validBids.map(({ bid }) => bid);
18405
- }
18406
- /**
18407
- * Fallback when DEX pricing is unavailable.
18408
- * Computes total spread per bid. Bids missing a required token are rejected.
18409
- * Bids offering less than required for a token are allowed (partial fill).
18410
- * Sorted by total offered amount descending.
18411
- */
18412
- sortByRawAmountFallback(bids, orderOutputs) {
18413
- console.log(
18414
- `[BidManager] sortByRawAmountFallback: checking ${bids.length} bid(s) against ${orderOutputs.length} required output(s)`
18415
- );
18416
- const validBids = [];
18417
- for (const bid of bids) {
18418
- let valid = true;
18419
- let totalOffered = new Decimal2(0);
18420
- let rejectReason = "";
18421
- for (const required of orderOutputs) {
18422
- const matching = bid.outputs.find((o) => o.token.toLowerCase() === required.token.toLowerCase());
18423
- if (!matching) {
18424
- valid = false;
18425
- rejectReason = `missing output token=${required.token}`;
18426
- break;
18427
- }
18428
- totalOffered = totalOffered.plus(new Decimal2(matching.amount.toString()));
18429
- }
18430
- if (!valid) {
18431
- console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED (fallback): ${rejectReason}`);
18432
- continue;
18433
- }
18434
- const totalRequired = orderOutputs.reduce(
18435
- (acc, o) => acc.plus(new Decimal2(o.amount.toString())),
18436
- new Decimal2(0)
18437
- );
18438
- if (totalOffered.lt(totalRequired)) {
18439
- console.log(
18440
- `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (fallback) (offered=${totalOffered.toString()}, required=${totalRequired.toString()}, covers=${totalOffered.div(totalRequired).mul(100).toFixed(2)}%)`
18441
- );
18442
- } else {
18443
- console.log(
18444
- `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED (fallback): totalOffered=${totalOffered.toString()}`
18445
- );
18446
- }
18447
- validBids.push({ bid, totalOffered });
18448
- }
18449
- validBids.sort((a, b) => b.totalOffered.comparedTo(a.totalOffered));
18450
- return validBids.map(({ bid }) => bid);
18451
- }
18452
18136
  // ── Token classification helpers ──────────────────────────────────
18453
18137
  /**
18454
18138
  * Returns `true` if `tokenAddr` is either USDC or USDT on the given chain.
@@ -18479,23 +18163,6 @@ var BidManager = class {
18479
18163
  return configService.getUsdtDecimals(chainId);
18480
18164
  }
18481
18165
  // ── Basket valuation helpers ──────────────────────────────────────
18482
- /**
18483
- * Sums the USD value of a basket of stable tokens (USDC/USDT only),
18484
- * normalising each amount by its decimal count and treating each token as $1.
18485
- *
18486
- * @param outputs - List of token/amount pairs where every token is a stable.
18487
- * @param chainId - State-machine ID used to look up decimals.
18488
- * @returns Total USD value as a `Decimal`.
18489
- */
18490
- computeStablesUsdValue(outputs, chainId) {
18491
- let total = new Decimal2(0);
18492
- for (const output of outputs) {
18493
- const tokenAddr = bytes32ToBytes20(output.token);
18494
- const decimals = this.getStableDecimals(tokenAddr, chainId);
18495
- total = total.plus(new Decimal2(output.amount.toString()).div(new Decimal2(10).pow(decimals)));
18496
- }
18497
- return total;
18498
- }
18499
18166
  /**
18500
18167
  * Prices every token in the basket via on-chain DEX quotes (token → USDC).
18501
18168
  * Stables are valued at $1. Non-stables are quoted through Uniswap
@@ -18510,6 +18177,7 @@ var BidManager = class {
18510
18177
  const { asset: wethAddr } = configService.getWrappedNativeAssetWithDecimals(chainId);
18511
18178
  let totalUsd = new Decimal2(0);
18512
18179
  for (const output of outputs) {
18180
+ if (output.amount === 0n) continue;
18513
18181
  const tokenAddr = bytes32ToBytes20(output.token);
18514
18182
  if (this.isStableToken(tokenAddr, chainId)) {
18515
18183
  const decimals = this.getStableDecimals(tokenAddr, chainId);
@@ -18633,6 +18301,14 @@ var GasEstimator = class {
18633
18301
  */
18634
18302
  async estimateFillOrder(params, pricingOptions = {}) {
18635
18303
  const { order } = params;
18304
+ if (params.outputs !== void 0 && params.inputs === void 0) {
18305
+ throw new Error("Custom output quotes require explicit inputs");
18306
+ }
18307
+ const inputs = (params.inputs ?? order.inputs).map((input) => ({
18308
+ ...input,
18309
+ token: normalizeAddressForEvmBytes32(input.token)
18310
+ }));
18311
+ const quotedOutputs = params.outputs ?? order.output.assets;
18636
18312
  const orderFeeGasPriceBumpPercent = pricingOptions.orderFeeGasPriceBumpPercent ?? 0n;
18637
18313
  const solverPrivateKey = generatePrivateKey();
18638
18314
  const solverAccountAddress = privateKeyToAddress(solverPrivateKey);
@@ -18641,7 +18317,7 @@ var GasEstimator = class {
18641
18317
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachineId);
18642
18318
  const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(destStateMachineId);
18643
18319
  const chainId = BigInt(Number.parseInt(destStateMachineId.split("-")[1]));
18644
- const totalEthValue = order.output.assets.filter((output) => bytes32ToBytes20(output.token) === ADDRESS_ZERO2).reduce((sum, output) => sum + output.amount, 0n);
18320
+ const totalEthValue = quotedOutputs.filter((output) => bytes32ToBytes20(output.token) === ADDRESS_ZERO2).reduce((sum, output) => sum + output.amount, 0n);
18645
18321
  const [sourceFeeToken, destFeeToken, gasPrice, latestBlock] = await Promise.all([
18646
18322
  getFeeToken(this.ctx, this.ctx.source.config.stateMachineId, this.ctx.source),
18647
18323
  getFeeToken(this.ctx, this.ctx.dest.config.stateMachineId, this.ctx.dest),
@@ -18650,7 +18326,7 @@ var GasEstimator = class {
18650
18326
  ]);
18651
18327
  const baseFeePerGas = latestBlock.baseFeePerGas ?? gasPrice;
18652
18328
  const feeTokenAsBytes32 = bytes20ToBytes32(destFeeToken.address);
18653
- const assetsForOverrides = [...order.output.assets];
18329
+ const assetsForOverrides = [...quotedOutputs];
18654
18330
  if (!assetsForOverrides.some((asset) => asset.token.toLowerCase() === feeTokenAsBytes32.toLowerCase())) {
18655
18331
  assetsForOverrides.push({ token: feeTokenAsBytes32, amount: 0n });
18656
18332
  }
@@ -18673,6 +18349,7 @@ var GasEstimator = class {
18673
18349
  ]);
18674
18350
  const { bundler: bundlerStateOverrides } = stateOverridesResult;
18675
18351
  const fillOptions = {
18352
+ inputs,
18676
18353
  relayerFee: crossChainFees.postRequestFee,
18677
18354
  // Always dispatch with the fee token (see the method docs).
18678
18355
  nativeDispatchFee: 0n,
@@ -18680,7 +18357,7 @@ var GasEstimator = class {
18680
18357
  // bound here would only risk the estimate reverting on a slow bundler round trip.
18681
18358
  // The caller sets the real one on the options it actually signs.
18682
18359
  validUntil: 0n,
18683
- outputs: order.output.assets.map((asset) => ({
18360
+ outputs: quotedOutputs.map((asset) => ({
18684
18361
  ...asset,
18685
18362
  token: normalizeAddressForEvmBytes32(asset.token)
18686
18363
  }))
@@ -18692,12 +18369,8 @@ var GasEstimator = class {
18692
18369
  let maxFeePerGas = gasPrice + gasPrice * BigInt(maxFeeBumpPercent) / 100n;
18693
18370
  const orderForEstimation = { ...order, session: solverAccountAddress };
18694
18371
  const commitment = orderCommitment(orderForEstimation);
18695
- const fillOptionsVersion = await getFillOptionsVersion(this.ctx.dest.client, intentGatewayV2Address);
18696
- const fillOrderCalldata = encodeFillOrder(
18697
- transformOrderForContract(orderForEstimation),
18698
- fillOptions,
18699
- fillOptionsVersion
18700
- );
18372
+ await assertGatewayRelease(this.ctx.dest.client, intentGatewayV2Address);
18373
+ const fillOrderCalldata = encodeFillOrder(transformOrderForContract(orderForEstimation), fillOptions);
18701
18374
  let callGasLimit = 500000n;
18702
18375
  let verificationGasLimit = 100000n;
18703
18376
  let preVerificationGas = 100000n;
@@ -18844,7 +18517,8 @@ var GasEstimator = class {
18844
18517
  totalGasCostWei,
18845
18518
  totalGasInFeeToken: totalGasInSourceFeeToken,
18846
18519
  relayerFeeInSourceFeeToken: crossChainFees.relayerFeeInSourceFeeToken,
18847
- fillOptions
18520
+ fillOptions,
18521
+ inputs
18848
18522
  };
18849
18523
  }
18850
18524
  /**
@@ -19007,35 +18681,47 @@ var OrderStatusChecker = class {
19007
18681
  }
19008
18682
  ctx;
19009
18683
  /**
19010
- * Checks if a V2 order has been filled by reading the commitment storage slot on the destination chain.
19011
- *
19012
- * Reads the storage slot returned by `calculateCommitmentSlotHash` on the IntentGatewayV2 contract.
19013
- * A non-zero value at that slot means the solver has called `fillOrder` and the order is complete
19014
- * from the user's perspective (the beneficiary has received their tokens).
18684
+ * Whether the order is finalized on the destination chain, read from the gateway's `_filled`
18685
+ * getter. It is set by the completing fill, and by a cancellation, which records the refund
18686
+ * beneficiary there. A partially filled order reads false; see {@link getFillProgress}.
19015
18687
  *
19016
18688
  * @param order - The V2 order to check. `order.id` is used as the commitment; if not set it is computed.
19017
- * @returns True if the order has been filled on the destination chain, false otherwise.
19018
18689
  */
19019
18690
  async isOrderFilled(order) {
19020
18691
  const commitment = order.id ?? orderCommitment(order);
19021
- const destStateMachineId = isHex(order.destination) ? hexToString$1(order.destination) : order.destination;
19022
- const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachineId);
19023
- const filledSlot = await this.ctx.dest.client.readContract({
18692
+ const gateway = this.ctx.dest.configService.getIntentGatewayAddress(destinationStateMachine(order));
18693
+ const finalizer = await this.ctx.dest.client.readContract({
19024
18694
  abi: ABI3,
19025
- address: intentGatewayV2Address,
19026
- functionName: "calculateCommitmentSlotHash",
18695
+ address: gateway,
18696
+ functionName: "_filled",
19027
18697
  args: [commitment]
19028
18698
  });
19029
- const filledStatus = await this.ctx.dest.client.getStorageAt({
19030
- address: intentGatewayV2Address,
19031
- slot: filledSlot
19032
- });
19033
- return filledStatus !== "0x0000000000000000000000000000000000000000000000000000000000000000";
18699
+ return !/^0x0{40}$/i.test(finalizer);
18700
+ }
18701
+ /**
18702
+ * Output credited to the order so far, one entry per leg, read from `_partialFills` on the
18703
+ * destination chain. Several solvers can each fill a slice of a same-chain or cross-chain order,
18704
+ * so this, not {@link isOrderFilled}, says how far along an open order is. Surplus paid above
18705
+ * the order's rate is not counted.
18706
+ *
18707
+ * @param order - The V2 order to check. `order.id` is used as the commitment; if not set it is computed.
18708
+ */
18709
+ async getFillProgress(order) {
18710
+ const commitment = order.id ?? orderCommitment(order);
18711
+ const gateway = this.ctx.dest.configService.getIntentGatewayAddress(destinationStateMachine(order));
18712
+ return Promise.all(
18713
+ order.output.assets.map(async (asset, index) => ({
18714
+ token: asset.token,
18715
+ amount: await readLegPartialFill(this.ctx.dest.client, gateway, commitment, index, asset.token)
18716
+ }))
18717
+ );
19034
18718
  }
19035
18719
  /**
19036
18720
  * Checks if a V2 order has been refunded by reading the `_orders` mapping on the source chain.
19037
18721
  *
19038
- * Calls `_orders(commitment, tokenAddress)` for each input token. When the order is placed the
18722
+ * Calls `_orders(commitment, index)` for each input: escrow is held per leg, keyed by the input's
18723
+ * index, so inputs that repeat a token are read separately. A gateway not yet upgraded to per-leg
18724
+ * escrow is read by token instead, see {@link readLegEscrow}. When the order is placed the
19039
18725
  * escrowed amounts are stored there. After a successful refund the contract zeroes them out.
19040
18726
  * An order is considered refunded when all escrowed input amounts have been returned (i.e. are 0).
19041
18727
  *
@@ -19047,14 +18733,14 @@ var OrderStatusChecker = class {
19047
18733
  const commitment = order.id ?? orderCommitment(order);
19048
18734
  const sourceStateMachineId = isHex(order.source) ? hexToString$1(order.source) : order.source;
19049
18735
  const intentGatewayV2Address = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachineId);
19050
- for (const input of order.inputs) {
19051
- const tokenAddress = bytes32ToBytes20(input.token);
19052
- const escrowedAmount = await this.ctx.source.client.readContract({
19053
- abi: ABI3,
19054
- address: intentGatewayV2Address,
19055
- functionName: "_orders",
19056
- args: [commitment, tokenAddress]
19057
- });
18736
+ for (let index = 0; index < order.inputs.length; index++) {
18737
+ const escrowedAmount = await readLegEscrow(
18738
+ this.ctx.source.client,
18739
+ intentGatewayV2Address,
18740
+ commitment,
18741
+ index,
18742
+ order.inputs[index].token
18743
+ );
19058
18744
  if (escrowedAmount !== 0n) {
19059
18745
  return false;
19060
18746
  }
@@ -19062,727 +18748,374 @@ var OrderStatusChecker = class {
19062
18748
  return true;
19063
18749
  }
19064
18750
  };
19065
-
19066
- // src/protocols/intents/liquidity-pool.ts
19067
- function sortPoolSymbols(symbolA, symbolB) {
19068
- return symbolA.toLowerCase() <= symbolB.toLowerCase() ? [symbolA, symbolB] : [symbolB, symbolA];
19069
- }
19070
- function poolSlug(symbolA, symbolB) {
19071
- return sortPoolSymbols(symbolA, symbolB).join("-");
18751
+ function destinationStateMachine(order) {
18752
+ return isHex(order.destination) ? hexToString$1(order.destination) : order.destination;
19072
18753
  }
19073
- function resolveLiquidityPool(symbolA, symbolB) {
19074
- const [token0Symbol, token1Symbol] = sortPoolSymbols(symbolA, symbolB);
19075
- return {
19076
- poolId: `${token0Symbol}-${token1Symbol}`,
19077
- token0Symbol,
19078
- token1Symbol
19079
- };
18754
+ var ORDERBOOK_URLS = {
18755
+ mainnet: "https://orderbook.hyperfx.finance/mainnet/graphql",
18756
+ testnet: "https://orderbook.hyperfx.finance/testnet/graphql"
18757
+ };
18758
+ function orderbookUrlFor(stateMachineId) {
18759
+ return TESTNET_CHAINS.has(stateMachineId) ? ORDERBOOK_URLS.testnet : ORDERBOOK_URLS.mainnet;
19080
18760
  }
19081
-
19082
- // src/protocols/intents/LiquidityEngine.ts
19083
- var INDEXER_FIXED_POINT_DECIMALS = 18;
19084
- var POOL_RATE_SCALE = 10n ** 18n;
19085
- var SELL = "SELL";
19086
- var BUY = "BUY";
19087
- var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT"]);
19088
- var LiquidityEngine = class {
19089
- constructor(queryClient) {
19090
- this.queryClient = queryClient;
19091
- }
19092
- queryClient;
18761
+ var ORDERBOOK_DECIMALS = 18;
18762
+ var ORDERBOOK_SCALE = 10n ** BigInt(ORDERBOOK_DECIMALS);
18763
+ var OrderbookRequestError = class extends Error {
18764
+ constructor(message) {
18765
+ super(`HyperFX orderbook request failed: ${message}`);
18766
+ this.name = "OrderbookRequestError";
18767
+ }
18768
+ };
18769
+ var RATE_FIELDS = "side fillChain rate depthIn depthOut backingLiquidity sourceChains orderCount solverCount";
18770
+ var TOP_OF_BOOK_QUERY = `
18771
+ query TopOfBook($tokenA: String!, $tokenB: String!, $fillChain: String!, $sourceChain: String!) {
18772
+ books { id base quote }
18773
+ aToB: bestRate(tokenIn: $tokenA, tokenOut: $tokenB, fillChain: $fillChain, sourceChain: $sourceChain) { ${RATE_FIELDS} }
18774
+ bToA: bestRate(tokenIn: $tokenB, tokenOut: $tokenA, fillChain: $fillChain, sourceChain: $sourceChain) { ${RATE_FIELDS} }
18775
+ }`;
18776
+ var ROUTE_LIQUIDITY_QUERY = `
18777
+ query RouteLiquidity($route: RouteInput!) {
18778
+ books { id base quote }
18779
+ routeLiquidity(route: $route) {
18780
+ route bestRate depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
18781
+ }
18782
+ }`;
18783
+ var QUOTE_QUERY = `
18784
+ query Quote($route: RouteInput!, $amountIn: BigInt!) {
18785
+ quote(route: $route, amountIn: $amountIn) {
18786
+ route side amountIn amountOut rate fillable depth maxFillableIn
18787
+ fills { orderRate amountOut advertisedSize }
18788
+ }
18789
+ }`;
18790
+ var ORDERBOOK_QUERIES = {
18791
+ topOfBook: TOP_OF_BOOK_QUERY,
18792
+ routeLiquidity: ROUTE_LIQUIDITY_QUERY,
18793
+ quote: QUOTE_QUERY
18794
+ };
18795
+ var HyperFxOrderbook = class {
18796
+ client;
18797
+ /** @param urlOrClient - The orderbook's GraphQL URL, or a configured `graphql-request` client. */
18798
+ constructor(urlOrClient = ORDERBOOK_URLS.mainnet) {
18799
+ this.client = typeof urlOrClient === "string" ? new GraphQLClient(urlOrClient) : urlOrClient;
18800
+ }
19093
18801
  /**
19094
- * Returns liquidity reachable from one source chain on one destination.
19095
- *
19096
- * The caller resolves chain-specific token addresses through chain
19097
- * configuration; this layer only maps those configured symbols onto the
19098
- * indexer's canonical pool and route fields.
19099
- *
19100
- * Destination, unrestricted, and explicit-route capacity are returned as
19101
- * separate values so callers can apply their own source-chain policy.
19102
- *
19103
- * @returns `undefined` only when the indexer has not published a destination
19104
- * pool sample yet.
19105
- */
19106
- async getAvailableLiquidity(params) {
19107
- const pool = resolveLiquidityPool(params.source.symbol, params.destination.symbol);
19108
- const direction = params.source.symbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
19109
- const variables = {
19110
- poolId: pool.poolId,
19111
- sourceChain: params.source.chain,
19112
- destinationChain: params.destination.chain,
19113
- direction
18802
+ * The best bid and ask on `fillChain` for the pair `tokenA`/`tokenB`, counting
18803
+ * only orders a swap from `sourceChain` can use.
18804
+ */
18805
+ async topOfBook(params) {
18806
+ const data = await this.request(TOP_OF_BOOK_QUERY, params);
18807
+ const book = findBook(data.books, params.tokenA, params.tokenB);
18808
+ const rates = [data.aToB, data.bToA].filter((rate) => rate !== null).map(parseRate);
18809
+ return {
18810
+ book,
18811
+ bid: rates.find((rate) => rate.side === "BID") ?? null,
18812
+ ask: rates.find((rate) => rate.side === "ASK") ?? null
19114
18813
  };
19115
- const response = await this.queryClient.request(AVAILABLE_LIQUIDITY, variables);
19116
- if (!response?.poolChainLiquidities?.nodes || !response?.poolRoutes?.nodes) {
19117
- throw new InvalidLiquidityIndexerResponseError("liquidity connections are missing");
19118
- }
19119
- const chainLiquidity = response.poolChainLiquidities.nodes[0];
19120
- if (!chainLiquidity) return void 0;
19121
- const route = response.poolRoutes.nodes[0];
18814
+ }
18815
+ /** Total liquidity on a route, no amount required. */
18816
+ async routeLiquidity(route) {
18817
+ const { books, routeLiquidity: raw } = await this.request(ROUTE_LIQUIDITY_QUERY, { route });
18818
+ const book = findBook(books, route.tokenIn, route.tokenOut);
19122
18819
  return {
19123
- sourceChain: params.source.chain,
19124
- destinationChain: params.destination.chain,
19125
- tokenAddress: normalizeEvmAddress(params.destination.address, "destination token"),
19126
- updatedAt: readIndexerDate(chainLiquidity.lastUpdatedAt, "destination lastUpdatedAt"),
19127
- destination: readLiquiditySlice(chainLiquidity.depth, chainLiquidity.bidCount, "destination"),
19128
- unrestricted: readLiquiditySlice(
19129
- chainLiquidity.unrestrictedDepth,
19130
- chainLiquidity.unrestrictedBidCount,
19131
- "unrestricted"
19132
- ),
19133
- explicitRoute: route ? {
19134
- ...readLiquiditySlice(route.depth, route.bidCount, "explicit route"),
19135
- updatedAt: readIndexerDate(route.lastUpdatedAt, "route lastUpdatedAt")
19136
- } : null
18820
+ route: raw.route,
18821
+ book,
18822
+ side: book.base === route.tokenIn ? "BID" : "ASK",
18823
+ bestRate: raw.bestRate === null ? null : BigInt(raw.bestRate),
18824
+ depthIn: BigInt(raw.depthIn),
18825
+ depthOut: BigInt(raw.depthOut),
18826
+ availableLiquidity: BigInt(raw.availableLiquidity),
18827
+ maxFillableIn: BigInt(raw.maxFillableIn),
18828
+ orderCount: raw.orderCount,
18829
+ solverCount: raw.solverCount
19137
18830
  };
19138
18831
  }
19139
- /**
19140
- * Returns the indexed pool's aggregate buy and sell rates in less-valued
19141
- * quote-token units per one base token.
19142
- *
19143
- * The indexer depth-weights fresh per-chain samples into the pool rates. The
19144
- * source and destination chains remain part of the result because they define
19145
- * the cross-chain route whose configured token symbols were resolved.
19146
- */
19147
- async getBuyAndSellRates(params) {
19148
- const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
19149
- const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
19150
- poolId: pool.poolId
18832
+ /** The clearing price for `amountIn` (1e18 tokenIn) on a route, which may combine several solvers' orders. */
18833
+ async quote(route, amountIn) {
18834
+ const { quote: raw } = await this.request(QUOTE_QUERY, {
18835
+ route,
18836
+ amountIn: amountIn.toString()
19151
18837
  });
19152
- if (!response?.liquidityPools?.nodes) {
19153
- throw new InvalidLiquidityIndexerResponseError("liquidity pool connection is missing");
19154
- }
19155
- const indexedPool = response.liquidityPools.nodes[0];
19156
- if (!indexedPool) return void 0;
19157
- validateIndexedPool(indexedPool, pool);
19158
- const sell = readIndexedRate(indexedPool.sellRate, indexedPool.lastUpdatedAt, "pool sell rate");
19159
- const buy = readIndexedRate(indexedPool.buyRate, indexedPool.lastUpdatedAt, "pool buy rate");
19160
- const inputIsToken0 = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase();
19161
- const direct = inputIsToken0 ? sell : buy;
19162
- const reverse = inputIsToken0 ? buy : sell;
19163
- if (!direct && !reverse) return void 0;
19164
- const quoteTokenSymbol = resolveQuoteTokenSymbol(
19165
- params.tokenInSymbol,
19166
- params.tokenOutSymbol,
19167
- direct?.scaledRate,
19168
- reverse?.scaledRate
19169
- );
19170
- const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
19171
- const orientedBuy = quoteIsTokenOut ? direct : reverse;
19172
- const orientedSell = quoteIsTokenOut ? reverse : direct;
19173
18838
  return {
19174
- baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
19175
- quoteTokenSymbol,
19176
- sourceChain: params.sourceChain,
19177
- destinationChain: params.destinationChain,
19178
- buyRate: orientedBuy ? formatUnits(orientedBuy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
19179
- sellRate: orientedSell ? formatUnits(reciprocalRate(orientedSell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
19180
- buyRateUpdatedAt: orientedBuy?.updatedAt ?? null,
19181
- sellRateUpdatedAt: orientedSell?.updatedAt ?? null
18839
+ route: raw.route,
18840
+ side: raw.side,
18841
+ amountIn: BigInt(raw.amountIn),
18842
+ amountOut: BigInt(raw.amountOut),
18843
+ rate: raw.rate === null ? null : BigInt(raw.rate),
18844
+ fillable: raw.fillable,
18845
+ depth: BigInt(raw.depth),
18846
+ maxFillableIn: BigInt(raw.maxFillableIn),
18847
+ fills: raw.fills.map((fill) => ({
18848
+ orderRate: BigInt(fill.orderRate),
18849
+ amountOut: BigInt(fill.amountOut),
18850
+ advertisedSize: BigInt(fill.advertisedSize)
18851
+ }))
19182
18852
  };
19183
18853
  }
19184
- };
19185
- var InvalidLiquidityIndexerResponseError = class extends Error {
19186
- constructor(reason) {
19187
- super(`Invalid liquidity indexer response: ${reason}`);
19188
- this.name = "InvalidLiquidityIndexerResponseError";
18854
+ async request(query, variables) {
18855
+ try {
18856
+ return await this.client.request(query, variables);
18857
+ } catch (error) {
18858
+ throw new OrderbookRequestError(describeError(error));
18859
+ }
19189
18860
  }
19190
18861
  };
18862
+ function describeError(error) {
18863
+ const graphqlErrors = error?.response?.errors;
18864
+ if (graphqlErrors?.length) return graphqlErrors.map((e) => e.message).join("; ");
18865
+ return error instanceof Error ? error.message : String(error);
18866
+ }
18867
+ function findBook(books, tokenA, tokenB) {
18868
+ const book = books.find(
18869
+ (b) => b.base === tokenA && b.quote === tokenB || b.base === tokenB && b.quote === tokenA
18870
+ );
18871
+ if (!book) throw new OrderbookRequestError(`no book trades ${tokenA} for ${tokenB}`);
18872
+ return book;
18873
+ }
18874
+ function parseRate(raw) {
18875
+ return {
18876
+ side: raw.side,
18877
+ fillChain: raw.fillChain,
18878
+ rate: BigInt(raw.rate),
18879
+ depthIn: BigInt(raw.depthIn),
18880
+ depthOut: BigInt(raw.depthOut),
18881
+ backingLiquidity: BigInt(raw.backingLiquidity),
18882
+ sourceChains: raw.sourceChains,
18883
+ orderCount: raw.orderCount,
18884
+ solverCount: raw.solverCount
18885
+ };
18886
+ }
18887
+
18888
+ // src/protocols/intents/orderbook/types.ts
19191
18889
  var UnsupportedLiquidityAssetError = class extends Error {
19192
18890
  constructor(chain, asset) {
19193
- super(`No configured liquidity asset found for ${asset} on ${chain}`);
18891
+ super(`No configured asset found for ${asset} on ${chain}`);
19194
18892
  this.name = "UnsupportedLiquidityAssetError";
19195
18893
  }
19196
18894
  };
19197
18895
  var UnsupportedLiquidityChainError = class extends Error {
19198
18896
  constructor(chainId) {
19199
- super(`No configured liquidity chain found for chain ID ${chainId}`);
18897
+ super(`No configured chain found for chain ID ${chainId}`);
19200
18898
  this.name = "UnsupportedLiquidityChainError";
19201
18899
  }
19202
18900
  };
19203
- function readLiquiditySlice(depth, providerCount, label) {
19204
- if (!Number.isSafeInteger(providerCount) || providerCount < 0) {
19205
- throw new InvalidLiquidityIndexerResponseError(`${label} provider count is invalid`);
19206
- }
19207
- return { totalLiquidity: formatIndexerAmount(depth, `${label} depth`), providerCount };
19208
- }
19209
- function formatIndexerAmount(value, label) {
19210
- try {
19211
- const amount = BigInt(value);
19212
- if (amount < 0n) throw new Error();
19213
- return formatUnits(amount, INDEXER_FIXED_POINT_DECIMALS);
19214
- } catch {
19215
- throw new InvalidLiquidityIndexerResponseError(`${label} is not a non-negative integer`);
19216
- }
19217
- }
19218
- function readIndexerDate(value, label) {
19219
- const date = new Date(dateStringtoTimestamp(value));
19220
- if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
19221
- return date;
19222
- }
19223
- function readIndexedRate(value, lastUpdatedAt, label) {
19224
- if (value === null) return void 0;
19225
- try {
19226
- const scaledRate = BigInt(value);
19227
- if (scaledRate <= 0n) throw new Error();
19228
- return {
19229
- scaledRate,
19230
- updatedAt: readIndexerDate(lastUpdatedAt, `${label} lastUpdatedAt`)
19231
- };
19232
- } catch (error) {
19233
- if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
19234
- throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
19235
- }
19236
- }
19237
- function validateIndexedPool(indexedPool, expected) {
19238
- if (indexedPool.id.toLowerCase() !== expected.poolId.toLowerCase() || indexedPool.token0Symbol.toLowerCase() !== expected.token0Symbol.toLowerCase() || indexedPool.token1Symbol.toLowerCase() !== expected.token1Symbol.toLowerCase()) {
19239
- throw new InvalidLiquidityIndexerResponseError(`pool identity does not match ${expected.poolId}`);
19240
- }
19241
- }
19242
- function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
19243
- const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
19244
- const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
19245
- if (inputIsUsdStable !== outputIsUsdStable) return inputIsUsdStable ? tokenOutSymbol : tokenInSymbol;
19246
- if (directRate !== void 0) return directRate >= POOL_RATE_SCALE ? tokenOutSymbol : tokenInSymbol;
19247
- if (reverseRate !== void 0) return reverseRate >= POOL_RATE_SCALE ? tokenInSymbol : tokenOutSymbol;
19248
- throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
19249
- }
19250
- function reciprocalRate(rate, label) {
19251
- const numerator = POOL_RATE_SCALE * POOL_RATE_SCALE;
19252
- const reciprocal = (numerator + rate - 1n) / rate;
19253
- if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
19254
- return reciprocal;
19255
- }
19256
-
19257
- // src/protocols/intents/quote/types.ts
19258
- var UnsupportedIntentQuoteStrategyError = class extends Error {
19259
- constructor(strategy) {
19260
- super(`Unsupported intent quote strategy: ${strategy}`);
19261
- this.name = "UnsupportedIntentQuoteStrategyError";
19262
- }
19263
- };
19264
- var UnsupportedIntentQuotePairError = class extends Error {
19265
- constructor(params) {
18901
+ var OrderbookQuoteNotConvergedError = class extends Error {
18902
+ constructor(route, rounds, lastAmountIn) {
19266
18903
  super(
19267
- `No ${params.quoteSource ?? "Uniswap v4 pool config"} found for ${params.tokenIn} -> ${params.tokenOut} on ${params.source} -> ${params.destination}`
18904
+ `No input for ${route.tokenIn} -> ${route.tokenOut} on ${route.sourceChain} -> ${route.destinationChain} delivered the requested output within ${rounds} quotes; the last tried ${lastAmountIn} raw units`
19268
18905
  );
19269
- this.name = "UnsupportedIntentQuotePairError";
19270
- }
19271
- };
19272
- var PhantomSnapshotUnavailableError = class extends Error {
19273
- constructor(tokenA, tokenB) {
19274
- super(`No Phantom order price snapshot found for ${tokenA} -> ${tokenB}`);
19275
- this.name = "PhantomSnapshotUnavailableError";
19276
- }
19277
- };
19278
- var InvalidPhantomSnapshotError = class extends Error {
19279
- constructor(commitment, reason) {
19280
- super(`Invalid Phantom order price snapshot ${commitment}: ${reason}`);
19281
- this.name = "InvalidPhantomSnapshotError";
19282
- }
19283
- };
19284
- var IndexedRateUnavailableError = class extends Error {
19285
- constructor(params) {
19286
- const route = params.source && params.destination && params.tokenIn && params.tokenOut ? ` for ${params.tokenIn} -> ${params.tokenOut} on ${params.source} -> ${params.destination}` : "";
19287
- const side = params.side ? ` ${params.side}` : "";
19288
- super(`No indexed${side} rate available${route}`);
19289
- this.name = "IndexedRateUnavailableError";
19290
- }
18906
+ this.route = route;
18907
+ this.rounds = rounds;
18908
+ this.lastAmountIn = lastAmountIn;
18909
+ this.name = "OrderbookQuoteNotConvergedError";
18910
+ }
18911
+ route;
18912
+ rounds;
18913
+ lastAmountIn;
19291
18914
  };
19292
- var InvalidIndexedRateError = class extends Error {
19293
- constructor(reason) {
19294
- super(`Invalid indexed intent rate: ${reason}`);
19295
- this.name = "InvalidIndexedRateError";
18915
+ var InsufficientOrderbookLiquidityError = class extends Error {
18916
+ constructor(route, maxFillableIn) {
18917
+ super(
18918
+ `The HyperFX orderbook cannot fill ${route.tokenIn} -> ${route.tokenOut} on ${route.sourceChain} -> ${route.destinationChain}; max fillable input is ${maxFillableIn} raw units`
18919
+ );
18920
+ this.route = route;
18921
+ this.maxFillableIn = maxFillableIn;
18922
+ this.name = "InsufficientOrderbookLiquidityError";
19296
18923
  }
18924
+ route;
18925
+ maxFillableIn;
19297
18926
  };
19298
- var BPS_DENOMINATOR = 10000n;
19299
- function validateQuoteParams(params) {
19300
- const hasAmountIn = params.amountIn !== void 0;
19301
- const hasAmountOut = params.amountOut !== void 0;
19302
- if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
19303
- if (params.amountIn !== void 0 && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
19304
- if (params.amountOut !== void 0 && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
19305
- if (params.tokenIn.toLowerCase() === params.tokenOut.toLowerCase()) {
19306
- throw new Error("tokenIn and tokenOut cannot be the same");
19307
- }
19308
- }
19309
- async function readProtocolFeeBps(chainConfigService, source) {
19310
- const gatewayAddress = chainConfigService.getIntentGatewayAddress(source.stateMachineId);
19311
- if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === zeroAddress) {
19312
- throw new Error(`IntentGatewayV2 is not configured for chain ${source.stateMachineId}`);
19313
- }
19314
- const gatewayParams = await source.client.readContract({
19315
- address: gatewayAddress,
19316
- abi: IntentGatewayV2_default.ABI,
19317
- functionName: "params"
19318
- });
19319
- const protocolFeeBps = Array.isArray(gatewayParams) ? BigInt(gatewayParams[4]) : BigInt(gatewayParams.protocolFeeBps ?? 0);
19320
- if (protocolFeeBps < 0n || protocolFeeBps >= BPS_DENOMINATOR) {
19321
- throw new Error(`Invalid IntentGateway protocol fee: ${protocolFeeBps} bps`);
19322
- }
19323
- return protocolFeeBps;
19324
- }
19325
- function deductProtocolFee(amount, protocolFeeBps) {
19326
- if (protocolFeeBps <= 0n) return amount;
19327
- const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
19328
- return amount - fee;
19329
- }
19330
- function grossUpForProtocolFee(netAmount, protocolFeeBps) {
19331
- if (protocolFeeBps <= 0n) return netAmount;
19332
- if (protocolFeeBps >= BPS_DENOMINATOR) throw new Error("protocolFeeBps must be less than 10,000");
19333
- return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
19334
- }
19335
- function divCeil(numerator, denominator) {
19336
- if (denominator <= 0n) throw new Error("denominator must be greater than zero");
19337
- return (numerator + denominator - 1n) / denominator;
19338
- }
19339
18927
 
19340
- // src/protocols/intents/quote/uniswapV4.ts
19341
- var UNISWAP_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
19342
- var UniswapV4IntentQuoteStrategy = class {
19343
- constructor(chainConfigService) {
19344
- this.chainConfigService = chainConfigService;
19345
- }
19346
- chainConfigService;
19347
- baseQuoteClient;
19348
- async quote(params, source, destination) {
18928
+ // src/protocols/intents/orderbook/market.ts
18929
+ var MAX_EXACT_OUTPUT_ROUNDS = 16;
18930
+ var OrderbookMarket = class {
18931
+ constructor(configService, orderbook) {
18932
+ this.configService = configService;
18933
+ this.orderbook = orderbook;
18934
+ }
18935
+ configService;
18936
+ orderbook;
18937
+ async quoteIntent(params, sourceChain, destinationChain) {
19349
18938
  validateQuoteParams(params);
19350
- const protocolFeeBps = await readProtocolFeeBps(this.chainConfigService, source);
19351
- const quoteClient = this.resolveQuoteClient(source, destination);
19352
- const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, destination.stateMachineId);
19353
- return params.amountIn !== void 0 ? this.quoteExactInput({ params, client: quoteClient, protocolFeeBps, poolConfig }) : this.quoteExactOutput({ params, client: quoteClient, protocolFeeBps, poolConfig });
19354
- }
19355
- resolveQuoteClient(source, destination) {
19356
- if (source.stateMachineId === UNISWAP_INTENT_QUOTE_CHAIN) return source.client;
19357
- if (destination.stateMachineId === UNISWAP_INTENT_QUOTE_CHAIN) return destination.client;
19358
- if (this.baseQuoteClient) return this.baseQuoteClient;
19359
- const rpcUrl = this.chainConfigService.getRpcUrl(UNISWAP_INTENT_QUOTE_CHAIN);
19360
- if (!rpcUrl) throw new Error(`RPC URL is not configured for ${UNISWAP_INTENT_QUOTE_CHAIN}`);
19361
- const baseQuoteClient = createPublicClient({
19362
- chain: base,
19363
- transport: http(rpcUrl)
19364
- });
19365
- this.baseQuoteClient = baseQuoteClient;
19366
- return baseQuoteClient;
19367
- }
19368
- resolvePoolConfig(params, source, destination) {
19369
- const override = params.uniswapV4?.poolKey;
19370
- if (override) {
19371
- const poolKey = normalizePoolKey(override);
19372
- const tokenInForQuote = getAddress(override.currencyIn ?? params.tokenIn);
19373
- if (tokenInForQuote !== poolKey.currency0 && tokenInForQuote !== poolKey.currency1) {
19374
- throw new Error(
19375
- `Input currency ${tokenInForQuote} is not part of the override pool (${poolKey.currency0}, ${poolKey.currency1}). For cross-chain quotes pass uniswapV4.poolKey.currencyIn with the Base-side input currency address.`
19376
- );
19377
- }
19378
- return {
19379
- poolKey,
19380
- quoterAddress: this.resolveQuoterAddress(UNISWAP_INTENT_QUOTE_CHAIN, override.quoterAddress),
19381
- tokenInForQuote
19382
- };
19383
- }
19384
- const poolConfig = this.resolveConfiguredPool(params, source, destination, UNISWAP_INTENT_QUOTE_CHAIN);
19385
- if (poolConfig) return poolConfig;
19386
- throw new UnsupportedIntentQuotePairError({
19387
- source,
19388
- destination,
19389
- tokenIn: params.tokenIn,
19390
- tokenOut: params.tokenOut
19391
- });
19392
- }
19393
- resolveConfiguredPool(params, sourceChain, destinationChain, quoteChain) {
19394
- for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(quoteChain)) {
19395
- const resolvedPool = this.resolveConfiguredPoolTokens(quoteChain, pool);
19396
- if (!resolvedPool) continue;
19397
- const tokenInForQuote = this.matchPoolToken(params.tokenIn, sourceChain, resolvedPool);
19398
- const tokenOutForQuote = this.matchPoolToken(params.tokenOut, destinationChain, resolvedPool);
19399
- if (!tokenInForQuote || !tokenOutForQuote || tokenInForQuote.symbol === tokenOutForQuote.symbol) continue;
19400
- const { currency0, currency1 } = sortCurrencies(resolvedPool[0].address, resolvedPool[1].address);
19401
- return {
19402
- poolKey: {
19403
- currency0,
19404
- currency1,
19405
- fee: pool.fee,
19406
- tickSpacing: pool.tickSpacing,
19407
- hooks: getAddress(pool.hooks ?? zeroAddress)
19408
- },
19409
- quoterAddress: this.resolveQuoterAddress(quoteChain),
19410
- tokenInForQuote: tokenInForQuote.address
19411
- };
19412
- }
19413
- return null;
19414
- }
19415
- resolveQuoterAddress(chain, override) {
19416
- const address = override ?? this.chainConfigService.getUniswapV4QuoterAddress(chain);
19417
- if (!address || address === "0x" || address === zeroAddress) {
19418
- throw new Error(`Uniswap V4 quoter is not configured for chain ${chain}`);
18939
+ const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
18940
+ const tokenOut = this.assetByAddress(destinationChain, params.tokenOut);
18941
+ const route = toRoute(tokenIn, tokenOut);
18942
+ if (params.amountIn !== void 0) {
18943
+ const quote2 = await this.orderbook().quote(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
18944
+ const amountOut2 = quote2.fillable ? fromOrderbookAmount(quote2.amountOut, tokenOut.decimals) : 0n;
18945
+ if (amountOut2 === 0n) throw this.insufficient(route, quote2.maxFillableIn, tokenIn);
18946
+ return buildQuote("EXACT_INPUT", params.amountIn, amountOut2, quote2, tokenIn, tokenOut);
19419
18947
  }
19420
- return getAddress(address);
19421
- }
19422
- resolveConfiguredPoolTokens(chain, pool) {
19423
- const first = this.resolveConfiguredPoolToken(chain, pool.tokens[0]);
19424
- const second = this.resolveConfiguredPoolToken(chain, pool.tokens[1]);
19425
- return first && second ? [first, second] : null;
19426
- }
19427
- resolveConfiguredPoolToken(chain, symbol) {
19428
- const address = this.chainConfigService.getAssetAddress(chain, symbol);
19429
- if (!address || address === "0x") return null;
19430
- return { symbol, address: getAddress(address) };
19431
- }
19432
- matchPoolToken(tokenAddress, orderChain, poolTokens) {
19433
- const addressMatch = poolTokens.find(
19434
- (poolToken) => poolToken.address.toLowerCase() === tokenAddress.toLowerCase()
19435
- );
19436
- if (addressMatch) return addressMatch;
19437
- const metadata = this.chainConfigService.getAssetMetadataByAddress(orderChain, tokenAddress);
19438
- if (!metadata) return null;
19439
- return poolTokens.find((poolToken) => poolToken.symbol === metadata.symbol) ?? null;
19440
- }
19441
- async quoteExactInput(args) {
19442
- const amountIn = args.params.amountIn;
19443
- if (amountIn === void 0) throw new Error("amountIn is required for an exact-input quote");
19444
- const swapAmountIn = deductProtocolFee(amountIn, args.protocolFeeBps);
19445
- const amountOut = await this.readV4QuoteExactInput(args.client, args.poolConfig, swapAmountIn);
19446
- return {
19447
- strategy: "uniswap_v4",
19448
- tradeType: "EXACT_INPUT",
19449
- amountIn,
18948
+ const amountOut = params.amountOut;
18949
+ const quote = await this.quoteExactOutput(route, toOrderbookAmount(amountOut, tokenOut.decimals), tokenIn);
18950
+ return buildQuote(
18951
+ "EXACT_OUTPUT",
18952
+ fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
19450
18953
  amountOut,
19451
- quoteMetadata: {
19452
- quoteChain: UNISWAP_INTENT_QUOTE_CHAIN,
19453
- poolKey: args.poolConfig.poolKey,
19454
- quoterAddress: args.poolConfig.quoterAddress,
19455
- protocolFeeBps: args.protocolFeeBps
19456
- }
19457
- };
18954
+ quote,
18955
+ tokenIn,
18956
+ tokenOut
18957
+ );
19458
18958
  }
19459
- async quoteExactOutput(args) {
19460
- const amountOut = args.params.amountOut;
19461
- if (amountOut === void 0) throw new Error("amountOut is required for an exact-output quote");
19462
- const swapAmountIn = await this.readV4QuoteExactOutput(args.client, args.poolConfig, amountOut);
19463
- const amountIn = grossUpForProtocolFee(swapAmountIn, args.protocolFeeBps);
18959
+ async availableLiquidity(params, sourceChain, destinationChain) {
18960
+ const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
18961
+ const tokenOut = this.assetByAddress(destinationChain, params.tokenOut);
18962
+ const liquidity = await this.orderbook().routeLiquidity(toRoute(tokenIn, tokenOut));
18963
+ const [base3, quote] = liquidity.side === "BID" ? [tokenIn, tokenOut] : [tokenOut, tokenIn];
19464
18964
  return {
19465
- strategy: "uniswap_v4",
19466
- tradeType: "EXACT_OUTPUT",
19467
- amountIn,
19468
- amountOut,
19469
- quoteMetadata: {
19470
- quoteChain: UNISWAP_INTENT_QUOTE_CHAIN,
19471
- poolKey: args.poolConfig.poolKey,
19472
- quoterAddress: args.poolConfig.quoterAddress,
19473
- protocolFeeBps: args.protocolFeeBps
19474
- }
18965
+ sourceChain: tokenIn.chain,
18966
+ destinationChain: tokenOut.chain,
18967
+ tokenInSymbol: tokenIn.symbol,
18968
+ tokenOutSymbol: tokenOut.symbol,
18969
+ tokenAddress: tokenOut.address,
18970
+ route: liquidity.route,
18971
+ side: liquidity.side,
18972
+ baseTokenSymbol: base3.symbol,
18973
+ quoteTokenSymbol: quote.symbol,
18974
+ bestRate: formatOptional(liquidity.bestRate),
18975
+ availableLiquidity: format(liquidity.availableLiquidity),
18976
+ depthIn: format(liquidity.depthIn),
18977
+ depthOut: format(liquidity.depthOut),
18978
+ maxFillableIn: format(liquidity.maxFillableIn),
18979
+ orderCount: liquidity.orderCount,
18980
+ solverCount: liquidity.solverCount
19475
18981
  };
19476
18982
  }
19477
- async readV4QuoteExactInput(client, poolConfig, amountIn) {
19478
- const data = encodeFunctionData({
19479
- abi: UNISWAP_V4_QUOTER_ABI,
19480
- functionName: "quoteExactInputSingle",
19481
- args: [
19482
- {
19483
- poolKey: poolConfig.poolKey,
19484
- zeroForOne: getZeroForOne(poolConfig.tokenInForQuote, poolConfig.poolKey),
19485
- exactAmount: amountIn,
19486
- hookData: "0x"
19487
- }
19488
- ]
19489
- });
19490
- const response = await client.call({ to: poolConfig.quoterAddress, data });
19491
- if (!response.data || response.data === "0x") {
19492
- throw new Error(`Uniswap V4 quoter at ${poolConfig.quoterAddress} returned no data`);
19493
- }
19494
- const [amountOut] = decodeFunctionResult({
19495
- abi: UNISWAP_V4_QUOTER_ABI,
19496
- functionName: "quoteExactInputSingle",
19497
- data: response.data
19498
- });
19499
- return amountOut;
19500
- }
19501
- async readV4QuoteExactOutput(client, poolConfig, amountOut) {
19502
- const data = encodeFunctionData({
19503
- abi: UNISWAP_V4_QUOTER_ABI,
19504
- functionName: "quoteExactOutputSingle",
19505
- args: [
19506
- {
19507
- poolKey: poolConfig.poolKey,
19508
- zeroForOne: getZeroForOne(poolConfig.tokenInForQuote, poolConfig.poolKey),
19509
- exactAmount: amountOut,
19510
- hookData: "0x"
19511
- }
19512
- ]
18983
+ async buyAndSellRates(params) {
18984
+ const tokenIn = this.assetBySymbol(params.sourceChain, params.tokenInSymbol);
18985
+ const tokenOut = this.assetBySymbol(params.destinationChain, params.tokenOutSymbol);
18986
+ const { book, bid, ask } = await this.orderbook().topOfBook({
18987
+ tokenA: tokenIn.symbol,
18988
+ tokenB: tokenOut.symbol,
18989
+ fillChain: tokenOut.chain,
18990
+ sourceChain: tokenIn.chain
19513
18991
  });
19514
- const response = await client.call({ to: poolConfig.quoterAddress, data });
19515
- if (!response.data || response.data === "0x") {
19516
- throw new Error(`Uniswap V4 quoter at ${poolConfig.quoterAddress} returned no data`);
19517
- }
19518
- const [amountIn] = decodeFunctionResult({
19519
- abi: UNISWAP_V4_QUOTER_ABI,
19520
- functionName: "quoteExactOutputSingle",
19521
- data: response.data
19522
- });
19523
- return amountIn;
19524
- }
19525
- };
19526
- function normalizePoolKey(poolKey) {
19527
- return {
19528
- currency0: getAddress(poolKey.currency0),
19529
- currency1: getAddress(poolKey.currency1),
19530
- fee: poolKey.fee,
19531
- tickSpacing: poolKey.tickSpacing,
19532
- hooks: getAddress(poolKey.hooks)
19533
- };
19534
- }
19535
- function sortCurrencies(tokenIn, tokenOut) {
19536
- const input = getAddress(tokenIn);
19537
- const output = getAddress(tokenOut);
19538
- return BigInt(input) < BigInt(output) ? { currency0: input, currency1: output } : { currency0: output, currency1: input };
19539
- }
19540
- function getZeroForOne(tokenIn, poolKey) {
19541
- return getAddress(tokenIn).toLowerCase() === getAddress(poolKey.currency0).toLowerCase();
19542
- }
19543
-
19544
- // src/protocols/intents/quote/phantomSnapshot.ts
19545
- var PHANTOM_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
19546
- var BASE_SNAPSHOT_ASSET_BY_ORDER_ASSET = {
19547
- USDC: "USDC",
19548
- USDT: "USDC",
19549
- cNGN: "cNGN"
19550
- };
19551
- var SUPPORTED_BASE_SNAPSHOT_PAIRS = /* @__PURE__ */ new Set(["USDC:cNGN", "cNGN:USDC"]);
19552
- var PhantomSnapshotIntentQuoteStrategy = class {
19553
- constructor(chainConfigService, getQueryClient) {
19554
- this.chainConfigService = chainConfigService;
19555
- this.getQueryClient = getQueryClient;
19556
- this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
19557
- }
19558
- chainConfigService;
19559
- getQueryClient;
19560
- pairResolver;
19561
- async quote(params, source, destination) {
19562
- validateQuoteParams(params);
19563
- const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
19564
- if (!pair) {
19565
- throw new UnsupportedIntentQuotePairError({
19566
- source: source.stateMachineId,
19567
- destination: destination.stateMachineId,
19568
- tokenIn: params.tokenIn,
19569
- tokenOut: params.tokenOut,
19570
- quoteSource: "Phantom snapshot pair"
19571
- });
19572
- }
19573
- const queryClient = this.getQueryClient();
19574
- const [protocolFeeBps, snapshot] = await Promise.all([
19575
- readProtocolFeeBps(this.chainConfigService, source),
19576
- _queryLatestPhantomOrderPriceSnapshot({
19577
- tokenA: pair.tokenA,
19578
- tokenB: pair.tokenB,
19579
- queryClient
19580
- })
19581
- ]);
19582
- if (!snapshot) throw new PhantomSnapshotUnavailableError(pair.tokenA, pair.tokenB);
19583
- this.validateSnapshot(snapshot);
19584
- return this.quoteSnapshot(params, protocolFeeBps, snapshot);
19585
- }
19586
- quoteSnapshot(params, protocolFeeBps, snapshot) {
19587
- if (params.amountIn !== void 0) {
19588
- const netAmountIn2 = deductProtocolFee(params.amountIn, protocolFeeBps);
19589
- const amountOut = netAmountIn2 * snapshot.medianPrice / snapshot.standardAmount;
19590
- if (amountOut <= 0n) {
19591
- throw new InvalidPhantomSnapshotError(snapshot.commitment, "quote rounds down to zero output");
19592
- }
19593
- return this.buildResult("EXACT_INPUT", params.amountIn, amountOut, protocolFeeBps, snapshot);
19594
- }
19595
- if (params.amountOut === void 0) throw new Error("Quote amount is missing after validation");
19596
- const netAmountIn = divCeil(params.amountOut * snapshot.standardAmount, snapshot.medianPrice);
19597
- const amountIn = grossUpForProtocolFee(netAmountIn, protocolFeeBps);
19598
- return this.buildResult("EXACT_OUTPUT", amountIn, params.amountOut, protocolFeeBps, snapshot);
19599
- }
19600
- buildResult(tradeType, amountIn, amountOut, protocolFeeBps, snapshot) {
18992
+ const bidRate = bid?.rate ?? null;
18993
+ const askRate = ask?.rate ?? null;
18994
+ const mid = bidRate !== null && askRate !== null ? (bidRate + askRate) / 2n : null;
18995
+ const spread = bidRate !== null && askRate !== null && askRate >= bidRate ? askRate - bidRate : null;
19601
18996
  return {
19602
- strategy: "phantom_snapshot",
19603
- tradeType,
19604
- amountIn,
19605
- amountOut,
19606
- quoteMetadata: {
19607
- quoteChain: PHANTOM_INTENT_QUOTE_CHAIN,
19608
- commitment: snapshot.commitment,
19609
- tokenA: snapshot.tokenA,
19610
- tokenB: snapshot.tokenB,
19611
- standardAmount: snapshot.standardAmount,
19612
- medianPrice: snapshot.medianPrice,
19613
- lowestPrice: snapshot.lowestPrice,
19614
- highestPrice: snapshot.highestPrice,
19615
- blockNumber: snapshot.blockNumber,
19616
- snapshotTime: snapshot.snapshotTime,
19617
- bidCount: snapshot.bidCount,
19618
- protocolFeeBps
19619
- }
18997
+ baseTokenSymbol: book.base,
18998
+ quoteTokenSymbol: book.quote,
18999
+ sourceChain: tokenIn.chain,
19000
+ destinationChain: tokenOut.chain,
19001
+ bid: formatOptional(bidRate),
19002
+ ask: formatOptional(askRate),
19003
+ mid: formatOptional(mid),
19004
+ spread: formatOptional(spread),
19005
+ spreadBps: spread !== null && mid ? Number(spread * 1000000n / mid) / 100 : null
19620
19006
  };
19621
19007
  }
19622
- validateSnapshot(snapshot) {
19623
- if (snapshot.standardAmount <= 0n) {
19624
- throw new InvalidPhantomSnapshotError(snapshot.commitment, "standardAmount must be greater than zero");
19625
- }
19626
- if (snapshot.medianPrice <= 0n) {
19627
- throw new InvalidPhantomSnapshotError(snapshot.commitment, "medianPrice must be greater than zero");
19628
- }
19629
- if (snapshot.bidCount <= 0) {
19630
- throw new InvalidPhantomSnapshotError(snapshot.commitment, "bidCount must be greater than zero");
19631
- }
19632
- if (Number.isNaN(snapshot.snapshotTime.getTime())) {
19633
- throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
19634
- }
19635
- }
19636
- };
19637
- var PhantomSnapshotPairResolver = class {
19638
- constructor(chainConfigService) {
19639
- this.chainConfigService = chainConfigService;
19640
- }
19641
- chainConfigService;
19642
- resolve(params, sourceStateMachineId, destinationStateMachineId) {
19643
- const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
19644
- const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
19645
- if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
19646
- return { tokenA: tokenIn.address, tokenB: tokenOut.address };
19647
- }
19648
- resolveBaseSnapshotToken(chain, tokenAddress) {
19649
- const orderAsset = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.symbol;
19650
- if (!orderAsset) return;
19651
- const snapshotAsset = BASE_SNAPSHOT_ASSET_BY_ORDER_ASSET[orderAsset];
19652
- if (!snapshotAsset) return;
19653
- const address = this.chainConfigService.getAssetAddress(PHANTOM_INTENT_QUOTE_CHAIN, snapshotAsset);
19654
- if (!isConfiguredAddress(address)) return;
19655
- return { symbol: snapshotAsset, address };
19656
- }
19657
- };
19658
- function isSupportedSnapshotPair(tokenA, tokenB) {
19659
- return SUPPORTED_BASE_SNAPSHOT_PAIRS.has(`${tokenA}:${tokenB}`);
19660
- }
19661
- function isConfiguredAddress(address) {
19662
- return Boolean(address && address !== "0x" && !/^0x0{40}$/i.test(address));
19663
- }
19664
- var INDEXED_RATE_DECIMALS = 18;
19665
- var INDEXED_RATE_SCALE = 10n ** BigInt(INDEXED_RATE_DECIMALS);
19666
- var IndexedRateIntentQuoteStrategy = class {
19667
- constructor(chainConfigService, getQueryClient) {
19668
- this.chainConfigService = chainConfigService;
19669
- this.getQueryClient = getQueryClient;
19670
- }
19671
- chainConfigService;
19672
- getQueryClient;
19673
- async quote(params, source, destination) {
19674
- validateQuoteParams(params);
19675
- const sourceConfig = getConfigByStateMachineId(source.stateMachineId);
19676
- const destinationConfig = getConfigByStateMachineId(destination.stateMachineId);
19677
- if (!sourceConfig) throw new UnsupportedLiquidityChainError(source.stateMachineId);
19678
- if (!destinationConfig) throw new UnsupportedLiquidityChainError(destination.stateMachineId);
19679
- const tokenIn = this.resolveAsset(sourceConfig.stateMachineId, params.tokenIn);
19680
- const tokenOut = this.resolveAsset(destinationConfig.stateMachineId, params.tokenOut);
19681
- const [protocolFeeBps, rates] = await Promise.all([
19682
- readProtocolFeeBps(this.chainConfigService, source),
19683
- new LiquidityEngine(this.getQueryClient()).getBuyAndSellRates({
19684
- sourceChain: sourceConfig.stateMachineId,
19685
- destinationChain: destinationConfig.stateMachineId,
19686
- tokenInSymbol: tokenIn.symbol,
19687
- tokenOutSymbol: tokenOut.symbol
19688
- })
19689
- ]);
19690
- if (!rates) {
19691
- throw new IndexedRateUnavailableError({
19692
- source: sourceConfig.stateMachineId,
19693
- destination: destinationConfig.stateMachineId,
19694
- tokenIn: tokenIn.symbol,
19695
- tokenOut: tokenOut.symbol
19696
- });
19697
- }
19698
- const selectedRate = selectIndexedRate(rates, tokenIn.symbol, tokenOut.symbol);
19699
- return quoteWithIndexedRate(params, tokenIn, tokenOut, selectedRate, rates, protocolFeeBps);
19008
+ /**
19009
+ * The orderbook only quotes an input amount, so an output is priced by
19010
+ * guessing the input from the best rate and raising it at each clearing
19011
+ * price until the quote delivers `targetOut`.
19012
+ */
19013
+ async quoteExactOutput(route, targetOut, tokenIn) {
19014
+ const orderbook = this.orderbook();
19015
+ const liquidity = await orderbook.routeLiquidity(route);
19016
+ if (liquidity.bestRate === null) throw this.insufficient(route, 0n, tokenIn);
19017
+ const inputUnit = toOrderbookAmount(1n, tokenIn.decimals);
19018
+ let amountIn = roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
19019
+ let lastAmountIn = amountIn;
19020
+ for (let round = 0; round < MAX_EXACT_OUTPUT_ROUNDS; round++) {
19021
+ if (amountIn > liquidity.maxFillableIn) throw this.insufficient(route, liquidity.maxFillableIn, tokenIn);
19022
+ const quote = await orderbook.quote(route, amountIn);
19023
+ if (quote.fillable && quote.amountOut >= targetOut) return quote;
19024
+ lastAmountIn = amountIn;
19025
+ const next = quote.rate === null ? amountIn : roundUpTo(requiredInput(quote.side, targetOut, quote.rate), inputUnit);
19026
+ amountIn = next > amountIn ? next : amountIn + inputUnit;
19027
+ }
19028
+ throw new OrderbookQuoteNotConvergedError(
19029
+ route,
19030
+ MAX_EXACT_OUTPUT_ROUNDS,
19031
+ fromOrderbookAmount(lastAmountIn, tokenIn.decimals)
19032
+ );
19700
19033
  }
19701
- resolveAsset(chain, address) {
19702
- const asset = this.chainConfigService.getAssetMetadataByAddress(chain, address);
19703
- if (!asset) throw new UnsupportedLiquidityAssetError(chain, address);
19704
- const { decimals } = asset;
19705
- if (decimals === void 0 || !Number.isSafeInteger(decimals) || decimals < 0) {
19706
- throw new InvalidIndexedRateError(`decimals are not configured for ${asset.symbol} on ${chain}`);
19707
- }
19708
- return { ...asset, decimals };
19034
+ insufficient(route, maxFillableIn, tokenIn) {
19035
+ return new InsufficientOrderbookLiquidityError(route, fromOrderbookAmount(maxFillableIn, tokenIn.decimals));
19709
19036
  }
19710
- };
19711
- function selectIndexedRate(rates, tokenInSymbol, tokenOutSymbol) {
19712
- if (tokenInSymbol === rates.baseTokenSymbol && tokenOutSymbol === rates.quoteTokenSymbol) {
19713
- return readIndexedRate2(
19714
- "buy",
19715
- rates.buyRate,
19716
- rates.buyRateUpdatedAt,
19717
- rates,
19718
- tokenInSymbol,
19719
- tokenOutSymbol
19037
+ assetByAddress(chain, address) {
19038
+ const resolvedChain = resolveChain(chain);
19039
+ return withDecimals(
19040
+ resolvedChain,
19041
+ address,
19042
+ this.configService.getAssetMetadataByAddress(resolvedChain, address)
19720
19043
  );
19721
19044
  }
19722
- if (tokenInSymbol === rates.quoteTokenSymbol && tokenOutSymbol === rates.baseTokenSymbol) {
19723
- return readIndexedRate2(
19724
- "sell",
19725
- rates.sellRate,
19726
- rates.sellRateUpdatedAt,
19727
- rates,
19728
- tokenInSymbol,
19729
- tokenOutSymbol
19730
- );
19045
+ assetBySymbol(chain, symbol) {
19046
+ const resolvedChain = resolveChain(chain);
19047
+ return withDecimals(resolvedChain, symbol, this.configService.getAssetMetadataBySymbol(resolvedChain, symbol));
19731
19048
  }
19732
- throw new InvalidIndexedRateError(
19733
- `indexed pair ${rates.baseTokenSymbol}/${rates.quoteTokenSymbol} does not match ${tokenInSymbol}/${tokenOutSymbol}`
19734
- );
19049
+ };
19050
+ function resolveChain(chain) {
19051
+ const stateMachineId = getConfigByStateMachineId(chain)?.stateMachineId;
19052
+ if (!stateMachineId) throw new UnsupportedLiquidityChainError(chain);
19053
+ return stateMachineId;
19735
19054
  }
19736
- function readIndexedRate2(side, rate, updatedAt, rates, tokenInSymbol, tokenOutSymbol) {
19737
- if (!rate || !updatedAt) {
19738
- throw new IndexedRateUnavailableError({
19739
- source: rates.sourceChain,
19740
- destination: rates.destinationChain,
19741
- tokenIn: tokenInSymbol,
19742
- tokenOut: tokenOutSymbol,
19743
- side
19744
- });
19745
- }
19746
- try {
19747
- const scaledRate = parseUnits(rate, INDEXED_RATE_DECIMALS);
19748
- if (scaledRate <= 0n || Number.isNaN(updatedAt.getTime())) throw new Error();
19749
- return { side, rate, scaledRate, updatedAt };
19750
- } catch {
19751
- throw new InvalidIndexedRateError(`${side} rate or timestamp is invalid`);
19055
+ function withDecimals(chain, asset, metadata) {
19056
+ if (!metadata) throw new UnsupportedLiquidityAssetError(chain, asset);
19057
+ const { decimals } = metadata;
19058
+ if (decimals === void 0 || !Number.isSafeInteger(decimals) || decimals < 0 || decimals > ORDERBOOK_DECIMALS) {
19059
+ throw new UnsupportedLiquidityAssetError(chain, `${metadata.symbol} (decimals are not configured)`);
19752
19060
  }
19061
+ return { chain, symbol: metadata.symbol, address: metadata.address, decimals };
19062
+ }
19063
+ function validateQuoteParams(params) {
19064
+ const hasAmountIn = params.amountIn !== void 0;
19065
+ const hasAmountOut = params.amountOut !== void 0;
19066
+ if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
19067
+ if (params.amountIn !== void 0 && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
19068
+ if (params.amountOut !== void 0 && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
19069
+ }
19070
+ function toRoute(tokenIn, tokenOut) {
19071
+ return {
19072
+ tokenIn: tokenIn.symbol,
19073
+ tokenOut: tokenOut.symbol,
19074
+ sourceChain: tokenIn.chain,
19075
+ destinationChain: tokenOut.chain
19076
+ };
19753
19077
  }
19754
- function quoteWithIndexedRate(params, tokenIn, tokenOut, selectedRate, rates, protocolFeeBps) {
19755
- const inputUnit = 10n ** BigInt(tokenIn.decimals);
19756
- const outputUnit = 10n ** BigInt(tokenOut.decimals);
19757
- if (params.amountIn !== void 0) {
19758
- const netAmountIn2 = deductProtocolFee(params.amountIn, protocolFeeBps);
19759
- const amountOut = selectedRate.side === "buy" ? netAmountIn2 * selectedRate.scaledRate * outputUnit / (inputUnit * INDEXED_RATE_SCALE) : netAmountIn2 * outputUnit * INDEXED_RATE_SCALE / (inputUnit * selectedRate.scaledRate);
19760
- if (amountOut <= 0n) throw new InvalidIndexedRateError("quote rounds down to zero output");
19761
- return buildResult("EXACT_INPUT", params.amountIn, amountOut, selectedRate, rates, protocolFeeBps);
19762
- }
19763
- if (params.amountOut === void 0) throw new Error("Quote amount is missing after validation");
19764
- const netAmountIn = selectedRate.side === "buy" ? divCeil(params.amountOut * inputUnit * INDEXED_RATE_SCALE, selectedRate.scaledRate * outputUnit) : divCeil(params.amountOut * inputUnit * selectedRate.scaledRate, outputUnit * INDEXED_RATE_SCALE);
19765
- const amountIn = grossUpForProtocolFee(netAmountIn, protocolFeeBps);
19766
- return buildResult("EXACT_OUTPUT", amountIn, params.amountOut, selectedRate, rates, protocolFeeBps);
19078
+ function requiredInput(side, amountOut, rate) {
19079
+ return side === "BID" ? divCeil(amountOut * ORDERBOOK_SCALE, rate) : divCeil(amountOut * rate, ORDERBOOK_SCALE);
19767
19080
  }
19768
- function buildResult(tradeType, amountIn, amountOut, selectedRate, rates, protocolFeeBps) {
19081
+ function buildQuote(tradeType, amountIn, amountOut, quote, tokenIn, tokenOut) {
19082
+ if (quote.rate === null) throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
19083
+ const [base3, quoteToken] = quote.side === "BID" ? [tokenIn, tokenOut] : [tokenOut, tokenIn];
19769
19084
  return {
19770
- strategy: "indexed_rates",
19771
19085
  tradeType,
19772
19086
  amountIn,
19773
19087
  amountOut,
19774
19088
  quoteMetadata: {
19775
- sourceChain: rates.sourceChain,
19776
- destinationChain: rates.destinationChain,
19777
- baseTokenSymbol: rates.baseTokenSymbol,
19778
- quoteTokenSymbol: rates.quoteTokenSymbol,
19779
- rateSide: selectedRate.side,
19780
- rate: selectedRate.rate,
19781
- rateUpdatedAt: selectedRate.updatedAt,
19782
- protocolFeeBps
19089
+ sourceChain: tokenIn.chain,
19090
+ destinationChain: tokenOut.chain,
19091
+ route: quote.route,
19092
+ side: quote.side,
19093
+ baseTokenSymbol: base3.symbol,
19094
+ quoteTokenSymbol: quoteToken.symbol,
19095
+ rate: format(quote.rate),
19096
+ maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
19097
+ orderCount: quote.fills.length
19783
19098
  }
19784
19099
  };
19785
19100
  }
19101
+ function toOrderbookAmount(amount, decimals) {
19102
+ return amount * 10n ** BigInt(ORDERBOOK_DECIMALS - decimals);
19103
+ }
19104
+ function fromOrderbookAmount(amount, decimals) {
19105
+ return amount / 10n ** BigInt(ORDERBOOK_DECIMALS - decimals);
19106
+ }
19107
+ function roundUpTo(amount, unit) {
19108
+ return divCeil(amount, unit) * unit;
19109
+ }
19110
+ function divCeil(numerator, denominator) {
19111
+ return (numerator + denominator - 1n) / denominator;
19112
+ }
19113
+ function format(amount) {
19114
+ return formatUnits(amount, ORDERBOOK_DECIMALS);
19115
+ }
19116
+ function formatOptional(amount) {
19117
+ return amount === null ? null : format(amount);
19118
+ }
19786
19119
 
19787
19120
  // src/protocols/intents/IntentGateway.ts
19788
19121
  var ORDER_FEE_GAS_PRICE_BUMP_POLICY = {
@@ -19822,8 +19155,10 @@ var IntentGateway = class _IntentGateway {
19822
19155
  bidManager;
19823
19156
  /** Estimates gas costs for filling an order and converts them to fee-token amounts. */
19824
19157
  gasEstimator;
19825
- /** Quote strategies for pricing orders before placement, keyed by strategy name. */
19826
- quoteStrategies;
19158
+ /** The HyperFX orderbook quotes, liquidity and rates are read from. */
19159
+ orderbook;
19160
+ /** Prices intents, liquidity and rates from {@link orderbook}. */
19161
+ market;
19827
19162
  /**
19828
19163
  * Private constructor — use {@link IntentGateway.create} instead.
19829
19164
  *
@@ -19867,17 +19202,8 @@ var IntentGateway = class _IntentGateway {
19867
19202
  this.bidManager = bidManager;
19868
19203
  this.gasEstimator = gasEstimator;
19869
19204
  this._crypto = crypto;
19870
- this.quoteStrategies = {
19871
- indexed_rates: new IndexedRateIntentQuoteStrategy(
19872
- dest.configService,
19873
- () => this.requireIndexer().queryClient
19874
- ),
19875
- phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
19876
- dest.configService,
19877
- () => this.requireIndexer().queryClient
19878
- ),
19879
- uniswap_v4: new UniswapV4IntentQuoteStrategy(dest.configService)
19880
- };
19205
+ this.orderbook = new HyperFxOrderbook(orderbookUrlFor(dest.config.stateMachineId));
19206
+ this.market = new OrderbookMarket(dest.configService, () => this.orderbook);
19881
19207
  }
19882
19208
  /**
19883
19209
  * Creates an initialized IntentGateway instance.
@@ -19923,89 +19249,66 @@ var IntentGateway = class _IntentGateway {
19923
19249
  }
19924
19250
  }
19925
19251
  /**
19926
- * Quotes an intent between this gateway's source and destination chains.
19252
+ * Points quotes, liquidity and rate reads at a HyperFX orderbook other than
19253
+ * the destination chain's network deployment ({@link ORDERBOOK_URLS}).
19254
+ * Returns `this` for chaining.
19255
+ *
19256
+ * @param orderbook - The orderbook's GraphQL URL, or a configured client.
19257
+ */
19258
+ withOrderbook(orderbook) {
19259
+ this.orderbook = typeof orderbook === "string" ? new HyperFxOrderbook(orderbook) : orderbook;
19260
+ return this;
19261
+ }
19262
+ /**
19263
+ * Quotes an intent between this gateway's source and destination chains from
19264
+ * the HyperFX orderbook. Provide exactly one of `amountIn` or `amountOut`, in
19265
+ * raw token units.
19927
19266
  *
19928
- * Uses the indexer's latest aggregate directional pool rate by default. Pass
19929
- * `strategy: "phantom_snapshot"` or `strategy: "uniswap_v4"` only when
19930
- * explicitly requesting a legacy quote source. Provide exactly one of
19931
- * `amountIn` or `amountOut`.
19267
+ * An exact-input quote is the orderbook's clearing price for `amountIn`,
19268
+ * which may combine several solvers' orders. An exact-output quote finds the
19269
+ * smallest input whose clearing price delivers `amountOut`. A cross-chain
19270
+ * route only counts orders whose solvers accept the source chain.
19932
19271
  *
19933
- * The gateway's source and destination chains resolve the configured order
19934
- * tokens; the indexer supplies the depth-weighted pool rate. Returned
19935
- * `amountIn`/`amountOut` already account for the gateway's protocol fee
19936
- * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order inputs.
19272
+ * The orderbook's rates already carry the gateway protocol fee, so the
19273
+ * returned amounts can be placed as the order's inputs and outputs directly.
19937
19274
  *
19938
- * @param params - Token pair, amount, and optional strategy/pool overrides.
19939
- * @returns The quoted amounts plus strategy-specific metadata.
19940
- * @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
19941
- * @throws {UnsupportedIntentQuotePairError} When the selected strategy does not support the pair.
19942
- * @throws {IndexedRateUnavailableError} When the requested direction has no indexed rate.
19275
+ * @throws {InsufficientOrderbookLiquidityError} When the route cannot fill the amount.
19276
+ * @throws {OrderbookRequestError} When the orderbook is unreachable or trades no book for the pair.
19943
19277
  */
19944
19278
  async quoteIntent(params) {
19945
- const source = { stateMachineId: this.source.config.stateMachineId, client: this.source.client };
19946
- const destination = { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client };
19947
- const strategy = params.strategy ?? "indexed_rates";
19948
- const handler = this.quoteStrategies[strategy];
19949
- if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
19950
- return handler.quote({ ...params, strategy }, source, destination);
19279
+ return this.market.quoteIntent(params, this.source.config.stateMachineId, this.dest.config.stateMachineId);
19951
19280
  }
19952
19281
  /**
19953
- * Returns indexed destination liquidity and its source-routing slices.
19954
- *
19955
- * Destination, unrestricted, and explicit-route capacity come exclusively
19956
- * from the indexer's pair-centric liquidity entities. The SDK does not decide
19957
- * whether unrestricted bidders cover the source chain. Amounts reflect the
19958
- * latest rolling sample; they are not reservations or fill guarantees.
19282
+ * Returns the orderbook liquidity serving a swap of `tokenIn` on the source
19283
+ * chain for `tokenOut` on the destination chain: the best rate, true and
19284
+ * virtual depth, and the largest input the route can fill.
19959
19285
  *
19960
- * Requires a prior call to {@link withQueryClient}.
19286
+ * Amounts reflect the live book; they are not reservations or fill guarantees.
19961
19287
  */
19962
19288
  async queryAvailableLiquidity(params) {
19963
- const { queryClient } = this.requireIndexer();
19964
- const sourceStateMachineId = getConfigByStateMachineId(this.source.config.stateMachineId)?.stateMachineId;
19965
- const destinationStateMachineId = getConfigByStateMachineId(this.dest.config.stateMachineId)?.stateMachineId;
19966
- if (!sourceStateMachineId) throw new UnsupportedLiquidityChainError(this.source.config.stateMachineId);
19967
- if (!destinationStateMachineId) throw new UnsupportedLiquidityChainError(this.dest.config.stateMachineId);
19968
- const sourceToken = this.source.configService.getAssetMetadataByAddress(sourceStateMachineId, params.tokenIn);
19969
- const destinationToken = this.dest.configService.getAssetMetadataByAddress(
19970
- destinationStateMachineId,
19971
- params.tokenOut
19289
+ return this.market.availableLiquidity(
19290
+ params,
19291
+ this.source.config.stateMachineId,
19292
+ this.dest.config.stateMachineId
19972
19293
  );
19973
- if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceStateMachineId, params.tokenIn);
19974
- if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationStateMachineId, params.tokenOut);
19975
- return new LiquidityEngine(queryClient).getAvailableLiquidity({
19976
- source: {
19977
- chain: sourceStateMachineId,
19978
- ...sourceToken
19979
- },
19980
- destination: {
19981
- chain: destinationStateMachineId,
19982
- ...destinationToken
19983
- }
19984
- });
19985
19294
  }
19986
19295
  /**
19987
- * Returns aggregate indexed pool buy and sell rates in less-valued quote-token
19988
- * units without requiring token addresses. Symbols are matched
19989
- * case-insensitively; chain IDs resolve configured token deployments.
19296
+ * Returns the orderbook's best bid and ask for a pair, with the mid and
19297
+ * spread, in quote-token units per one base token. Only orders filled on the
19298
+ * destination chain that accept swaps from the source chain are counted.
19299
+ * Symbols are matched case-insensitively; chain IDs resolve configured token
19300
+ * deployments.
19990
19301
  */
19991
19302
  async queryBuyAndSellRates(params) {
19992
- const { queryClient } = this.requireIndexer();
19993
19303
  const sourceChain = chainConfigs[params.sourceChainId]?.stateMachineId;
19994
19304
  const destinationChain = chainConfigs[params.destinationChainId]?.stateMachineId;
19995
19305
  if (!sourceChain) throw new UnsupportedLiquidityChainError(params.sourceChainId);
19996
19306
  if (!destinationChain) throw new UnsupportedLiquidityChainError(params.destinationChainId);
19997
- const sourceToken = this.source.configService.getAssetMetadataBySymbol(sourceChain, params.tokenInSymbol);
19998
- const destinationToken = this.dest.configService.getAssetMetadataBySymbol(
19999
- destinationChain,
20000
- params.tokenOutSymbol
20001
- );
20002
- if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceChain, params.tokenInSymbol);
20003
- if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationChain, params.tokenOutSymbol);
20004
- return new LiquidityEngine(queryClient).getBuyAndSellRates({
19307
+ return this.market.buyAndSellRates({
20005
19308
  sourceChain,
20006
19309
  destinationChain,
20007
- tokenInSymbol: sourceToken.symbol,
20008
- tokenOutSymbol: destinationToken.symbol
19310
+ tokenInSymbol: params.tokenInSymbol,
19311
+ tokenOutSymbol: params.tokenOutSymbol
20009
19312
  });
20010
19313
  }
20011
19314
  /**
@@ -20256,6 +19559,8 @@ var IntentGateway = class _IntentGateway {
20256
19559
  /**
20257
19560
  * Returns both the native token cost and the relayer fee for cancelling an
20258
19561
  * order. Use `relayerFee` to approve the ERC-20 spend before submitting.
19562
+ * Same-chain orders use the direct local route for either `from` option and
19563
+ * return `{ nativeValue: 0n, relayerFee: 0n }`.
20259
19564
  *
20260
19565
  * Delegates to {@link OrderCanceller.quoteCancelOrder}.
20261
19566
  *
@@ -20279,9 +19584,17 @@ var IntentGateway = class _IntentGateway {
20279
19584
  *
20280
19585
  * Delegates to {@link OrderCanceller.cancelOrder}.
20281
19586
  *
19587
+ * Same-chain orders always use the direct source-gateway route. The order
19588
+ * user may call through the deadline; any account may call strictly after
19589
+ * it, pays the transaction gas, and cannot change the refund beneficiary.
19590
+ * The SDK yields unsigned transaction fields and accepts either the caller's
19591
+ * signed raw transaction or its already-broadcast transaction hash.
19592
+ *
20282
19593
  * @param order - The order to cancel.
20283
- * @param indexerClient - Indexer client used for ISMP request status streaming.
20284
- * @param options - Choose the initiation side. Defaults to source-side cancellation.
19594
+ * @param indexerClient - Indexer client used for cross-chain ISMP request
19595
+ * status streaming; the same-chain route does not access it.
19596
+ * @param options - Choose the cross-chain initiation side. Defaults to source;
19597
+ * ignored for routing when source and destination are the same chain.
20285
19598
  * @yields {@link CancelEvent} objects describing each cancellation stage.
20286
19599
  */
20287
19600
  async *cancelOrder(order, indexerClient, options = {}) {
@@ -20411,7 +19724,7 @@ var IntentGateway = class _IntentGateway {
20411
19724
  if (estimate.totalGasCostWei === 0n || estimate.totalGasInFeeToken === 0n) {
20412
19725
  throw new Error("Gas estimation failed");
20413
19726
  }
20414
- const fees = isSameChain ? estimate.totalGasInFeeToken * 2n : (estimate.totalGasInFeeToken + estimate.relayerFeeInSourceFeeToken) * 105n / 100n;
19727
+ const fees = isSameChain ? estimate.totalGasInFeeToken * 2n : ((estimate.totalGasInFeeToken + estimate.relayerFeeInSourceFeeToken) * 105n + 99n) / 100n;
20415
19728
  const { address: feeToken } = await this.source.getFeeTokenWithDecimals();
20416
19729
  return {
20417
19730
  fees,
@@ -20444,17 +19757,27 @@ var IntentGateway = class _IntentGateway {
20444
19757
  return this._crypto.decodeERC7821Execute(callData);
20445
19758
  }
20446
19759
  /**
20447
- * Checks whether an order has been filled on the destination chain.
19760
+ * Checks whether an order is finalized on the destination chain.
20448
19761
  *
20449
19762
  * Delegates to {@link OrderStatusChecker.isOrderFilled}.
20450
19763
  *
20451
19764
  * @param order - The order to check.
20452
- * @returns `true` if the order's commitment slot on the destination chain is
20453
- * non-zero (i.e. `fillOrder` has been called successfully).
19765
+ * @returns `true` once a completing fill or a destination-side cancellation
19766
+ * has finalized the order; `false` while it is open or only partly filled.
20454
19767
  */
20455
19768
  async isOrderFilled(order) {
20456
19769
  return this.orderStatusChecker.isOrderFilled(order);
20457
19770
  }
19771
+ /**
19772
+ * Output credited to the order so far, one entry per leg.
19773
+ *
19774
+ * Delegates to {@link OrderStatusChecker.getFillProgress}.
19775
+ *
19776
+ * @param order - The order to check.
19777
+ */
19778
+ async getFillProgress(order) {
19779
+ return this.orderStatusChecker.getFillProgress(order);
19780
+ }
20458
19781
  /**
20459
19782
  * Checks whether all escrowed inputs for an order have been refunded on the
20460
19783
  * source chain.
@@ -20462,8 +19785,8 @@ var IntentGateway = class _IntentGateway {
20462
19785
  * Delegates to {@link OrderStatusChecker.isOrderRefunded}.
20463
19786
  *
20464
19787
  * @param order - The order to check.
20465
- * @returns `true` if every input token's escrowed amount has been zeroed out
20466
- * in the `_orders` mapping on the source chain.
19788
+ * @returns `true` if every input's escrowed amount has been zeroed out
19789
+ * in the per-leg `_orders` mapping on the source chain.
20467
19790
  */
20468
19791
  async isOrderRefunded(order) {
20469
19792
  return this.orderStatusChecker.isOrderRefunded(order);
@@ -20500,9 +19823,7 @@ var IntentGateway = class _IntentGateway {
20500
19823
  }
20501
19824
  requireIndexer() {
20502
19825
  if (!this.indexer) {
20503
- throw new Error(
20504
- "IntentGateway: call withQueryClient(queryClient) before using indexer-backed methods or Phantom quotes"
20505
- );
19826
+ throw new Error("IntentGateway: call withQueryClient(queryClient) before using indexer-backed methods");
20506
19827
  }
20507
19828
  return this.indexer;
20508
19829
  }
@@ -20525,74 +19846,64 @@ var IntentGateway = class _IntentGateway {
20525
19846
  const { queryClient, pollInterval, logger } = this.requireIndexer();
20526
19847
  const streamLogger = logger.withTag("[orderStatusStream]");
20527
19848
  const TERMINAL = ["FILLED", "REDEEMED", "REFUNDED"];
19849
+ const latestOrderStatus = (order2) => {
19850
+ const latest = order2.statuses[order2.statuses.length - 1];
19851
+ if (latest.status === "CANCELLED") {
19852
+ for (let i = order2.statuses.length - 1; i >= 0; i--) {
19853
+ if (TERMINAL.includes(order2.statuses[i].status)) return order2.statuses[i];
19854
+ }
19855
+ }
19856
+ return latest;
19857
+ };
20528
19858
  let order;
20529
19859
  while (!order) {
20530
19860
  await sleep(pollInterval);
20531
19861
  order = await _queryOrderInternal({ commitmentHash: commitment, queryClient, logger });
20532
19862
  }
20533
19863
  streamLogger.trace("`Order` found");
20534
- const latestStatus = order.statuses[order.statuses.length - 1];
19864
+ let latestStatus = latestOrderStatus(order);
20535
19865
  yield { status: latestStatus.status, metadata: latestStatus.metadata };
20536
19866
  if (TERMINAL.includes(latestStatus.status)) return;
20537
19867
  while (true) {
20538
19868
  await sleep(pollInterval);
20539
19869
  const updatedOrder = await _queryOrderInternal({ commitmentHash: commitment, queryClient, logger });
20540
19870
  if (!updatedOrder) continue;
20541
- const newLatestStatus = updatedOrder.statuses[updatedOrder.statuses.length - 1];
19871
+ const newLatestStatus = latestOrderStatus(updatedOrder);
20542
19872
  if (newLatestStatus.status !== latestStatus.status) {
19873
+ latestStatus = newLatestStatus;
20543
19874
  yield { status: newLatestStatus.status, metadata: newLatestStatus.metadata };
20544
19875
  if (TERMINAL.includes(newLatestStatus.status)) return;
20545
19876
  }
20546
19877
  }
20547
19878
  }
20548
19879
  };
20549
- function rpcFetch() {
20550
- const f = globalThis.fetch;
20551
- if (typeof f !== "function") {
20552
- throw new Error("No fetch available; call setAggregationFetch() before using the aggregation helpers");
20553
- }
20554
- return f;
19880
+
19881
+ // src/protocols/intents/liquidity-pool.ts
19882
+ function sortPoolSymbols(symbolA, symbolB) {
19883
+ return symbolA.toLowerCase() <= symbolB.toLowerCase() ? [symbolA, symbolB] : [symbolB, symbolA];
20555
19884
  }
20556
- var PhantomRpcError = class extends Error {
20557
- constructor(message, cause) {
20558
- super(message);
20559
- this.cause = cause;
20560
- this.name = "PhantomRpcError";
20561
- }
20562
- cause;
20563
- };
20564
- async function rpcCall(url, payload) {
20565
- let lastErr;
20566
- for (let attempt = 0; attempt < 4; attempt++) {
20567
- if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, 150 * attempt));
20568
- let timer;
20569
- try {
20570
- const timeout = new Promise((_, reject) => {
20571
- timer = setTimeout(() => reject(new Error(`rpc timeout: ${url}`)), 12e3);
20572
- });
20573
- const response = await Promise.race([
20574
- rpcFetch()(url, {
20575
- method: "POST",
20576
- headers: { accept: "application/json", "content-type": "application/json" },
20577
- body: JSON.stringify(payload)
20578
- }),
20579
- timeout
20580
- ]);
20581
- const body = await response.json();
20582
- if (body?.error) {
20583
- lastErr = new Error(`rpc error: ${JSON.stringify(body.error).slice(0, 200)}`);
20584
- continue;
20585
- }
20586
- return body;
20587
- } catch (err) {
20588
- lastErr = err;
20589
- } finally {
20590
- if (timer) clearTimeout(timer);
20591
- }
20592
- }
20593
- throw new PhantomRpcError(`RPC call failed after 4 attempts: ${url}`, lastErr);
19885
+ function poolSlug(symbolA, symbolB) {
19886
+ return sortPoolSymbols(symbolA, symbolB).join("-");
19887
+ }
19888
+
19889
+ // src/protocols/intents/rateFill.ts
19890
+ function ceilDiv(numerator, denominator) {
19891
+ return numerator === 0n ? 0n : (numerator - 1n) / denominator + 1n;
19892
+ }
19893
+ function previewRateFill(escrow, required, filled, take, offered) {
19894
+ if (escrow <= 0n || required <= 0n || take <= 0n || offered <= 0n) {
19895
+ throw new Error("RateFillInvalidAmount");
19896
+ }
19897
+ if (filled < 0n || filled >= required) throw new Error("RateFillNoProgress");
19898
+ if (ceilDiv(take * required, escrow) > offered) throw new Error("RateBelowOrder");
19899
+ const uncappedCredit = take * required / escrow;
19900
+ const credit = uncappedCredit < required - filled ? uncappedCredit : required - filled;
19901
+ const release = escrow * (filled + credit) / required - escrow * filled / required;
19902
+ if (credit === 0n || release === 0n) throw new Error("RateFillNoProgress");
19903
+ const ratePayment = ceilDiv(offered * release, take);
19904
+ const delivered = ratePayment > credit ? ratePayment : credit;
19905
+ return { credit, release, delivered, surplus: delivered - credit };
20594
19906
  }
20595
- var FILL_ORDER_ABI = IntentGatewayV2_default.ABI;
20596
19907
  var DECLARATION_V1 = 1;
20597
19908
  var DECLARATION_V2 = 2;
20598
19909
  var PAYMASTER_ADDRESS_BYTES = 20;
@@ -20790,40 +20101,6 @@ function encodeAcceptedSourceChains(chains2) {
20790
20101
  function decodeAcceptedSourceChains(paymasterAndData) {
20791
20102
  return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
20792
20103
  }
20793
- var UNISWAP_QUOTE_HAIRCUT_BPS = 10n;
20794
- function haircut(amount, bps) {
20795
- return amount * (10000n - bps) / 10000n;
20796
- }
20797
- function applyUniswapQuoteHaircut(amount) {
20798
- return haircut(amount, UNISWAP_QUOTE_HAIRCUT_BPS);
20799
- }
20800
- var SELECTOR_GATEWAY_PARAMS = "0xcff0ab96";
20801
- var GATEWAY_PARAMS_WORDS = 6;
20802
- var GATEWAY_PARAMS_FEE_WORD = 4;
20803
- async function readProtocolFeeHaircutBps(evmRpcUrl, gatewayAddress) {
20804
- const result = await rpcCall(evmRpcUrl, {
20805
- id: 1,
20806
- jsonrpc: "2.0",
20807
- method: "eth_call",
20808
- params: [{ to: gatewayAddress, data: SELECTOR_GATEWAY_PARAMS }, "latest"]
20809
- });
20810
- const hex = result.result;
20811
- if (typeof hex !== "string" || hex.length < 2 + 64 * GATEWAY_PARAMS_WORDS) {
20812
- throw new PhantomRpcError(`IntentGateway.params() returned no usable result from ${gatewayAddress} on ${evmRpcUrl}`);
20813
- }
20814
- const start = 2 + 64 * GATEWAY_PARAMS_FEE_WORD;
20815
- const protocolFeeBps = BigInt(`0x${hex.slice(start, start + 64)}`);
20816
- if (protocolFeeBps >= 10000n) {
20817
- throw new PhantomRpcError(`IntentGateway ${gatewayAddress} reports an implausible protocol fee: ${protocolFeeBps} bps`);
20818
- }
20819
- return protocolFeeBps;
20820
- }
20821
- function applyProtocolFeeHaircut(amount, protocolFeeBps) {
20822
- return haircut(amount, protocolFeeBps);
20823
- }
20824
- FILL_ORDER_ABI.find(
20825
- (item) => item?.type === "function" && item?.name === "fillOrder"
20826
- )?.inputs?.[0];
20827
20104
  var TokenGateway = class {
20828
20105
  source;
20829
20106
  dest;
@@ -25039,9 +24316,9 @@ async function teleport(teleport_param) {
25039
24316
  if (scaleEncodedAssetId === null) {
25040
24317
  throw new Error("Unknown asset id provided");
25041
24318
  }
25042
- const destinationStateMachine = normalizeStateMachineId(params.destination);
25043
- const destination = convertStateMachineIdToEnum(destinationStateMachine);
25044
- const recipient = hexToBytes(normalizeAddressForStateMachine(params.recipient, destinationStateMachine));
24319
+ const destinationStateMachine2 = normalizeStateMachineId(params.destination);
24320
+ const destination = convertStateMachineIdToEnum(destinationStateMachine2);
24321
+ const recipient = hexToBytes(normalizeAddressForStateMachine(params.recipient, destinationStateMachine2));
25045
24322
  const teleportParams = {
25046
24323
  destination,
25047
24324
  recepient: Array.from(recipient),
@@ -25294,6 +24571,6 @@ async function teleportDot(param_) {
25294
24571
  return stream;
25295
24572
  }
25296
24573
 
25297
- export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, CHAINS_WITHOUT_VALID_UNTIL, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, FILL_ORDER_V1_ABI, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, INCLUSION_TIMEOUT_MS, IndexedRateUnavailableError, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidIndexedRateError, InvalidLiquidityIndexerResponseError, InvalidPhantomSnapshotError, IsmpClient, LEGACY_FILL_OPTIONS_IMPLEMENTATIONS, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PERMIT2_SPONSORSHIP_BYTES, PLACE_ORDER_SELECTOR, PhantomSnapshotUnavailableError, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, UNISWAP_QUOTE_HAIRCUT_BPS, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, applyProtocolFeeHaircut, applyUniswapQuoteHaircut, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodeFillOrder, decodePhantomBidDeclaration, decodePhantomBidPaymasterAndData, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeFillOrder, encodeISMPMessage, encodePhantomBidDeclaration, encodePhantomBidPaymasterAndData, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getFillOptionsVersion, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, poolSlug, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, readProtocolFeeHaircutBps, requestCommitmentKey, resetFillOptionsVersionCache, responseCommitmentKey, retryPromise, sortPoolSymbols, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
24574
+ export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, CONTRACT_VERSION_ABI, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, FILL_ORDER_SELECTOR, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, HyperFxOrderbook, INCLUSION_TIMEOUT_MS, InsufficientOrderbookLiquidityError, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, IsmpClient, MAX_DECLARED_ENTRIES, MOCK_ADDRESS, ORDERBOOK_DECIMALS, ORDERBOOK_QUERIES, ORDERBOOK_URLS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, OrderbookQuoteNotConvergedError, OrderbookRequestError, PACKED_USEROP_TYPEHASH, PERMIT2_SPONSORSHIP_BYTES, PLACE_ORDER_SELECTOR, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SUPPORTED_INTENTS_VERSION, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, USE_ETHERSCAN_CHAINS, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, assertGatewayRelease, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodeFillOrder, decodePhantomBidDeclaration, decodePhantomBidPaymasterAndData, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeFillOrder, encodeISMPMessage, encodePhantomBidDeclaration, encodePhantomBidPaymasterAndData, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, isCanonicalEvmToken, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, orderbookUrlFor, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, poolSlug, postRequestCommitment, previewRateFill, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, readLegEscrow, readLegPartialFill, requestCommitmentKey, responseCommitmentKey, retryPromise, sortPoolSymbols, supportsRateFills, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
25298
24575
  //# sourceMappingURL=index.js.map
25299
24576
  //# sourceMappingURL=index.js.map