@hyperbridge/sdk 2.8.2 → 2.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.d.ts +365 -51
- package/dist/browser/index.js +732 -291
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +739 -289
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +69 -16
- package/dist/node/index.d.ts +69 -16
- package/dist/node/index.js +732 -291
- package/dist/node/index.js.map +1 -1
- package/dist/node/{intents-helpers-D_km9I2f.d.cts → intents-helpers-CkPAsLHB.d.cts} +332 -40
- package/dist/node/{intents-helpers-D_km9I2f.d.ts → intents-helpers-CkPAsLHB.d.ts} +332 -40
- package/dist/node/intents-helpers.cjs +394 -39
- package/dist/node/intents-helpers.cjs.map +1 -1
- package/dist/node/intents-helpers.d.cts +1 -1
- package/dist/node/intents-helpers.d.ts +1 -1
- package/dist/node/intents-helpers.js +391 -40
- package/dist/node/intents-helpers.js.map +1 -1
- package/package.json +155 -153
package/dist/node/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimis
|
|
|
7
7
|
import { TronWeb } from 'tronweb';
|
|
8
8
|
import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
|
|
9
9
|
import { match } from 'ts-pattern';
|
|
10
|
-
import { WsProvider, ApiPromise, Keyring } from '@polkadot/api';
|
|
10
|
+
import { WsProvider, ApiPromise, HttpProvider, Keyring } from '@polkadot/api';
|
|
11
11
|
import { Struct, Vector, u8, Bytes, Enum, Tuple, _void, u64, u32, Option, bool, u128 } from 'scale-ts';
|
|
12
12
|
import { keccakAsU8a, decodeAddress, keccakAsHex, xxhashAsU8a, blake2AsU8a } from '@polkadot/util-crypto';
|
|
13
13
|
import { hexToU8a, u8aToHex, u8aConcat, stringToU8a, isHex as isHex$1, u8aToString } from '@polkadot/util';
|
|
@@ -2756,18 +2756,25 @@ var chainConfigs = {
|
|
|
2756
2756
|
DAI: "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3",
|
|
2757
2757
|
USDC: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
|
|
2758
2758
|
USDT: "0x55d398326f99059ff775485246999027b3197955",
|
|
2759
|
-
EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209"
|
|
2759
|
+
EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209",
|
|
2760
|
+
cNGN: "0xa8AEA66B361a8d53e8865c62D142167Af28Af058"
|
|
2760
2761
|
},
|
|
2761
2762
|
tokenDecimals: {
|
|
2762
2763
|
USDC: 18,
|
|
2763
2764
|
USDT: 18,
|
|
2765
|
+
// 6, not 18 — cNGN keeps the same decimals it has on every other chain, unlike the
|
|
2766
|
+
// Binance-pegged stables above. Every phantom standard_amount and pool rate divides
|
|
2767
|
+
// by this, so the divergence from its neighbours here is load-bearing, not a typo.
|
|
2768
|
+
cNGN: 6,
|
|
2764
2769
|
EXT: 18
|
|
2765
2770
|
},
|
|
2766
2771
|
tokenStorageSlots: {
|
|
2767
2772
|
USDT: { balanceSlot: 1, allowanceSlot: 2 },
|
|
2768
2773
|
USDC: { balanceSlot: 1, allowanceSlot: 2 },
|
|
2769
2774
|
WETH: { balanceSlot: 3, allowanceSlot: 4 },
|
|
2770
|
-
DAI: { balanceSlot: 0, allowanceSlot: 0 }
|
|
2775
|
+
DAI: { balanceSlot: 0, allowanceSlot: 0 },
|
|
2776
|
+
cNGN: { balanceSlot: 201, allowanceSlot: 202 }
|
|
2777
|
+
// custom upgradeable layout, as on Base
|
|
2771
2778
|
},
|
|
2772
2779
|
addresses: {
|
|
2773
2780
|
IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
|
|
@@ -5232,11 +5239,21 @@ var ChainConfigService = class {
|
|
|
5232
5239
|
* it, so a new asset is added once in `chain.ts` and nowhere else.
|
|
5233
5240
|
*/
|
|
5234
5241
|
getAssetBySymbol(chain, symbol) {
|
|
5235
|
-
|
|
5242
|
+
return this.getAssetMetadataBySymbol(chain, symbol)?.address;
|
|
5243
|
+
}
|
|
5244
|
+
/** Resolves a configured token symbol case-insensitively on a specific chain. */
|
|
5245
|
+
getAssetMetadataBySymbol(chain, symbol) {
|
|
5246
|
+
const config = this.getConfig(chain);
|
|
5247
|
+
const assets = config?.assets;
|
|
5236
5248
|
if (!assets) return void 0;
|
|
5237
5249
|
const target = symbol.trim().toUpperCase();
|
|
5238
5250
|
for (const [key, address] of Object.entries(assets)) {
|
|
5239
|
-
if (key.toUpperCase()
|
|
5251
|
+
if (key.toUpperCase() !== target) continue;
|
|
5252
|
+
return {
|
|
5253
|
+
symbol: key,
|
|
5254
|
+
address,
|
|
5255
|
+
decimals: config.tokenDecimals?.[key]
|
|
5256
|
+
};
|
|
5240
5257
|
}
|
|
5241
5258
|
return void 0;
|
|
5242
5259
|
}
|
|
@@ -7799,6 +7816,28 @@ function encodeISMPMessage(message) {
|
|
|
7799
7816
|
}
|
|
7800
7817
|
var OFFCHAIN_BID_PREFIX = new TextEncoder().encode("intents::bid::");
|
|
7801
7818
|
var OFFCHAIN_PHANTOM_PREFIX = new TextEncoder().encode("intents::phantom::order::");
|
|
7819
|
+
var HYPERBRIDGE_TYPES_BUNDLE = {
|
|
7820
|
+
spec: {
|
|
7821
|
+
nexus: { hasher: keccakAsU8a },
|
|
7822
|
+
gargantua: { hasher: keccakAsU8a }
|
|
7823
|
+
}
|
|
7824
|
+
};
|
|
7825
|
+
var BASE_TIP = 1000000000n;
|
|
7826
|
+
var HTTP_CONNECT_TIMEOUT_MS = 2e4;
|
|
7827
|
+
var INCLUSION_TIMEOUT_MS = 2e4;
|
|
7828
|
+
var PHANTOM_POLL_INTERVAL_MS = 15e3;
|
|
7829
|
+
var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
|
|
7830
|
+
function rejectAfter(ms, message) {
|
|
7831
|
+
return new Promise((_resolve, reject) => {
|
|
7832
|
+
const timer = setTimeout(() => reject(new Error(message)), ms);
|
|
7833
|
+
timer.unref?.();
|
|
7834
|
+
});
|
|
7835
|
+
}
|
|
7836
|
+
function deriveHttpUrl(wsUrl) {
|
|
7837
|
+
if (wsUrl.startsWith("wss://")) return `https://${wsUrl.slice("wss://".length)}`;
|
|
7838
|
+
if (wsUrl.startsWith("ws://")) return `http://${wsUrl.slice("ws://".length)}`;
|
|
7839
|
+
throw new Error(`Cannot derive an HTTP endpoint from a non-websocket url: ${wsUrl}`);
|
|
7840
|
+
}
|
|
7802
7841
|
var BidCodec = Struct({ filler: Bytes(32), user_op: Vector(u8) });
|
|
7803
7842
|
var PackedUserOperationCodec = Struct({
|
|
7804
7843
|
sender: Bytes(20),
|
|
@@ -7859,6 +7898,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7859
7898
|
ownsConnection;
|
|
7860
7899
|
/** Cached result of whether the node exposes intents_* RPC methods */
|
|
7861
7900
|
hasIntentsRpc = null;
|
|
7901
|
+
/** The HTTP-backed api, connected on first use. Cleared after a failed attempt so it retries. */
|
|
7902
|
+
httpApi = null;
|
|
7862
7903
|
// Serialises every extrinsic submission on this instance's substrate account. All submit/retract
|
|
7863
7904
|
// methods funnel through signAndSendExtrinsic, each using the API's auto-nonce; fired in parallel
|
|
7864
7905
|
// (bids for orders on different chains, or several phantom orders in one interval) they would grab
|
|
@@ -7875,12 +7916,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7875
7916
|
static async connect(wsUrl, substratePrivateKey) {
|
|
7876
7917
|
const api = await ApiPromise.create({
|
|
7877
7918
|
provider: new WsProvider(wsUrl),
|
|
7878
|
-
typesBundle:
|
|
7879
|
-
spec: {
|
|
7880
|
-
nexus: { hasher: keccakAsU8a },
|
|
7881
|
-
gargantua: { hasher: keccakAsU8a }
|
|
7882
|
-
}
|
|
7883
|
-
}
|
|
7919
|
+
typesBundle: HYPERBRIDGE_TYPES_BUNDLE
|
|
7884
7920
|
});
|
|
7885
7921
|
return new _IntentsCoprocessor(api, substratePrivateKey, true);
|
|
7886
7922
|
}
|
|
@@ -7906,15 +7942,85 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7906
7942
|
static fromApi(api, substratePrivateKey) {
|
|
7907
7943
|
return new _IntentsCoprocessor(api, substratePrivateKey, false);
|
|
7908
7944
|
}
|
|
7945
|
+
/**
|
|
7946
|
+
* The API every RPC query runs on: HTTP, connected to the same node as the websocket. Exposed so
|
|
7947
|
+
* callers query through this connection rather than opening one of their own.
|
|
7948
|
+
*
|
|
7949
|
+
* The split is by what each transport is for. Queries are one-shot request/response, which HTTP
|
|
7950
|
+
* serves without holding any state that can silently rot between calls. The websocket earns its
|
|
7951
|
+
* keep only where subscriptions do — watching a submitted extrinsic to inclusion.
|
|
7952
|
+
*/
|
|
7953
|
+
async queryApi() {
|
|
7954
|
+
return await this.http();
|
|
7955
|
+
}
|
|
7956
|
+
/**
|
|
7957
|
+
* The websocket API, exposed so callers share this one connection instead of opening a second
|
|
7958
|
+
* socket to the same node. Only needed for subscriptions; use {@link queryApi} to read.
|
|
7959
|
+
*/
|
|
7960
|
+
get apiConnection() {
|
|
7961
|
+
return this.api;
|
|
7962
|
+
}
|
|
7909
7963
|
/**
|
|
7910
7964
|
* Disconnects the underlying API connection if this instance owns it.
|
|
7911
|
-
* Only disconnects if created via `connect()`, not when using shared connections.
|
|
7965
|
+
* Only disconnects the websocket if created via `connect()`, not when using shared connections.
|
|
7966
|
+
* The HTTP api is always created here, so it is always ours to close.
|
|
7912
7967
|
*/
|
|
7913
7968
|
async disconnect() {
|
|
7969
|
+
const http4 = this.httpApi;
|
|
7970
|
+
this.httpApi = null;
|
|
7971
|
+
if (http4) {
|
|
7972
|
+
await http4.then((api) => api.disconnect()).catch(() => {
|
|
7973
|
+
});
|
|
7974
|
+
}
|
|
7914
7975
|
if (this.ownsConnection) {
|
|
7915
7976
|
await this.api.disconnect();
|
|
7916
7977
|
}
|
|
7917
7978
|
}
|
|
7979
|
+
/**
|
|
7980
|
+
* The HTTP api for this node, connected on first use. Every coprocessor has one — the endpoint
|
|
7981
|
+
* is derived from the websocket's own endpoint, so there is nothing to configure and nothing to
|
|
7982
|
+
* be absent.
|
|
7983
|
+
*
|
|
7984
|
+
* The connection attempt is bounded on both sides. `isReadyOrError` rejects on a failed
|
|
7985
|
+
* handshake, where plain `isReady` would simply never resolve, and the timeout covers an
|
|
7986
|
+
* endpoint that accepts the request and then goes quiet. An unbounded wait here would hang the
|
|
7987
|
+
* poll tick awaiting it, which is precisely the silent stall that polling exists to avoid. A
|
|
7988
|
+
* failed attempt is not cached, so the next call tries again.
|
|
7989
|
+
*/
|
|
7990
|
+
async http() {
|
|
7991
|
+
if (!this.httpApi) {
|
|
7992
|
+
const httpUrl = deriveHttpUrl(this.wsEndpoint());
|
|
7993
|
+
const api = new ApiPromise({
|
|
7994
|
+
provider: new HttpProvider(httpUrl),
|
|
7995
|
+
typesBundle: HYPERBRIDGE_TYPES_BUNDLE,
|
|
7996
|
+
// A second connection to the node the ws api already reported on; its init warnings
|
|
7997
|
+
// would just be duplicates.
|
|
7998
|
+
noInitWarn: true
|
|
7999
|
+
});
|
|
8000
|
+
this.httpApi = Promise.race([
|
|
8001
|
+
api.isReadyOrError,
|
|
8002
|
+
rejectAfter(HTTP_CONNECT_TIMEOUT_MS, `HTTP RPC ${httpUrl} did not become ready`)
|
|
8003
|
+
]).catch(async (err) => {
|
|
8004
|
+
await api.disconnect().catch(() => {
|
|
8005
|
+
});
|
|
8006
|
+
this.httpApi = null;
|
|
8007
|
+
throw new Error(`HTTP RPC ${httpUrl} is unavailable: ${err instanceof Error ? err.message : err}`);
|
|
8008
|
+
});
|
|
8009
|
+
}
|
|
8010
|
+
return await this.httpApi;
|
|
8011
|
+
}
|
|
8012
|
+
/**
|
|
8013
|
+
* The endpoint the websocket provider is connected to. Read from the provider rather than
|
|
8014
|
+
* remembered from a constructor argument, so it is the one endpoint in use no matter which
|
|
8015
|
+
* factory built this instance.
|
|
8016
|
+
*/
|
|
8017
|
+
wsEndpoint() {
|
|
8018
|
+
const endpoint = this.api._rpcCore?.provider?.endpoint;
|
|
8019
|
+
if (!endpoint) {
|
|
8020
|
+
throw new Error("Cannot determine the Hyperbridge websocket endpoint to derive an HTTP endpoint from");
|
|
8021
|
+
}
|
|
8022
|
+
return endpoint;
|
|
8023
|
+
}
|
|
7918
8024
|
/**
|
|
7919
8025
|
* Creates a Substrate keypair from the configured private key.
|
|
7920
8026
|
* Supports hex seed (with or without 0x), mnemonic phrases, and URI derivation paths (//Alice).
|
|
@@ -7937,50 +8043,119 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7937
8043
|
/**
|
|
7938
8044
|
* Signs and sends an extrinsic. Submissions are serialised through {@link submissionQueue} so
|
|
7939
8045
|
* concurrent calls never collide on the substrate account nonce — each extrinsic reaches a block
|
|
7940
|
-
* (or is confirmed still pooled and returned as `pending`) before the next is signed
|
|
7941
|
-
*
|
|
8046
|
+
* (or is confirmed still pooled and returned as `pending`) before the next is signed. The
|
|
8047
|
+
* auto-nonce is the account's on-chain nonce, so a still-pooled extrinsic does not advance it: a
|
|
8048
|
+
* submission signed behind a pending one bounces off it (1013/1014) and is reported as pending
|
|
8049
|
+
* too, rather than landing as a second copy.
|
|
8050
|
+
*
|
|
8051
|
+
* The extrinsic is built rather than passed in because the api it is built on decides where it
|
|
8052
|
+
* is signed and sent: a websocket that is down when the queue reaches this submission diverts it
|
|
8053
|
+
* to {@link sendViaHttp}, which needs the call bound to the HTTP api instead.
|
|
7942
8054
|
*/
|
|
7943
|
-
async signAndSendExtrinsic(
|
|
7944
|
-
const result = await this.submissionQueue.add(
|
|
7945
|
-
(
|
|
7946
|
-
|
|
8055
|
+
async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = INCLUSION_TIMEOUT_MS) {
|
|
8056
|
+
const result = await this.submissionQueue.add(async () => {
|
|
8057
|
+
if (!this.api.isConnected) {
|
|
8058
|
+
try {
|
|
8059
|
+
return await this.sendViaHttp(await this.http(), build);
|
|
8060
|
+
} catch (err) {
|
|
8061
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
8062
|
+
}
|
|
8063
|
+
}
|
|
8064
|
+
return await this.sendExtrinsicWithRetries(build(this.api), maxRetries, timeoutMs);
|
|
8065
|
+
});
|
|
7947
8066
|
return result ?? { success: false, error: "Submission queue returned no result" };
|
|
7948
8067
|
}
|
|
8068
|
+
/**
|
|
8069
|
+
* Last-resort submission for when the websocket is down at signing time. A bid is only worth
|
|
8070
|
+
* anything inside its window, so waiting for a reconnect usually means not bidding at all.
|
|
8071
|
+
*
|
|
8072
|
+
* HTTP has no subscriptions, so this is `author_submitExtrinsic`: the node accepts the extrinsic
|
|
8073
|
+
* into its pool and returns its hash, and nothing further is observable from here. That is
|
|
8074
|
+
* exactly the `pending` contract — in flight, outcome unknown, do not re-sign — so the result
|
|
8075
|
+
* says so rather than claiming a success it cannot see.
|
|
8076
|
+
*
|
|
8077
|
+
* Only reached when the socket was already down before signing. A submission that got as far as
|
|
8078
|
+
* the pool over the websocket is never retried here: that is the duplicate-nonce race the
|
|
8079
|
+
* `pending` result exists to prevent.
|
|
8080
|
+
*/
|
|
8081
|
+
async sendViaHttp(api, build) {
|
|
8082
|
+
try {
|
|
8083
|
+
const hash = await build(api).signAndSend(this.getKeyPair(), { tip: BASE_TIP });
|
|
8084
|
+
return { success: false, pending: true, extrinsicHash: hash.toHex() };
|
|
8085
|
+
} catch (err) {
|
|
8086
|
+
return this.classifySubmissionError(err instanceof Error ? err : new Error(String(err)));
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
7949
8089
|
/**
|
|
7950
8090
|
* Signs and sends an extrinsic, handling status updates and errors.
|
|
7951
8091
|
* Implements retry logic with progressive tip increases for stuck transactions.
|
|
7952
8092
|
*
|
|
7953
|
-
*
|
|
7954
|
-
*
|
|
7955
|
-
*
|
|
7956
|
-
*
|
|
7957
|
-
*
|
|
7958
|
-
*
|
|
8093
|
+
* Two kinds of failure are retried, and the difference is the nonce.
|
|
8094
|
+
*
|
|
8095
|
+
* An attempt that verifiably went nowhere (rejected before the pool, dropped, invalid) leaves
|
|
8096
|
+
* the account nonce free, so the next attempt simply re-signs with the auto-nonce.
|
|
8097
|
+
*
|
|
8098
|
+
* An attempt that reached the pool and was still there when the watch timed out (`stalled`) is
|
|
8099
|
+
* retried as a *replacement*: the same nonce it was signed with, and double the tip. Substrate's
|
|
8100
|
+
* pool evicts a pooled extrinsic in favour of a higher-priority one at the same (account, nonce),
|
|
8101
|
+
* so exactly one of the two can ever execute. This matters for a bid, which is worth nothing once
|
|
8102
|
+
* its window closes — waiting out a stalled extrinsic usually means not bidding at all.
|
|
8103
|
+
*
|
|
8104
|
+
* Re-signing without pinning the nonce is what must never happen here. The auto-nonce is read
|
|
8105
|
+
* from on-chain state, so it is only the stalled extrinsic's nonce for as long as that extrinsic
|
|
8106
|
+
* stays out of a block — and a stall is precisely the case where it may land at any moment. Once
|
|
8107
|
+
* it does, an unpinned retry takes the *next* nonce and both execute: a duplicate `placeBid` that
|
|
8108
|
+
* fails on-chain, and a second `retractBid` that pulls the bid just placed. When the signed nonce
|
|
8109
|
+
* cannot be read, the stalled result is returned rather than guessed at.
|
|
8110
|
+
*
|
|
8111
|
+
* A rejection that bounced off a copy already pooled (1013/1014) is likewise left alone: that
|
|
8112
|
+
* copy is in flight and its outcome is unknown here, so the `pending` result goes back to the
|
|
8113
|
+
* caller to confirm later.
|
|
7959
8114
|
*/
|
|
7960
8115
|
async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
|
|
7961
8116
|
const keyPair = this.getKeyPair();
|
|
7962
|
-
const baseTip = 1000000000n;
|
|
7963
8117
|
let attempt = 0;
|
|
8118
|
+
let nonce;
|
|
8119
|
+
let stalled;
|
|
7964
8120
|
while (attempt < maxRetries) {
|
|
7965
|
-
const currentTip =
|
|
8121
|
+
const currentTip = BASE_TIP * BigInt(2 ** attempt);
|
|
7966
8122
|
attempt++;
|
|
7967
8123
|
try {
|
|
7968
|
-
const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs);
|
|
7969
|
-
if (result.success || result.
|
|
8124
|
+
const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs, nonce);
|
|
8125
|
+
if (result.success || result.error?.includes("Dispatch error")) {
|
|
7970
8126
|
return result;
|
|
7971
8127
|
}
|
|
8128
|
+
if (result.stalled) {
|
|
8129
|
+
stalled = result;
|
|
8130
|
+
nonce ??= this.signedNonce(extrinsic);
|
|
8131
|
+
if (nonce === void 0) return result;
|
|
8132
|
+
continue;
|
|
8133
|
+
}
|
|
8134
|
+
if (result.pending) return stalled ?? result;
|
|
7972
8135
|
} catch (err) {
|
|
7973
|
-
return {
|
|
8136
|
+
return stalled ?? {
|
|
7974
8137
|
success: false,
|
|
7975
8138
|
error: err instanceof Error ? err.message : "Unknown error"
|
|
7976
8139
|
};
|
|
7977
8140
|
}
|
|
7978
8141
|
}
|
|
7979
|
-
return {
|
|
8142
|
+
return stalled ?? {
|
|
7980
8143
|
success: false,
|
|
7981
8144
|
error: `Transaction failed after ${maxRetries} attempts`
|
|
7982
8145
|
};
|
|
7983
8146
|
}
|
|
8147
|
+
/**
|
|
8148
|
+
* The nonce an extrinsic was signed with, or undefined if it carries no readable one — which is
|
|
8149
|
+
* the case before it has ever been signed, and for a stub api in tests.
|
|
8150
|
+
*/
|
|
8151
|
+
signedNonce(extrinsic) {
|
|
8152
|
+
try {
|
|
8153
|
+
const nonce = extrinsic.nonce?.toNumber?.();
|
|
8154
|
+
return typeof nonce === "number" && Number.isFinite(nonce) ? nonce : void 0;
|
|
8155
|
+
} catch {
|
|
8156
|
+
return void 0;
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
7984
8159
|
/**
|
|
7985
8160
|
* Classifies a submission rejection. Codes 1013 ("already imported") and 1014 ("priority is
|
|
7986
8161
|
* too low") both mean a copy of this account+nonce is already in the pool — almost always our
|
|
@@ -7998,10 +8173,15 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7998
8173
|
*
|
|
7999
8174
|
* A timeout is only a failure when the extrinsic never made it into the transaction pool.
|
|
8000
8175
|
* Once a pool-entry status (Future/Ready/Broadcast/Retracted) has been seen, the extrinsic is
|
|
8001
|
-
* in flight and may well execute after the watch is abandoned — the result is then `pending
|
|
8002
|
-
* telling the caller to
|
|
8176
|
+
* in flight and may well execute after the watch is abandoned — the result is then `pending`
|
|
8177
|
+
* and `stalled`, telling the caller to replace it under the same nonce or confirm it later,
|
|
8178
|
+
* never to re-sign the same call under a fresh one.
|
|
8179
|
+
*
|
|
8180
|
+
* `nonce` pins the submission to a specific account nonce, which is what makes a retry a pool
|
|
8181
|
+
* replacement rather than a second extrinsic queued behind the first. Left undefined on the
|
|
8182
|
+
* first attempt, where the api's auto-nonce is correct.
|
|
8003
8183
|
*/
|
|
8004
|
-
async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs) {
|
|
8184
|
+
async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs, nonce) {
|
|
8005
8185
|
return new Promise((resolve) => {
|
|
8006
8186
|
let resolved = false;
|
|
8007
8187
|
let unsubscribe = null;
|
|
@@ -8015,12 +8195,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8015
8195
|
resolve({
|
|
8016
8196
|
success: false,
|
|
8017
8197
|
pending: enteredPool || void 0,
|
|
8198
|
+
stalled: enteredPool || void 0,
|
|
8018
8199
|
extrinsicHash: enteredPool ? extrinsic.hash.toHex() : void 0,
|
|
8019
8200
|
error: `Transaction timed out after ${timeoutMs}ms${enteredPool ? " while in the transaction pool" : ""}`
|
|
8020
8201
|
});
|
|
8021
8202
|
}
|
|
8022
8203
|
}, timeoutMs);
|
|
8023
|
-
extrinsic.signAndSend(keyPair, { tip }, (result) => {
|
|
8204
|
+
extrinsic.signAndSend(keyPair, nonce === void 0 ? { tip } : { tip, nonce }, (result) => {
|
|
8024
8205
|
if (resolved) return;
|
|
8025
8206
|
if (result.status.isFuture || result.status.isReady || result.status.isBroadcast || result.status.isRetracted) {
|
|
8026
8207
|
enteredPool = true;
|
|
@@ -8028,16 +8209,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8028
8209
|
if (result.dispatchError && (result.status.isInBlock || result.status.isFinalized)) {
|
|
8029
8210
|
resolved = true;
|
|
8030
8211
|
clearTimeout(timeoutId);
|
|
8031
|
-
let errorMsg;
|
|
8032
|
-
if (result.dispatchError.isModule) {
|
|
8033
|
-
const decoded = this.api.registry.findMetaError(result.dispatchError.asModule);
|
|
8034
|
-
errorMsg = `Dispatch error: ${decoded.section}::${decoded.name}`;
|
|
8035
|
-
} else {
|
|
8036
|
-
errorMsg = `Dispatch error: ${result.dispatchError.toString()}`;
|
|
8037
|
-
}
|
|
8038
8212
|
resolve({
|
|
8039
8213
|
success: false,
|
|
8040
|
-
error:
|
|
8214
|
+
error: `Dispatch error: ${this.describeDispatchError(result.dispatchError)}`
|
|
8041
8215
|
});
|
|
8042
8216
|
} else if (result.status.isDropped || result.status.isInvalid || result.status.isUsurped || result.status.isFinalityTimeout) {
|
|
8043
8217
|
resolved = true;
|
|
@@ -8055,21 +8229,19 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8055
8229
|
if (interrupted) {
|
|
8056
8230
|
const [indexCodec, dispatchError] = interrupted.event.data;
|
|
8057
8231
|
if (Number(indexCodec.toString()) === 0) {
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
} else {
|
|
8063
|
-
errorMsg = `Dispatch error: batch interrupted (${dispatchError?.toString()})`;
|
|
8064
|
-
}
|
|
8065
|
-
resolve({ success: false, error: errorMsg });
|
|
8232
|
+
resolve({
|
|
8233
|
+
success: false,
|
|
8234
|
+
error: `Dispatch error: ${this.describeDispatchError(dispatchError)}`
|
|
8235
|
+
});
|
|
8066
8236
|
return;
|
|
8067
8237
|
}
|
|
8068
8238
|
}
|
|
8069
8239
|
resolve({
|
|
8070
8240
|
success: true,
|
|
8071
8241
|
blockHash: (result.status.isInBlock ? result.status.asInBlock : result.status.asFinalized).toHex(),
|
|
8072
|
-
extrinsicHash: extrinsic.hash.toHex()
|
|
8242
|
+
extrinsicHash: extrinsic.hash.toHex(),
|
|
8243
|
+
// Carried so a batch caller can attribute each item's outcome.
|
|
8244
|
+
events: result.events
|
|
8073
8245
|
});
|
|
8074
8246
|
}
|
|
8075
8247
|
}).then((unsub) => {
|
|
@@ -8096,8 +8268,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8096
8268
|
*/
|
|
8097
8269
|
async submitBid(commitment, userOp) {
|
|
8098
8270
|
try {
|
|
8099
|
-
|
|
8100
|
-
return await this.signAndSendExtrinsic(extrinsic);
|
|
8271
|
+
return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.placeBid(commitment, userOp));
|
|
8101
8272
|
} catch (error) {
|
|
8102
8273
|
return {
|
|
8103
8274
|
success: false,
|
|
@@ -8115,8 +8286,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8115
8286
|
*/
|
|
8116
8287
|
async retractBid(commitment) {
|
|
8117
8288
|
try {
|
|
8118
|
-
|
|
8119
|
-
return await this.signAndSendExtrinsic(extrinsic);
|
|
8289
|
+
return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.retractBid(commitment));
|
|
8120
8290
|
} catch (error) {
|
|
8121
8291
|
return {
|
|
8122
8292
|
success: false,
|
|
@@ -8146,11 +8316,12 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8146
8316
|
*/
|
|
8147
8317
|
async submitBidWithRetraction(retractCommitment, bidCommitment, userOp) {
|
|
8148
8318
|
try {
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8319
|
+
return await this.signAndSendExtrinsic(
|
|
8320
|
+
(api) => api.tx.utility.batch([
|
|
8321
|
+
api.tx.intentsCoprocessor.placeBid(bidCommitment, userOp),
|
|
8322
|
+
api.tx.intentsCoprocessor.retractBid(retractCommitment)
|
|
8323
|
+
])
|
|
8324
|
+
);
|
|
8154
8325
|
} catch (error) {
|
|
8155
8326
|
return {
|
|
8156
8327
|
success: false,
|
|
@@ -8158,6 +8329,98 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8158
8329
|
};
|
|
8159
8330
|
}
|
|
8160
8331
|
}
|
|
8332
|
+
/**
|
|
8333
|
+
* Places every phantom bid of one interval in a single extrinsic, retracting each chain's
|
|
8334
|
+
* previous bid alongside it.
|
|
8335
|
+
*
|
|
8336
|
+
* The pallet registers one phantom order per configured chain in the same block, so this is the
|
|
8337
|
+
* whole interval's set. Submitting them one at a time costs a block per chain: submissions are
|
|
8338
|
+
* serialised on the account nonce and each waits for inclusion, so the last chain's bid is many
|
|
8339
|
+
* blocks behind the first, against a bid window measured in tens of blocks. Batched, every bid
|
|
8340
|
+
* lands in the same block.
|
|
8341
|
+
*
|
|
8342
|
+
* Uses `utility.force_batch`, not `utility.batch`. `batch` stops at the first failing call, so
|
|
8343
|
+
* one rejected bid — a closed window, a duplicate, an insufficient deposit — would silently
|
|
8344
|
+
* drop every bid after it. `force_batch` runs them all and reports each outcome. It needs no
|
|
8345
|
+
* special origin: any signed account may call it, exactly like `batch`.
|
|
8346
|
+
*
|
|
8347
|
+
* @param bids - The bids to place; an empty list is a no-op
|
|
8348
|
+
* @returns Per-bid outcomes, in the order given
|
|
8349
|
+
*/
|
|
8350
|
+
async submitPhantomBids(bids) {
|
|
8351
|
+
if (bids.length === 0) return { bids: [] };
|
|
8352
|
+
const placeIndexByBid = [];
|
|
8353
|
+
let callCount = 0;
|
|
8354
|
+
for (const bid of bids) {
|
|
8355
|
+
placeIndexByBid.push(callCount);
|
|
8356
|
+
callCount += bid.retractCommitment ? 2 : 1;
|
|
8357
|
+
}
|
|
8358
|
+
const outcome = await this.signAndSendExtrinsic(
|
|
8359
|
+
(api) => api.tx.utility.forceBatch(
|
|
8360
|
+
bids.flatMap((bid) => {
|
|
8361
|
+
const calls = [api.tx.intentsCoprocessor.placeBid(bid.commitment, bid.userOp)];
|
|
8362
|
+
if (bid.retractCommitment) {
|
|
8363
|
+
calls.push(api.tx.intentsCoprocessor.retractBid(bid.retractCommitment));
|
|
8364
|
+
}
|
|
8365
|
+
return calls;
|
|
8366
|
+
})
|
|
8367
|
+
)
|
|
8368
|
+
);
|
|
8369
|
+
if (!outcome.success) {
|
|
8370
|
+
return {
|
|
8371
|
+
bids: bids.map((bid) => ({ commitment: bid.commitment, success: false, error: outcome.error })),
|
|
8372
|
+
pending: outcome.pending,
|
|
8373
|
+
extrinsicHash: outcome.extrinsicHash,
|
|
8374
|
+
error: outcome.error
|
|
8375
|
+
};
|
|
8376
|
+
}
|
|
8377
|
+
const items = this.readForceBatchItems(outcome.events ?? [], callCount);
|
|
8378
|
+
return {
|
|
8379
|
+
bids: bids.map((bid, index) => {
|
|
8380
|
+
const error = items.errors[placeIndexByBid[index]];
|
|
8381
|
+
return { commitment: bid.commitment, success: !error, error };
|
|
8382
|
+
}),
|
|
8383
|
+
blockHash: outcome.blockHash,
|
|
8384
|
+
extrinsicHash: outcome.extrinsicHash,
|
|
8385
|
+
error: items.error
|
|
8386
|
+
};
|
|
8387
|
+
}
|
|
8388
|
+
/**
|
|
8389
|
+
* Reads one outcome per call out of a force_batch's events.
|
|
8390
|
+
*
|
|
8391
|
+
* `ItemFailed` carries no index — pallet-utility emits exactly one `ItemCompleted` or
|
|
8392
|
+
* `ItemFailed` per call, in call order, so the k-th item event belongs to call k.
|
|
8393
|
+
*
|
|
8394
|
+
* A count that does not match the calls submitted means the events are not the ones assumed
|
|
8395
|
+
* here, and every attribution after the discrepancy would be off by one. The bids are then
|
|
8396
|
+
* reported as placed: a bid wrongly recorded as landed is retracted next interval and the
|
|
8397
|
+
* retraction harmlessly fails with `BidNotFound`, whereas one wrongly recorded as failed is
|
|
8398
|
+
* never retracted at all and leaves its deposit reserved.
|
|
8399
|
+
*/
|
|
8400
|
+
readForceBatchItems(events, callCount) {
|
|
8401
|
+
const errors = [];
|
|
8402
|
+
for (const { event } of events) {
|
|
8403
|
+
if (event.section !== "utility") continue;
|
|
8404
|
+
if (event.method === "ItemCompleted") errors.push(void 0);
|
|
8405
|
+
else if (event.method === "ItemFailed") errors.push(this.describeDispatchError(event.data[0]));
|
|
8406
|
+
}
|
|
8407
|
+
if (errors.length !== callCount) {
|
|
8408
|
+
return {
|
|
8409
|
+
errors: new Array(callCount).fill(void 0),
|
|
8410
|
+
error: `force_batch reported ${errors.length} item events for ${callCount} calls; outcomes not attributed`
|
|
8411
|
+
};
|
|
8412
|
+
}
|
|
8413
|
+
return { errors };
|
|
8414
|
+
}
|
|
8415
|
+
/** Renders a DispatchError as `pallet::Error`, falling back to its raw form. */
|
|
8416
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8417
|
+
describeDispatchError(dispatchError) {
|
|
8418
|
+
if (dispatchError?.isModule) {
|
|
8419
|
+
const decoded = this.api.registry.findMetaError(dispatchError.asModule);
|
|
8420
|
+
return `${decoded.section}::${decoded.name}`;
|
|
8421
|
+
}
|
|
8422
|
+
return dispatchError?.toString() ?? "unknown dispatch error";
|
|
8423
|
+
}
|
|
8161
8424
|
/**
|
|
8162
8425
|
* Fetches all bid storage entries for a given order commitment.
|
|
8163
8426
|
* Returns the on-chain data only (filler addresses and deposits).
|
|
@@ -8166,7 +8429,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8166
8429
|
* @returns Array of BidStorageEntry objects
|
|
8167
8430
|
*/
|
|
8168
8431
|
async getBidStorageEntries(commitment) {
|
|
8169
|
-
const
|
|
8432
|
+
const api = await this.http();
|
|
8433
|
+
const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
|
|
8170
8434
|
return entries.map(([storageKey, depositValue]) => ({
|
|
8171
8435
|
commitment,
|
|
8172
8436
|
filler: storageKey.args[1].toString(),
|
|
@@ -8196,9 +8460,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8196
8460
|
* Single round-trip but does not include deposit amounts.
|
|
8197
8461
|
*/
|
|
8198
8462
|
async getBidsViaRpc(commitment) {
|
|
8199
|
-
const
|
|
8200
|
-
|
|
8201
|
-
]);
|
|
8463
|
+
const api = await this.http();
|
|
8464
|
+
const result = await api._rpcCore.provider.send("intents_getBidsForOrder", [commitment]);
|
|
8202
8465
|
return result.map((entry) => {
|
|
8203
8466
|
const userOp = decodeUserOpScale(entry.user_op);
|
|
8204
8467
|
const filler = new Keyring({ type: "sr25519" }).encodeAddress(hexToU8a(entry.filler));
|
|
@@ -8210,7 +8473,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8210
8473
|
* Slower but works on all nodes and includes deposit amounts.
|
|
8211
8474
|
*/
|
|
8212
8475
|
async getBidsViaStorage(commitment) {
|
|
8213
|
-
const
|
|
8476
|
+
const api = await this.http();
|
|
8477
|
+
const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
|
|
8214
8478
|
if (entries.length === 0) return [];
|
|
8215
8479
|
const bidPromises = entries.map(async ([storageKey, depositValue]) => {
|
|
8216
8480
|
try {
|
|
@@ -8218,7 +8482,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8218
8482
|
const deposit = BigInt(depositValue.toString());
|
|
8219
8483
|
const offchainKey = this.buildOffchainBidKey(commitment, filler);
|
|
8220
8484
|
const offchainKeyHex = u8aToHex(offchainKey);
|
|
8221
|
-
const offchainResult = await
|
|
8485
|
+
const offchainResult = await api.rpc.offchain.localStorageGet("PERSISTENT", offchainKeyHex);
|
|
8222
8486
|
if (!offchainResult || offchainResult.isNone) return null;
|
|
8223
8487
|
const bidData = offchainResult.unwrap().toHex();
|
|
8224
8488
|
const decoded = this.decodeBid(bidData);
|
|
@@ -8252,7 +8516,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8252
8516
|
*/
|
|
8253
8517
|
async fetchPhantomOrder(commitment) {
|
|
8254
8518
|
const key = u8aConcat(OFFCHAIN_PHANTOM_PREFIX, hexToU8a(commitment));
|
|
8255
|
-
const
|
|
8519
|
+
const api = await this.http();
|
|
8520
|
+
const result = await api.rpc.offchain.localStorageGet("PERSISTENT", u8aToHex(key));
|
|
8256
8521
|
if (!result || result.isNone) return null;
|
|
8257
8522
|
const rawHex = result.unwrap().toHex();
|
|
8258
8523
|
if (rawHex === "0x" || rawHex === "0x00") return null;
|
|
@@ -8296,8 +8561,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8296
8561
|
* Reads the PhantomOrderRegistered events emitted in a single block.
|
|
8297
8562
|
*/
|
|
8298
8563
|
async getPhantomOrdersInBlock(blockNumber) {
|
|
8299
|
-
const
|
|
8300
|
-
const
|
|
8564
|
+
const api = await this.http();
|
|
8565
|
+
const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
|
|
8566
|
+
const apiAt = await api.at(blockHash);
|
|
8301
8567
|
const records = await apiAt.query.system.events();
|
|
8302
8568
|
const orders = [];
|
|
8303
8569
|
for (const { event } of records) {
|
|
@@ -8318,7 +8584,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8318
8584
|
return orders;
|
|
8319
8585
|
}
|
|
8320
8586
|
/**
|
|
8321
|
-
* Polls for newly registered phantom orders, invoking the callback once per
|
|
8587
|
+
* Polls for newly registered phantom orders, invoking the callback once per block that carries
|
|
8588
|
+
* any, with all of that block's orders.
|
|
8589
|
+
*
|
|
8590
|
+
* Per block rather than per order because that is how the pallet writes them: one order per
|
|
8591
|
+
* configured chain, all registered in the same `on_initialize`. Delivering them together lets a
|
|
8592
|
+
* caller bid on the whole interval in one extrinsic (see {@link submitPhantomBids}) instead of
|
|
8593
|
+
* one per chain.
|
|
8322
8594
|
*
|
|
8323
8595
|
* Each tick reads the current head and scans every block between the last one processed and that
|
|
8324
8596
|
* head, so the block cursor — not the connection — determines what has been seen. This replaced a
|
|
@@ -8331,10 +8603,16 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8331
8603
|
* cannot drop them, because the cursor only advances past a block whose events were actually
|
|
8332
8604
|
* read. Recovery replays the backlog.
|
|
8333
8605
|
*
|
|
8606
|
+
* Every read here goes over HTTP, never the websocket. Polling is a sequence of independent
|
|
8607
|
+
* one-shot requests with no state to lose between them, which is exactly what a stateless
|
|
8608
|
+
* transport does well: a request either answers or fails loudly on this tick, instead of a
|
|
8609
|
+
* socket that looks alive while delivering nothing. It also means a websocket outage does not
|
|
8610
|
+
* pause phantom bidding at all — the two transports fail independently.
|
|
8611
|
+
*
|
|
8334
8612
|
* Returns a function that stops polling.
|
|
8335
8613
|
*/
|
|
8336
8614
|
pollPhantomOrders(callback, options = {}) {
|
|
8337
|
-
const { intervalMs
|
|
8615
|
+
const { intervalMs, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
|
|
8338
8616
|
let cursor = null;
|
|
8339
8617
|
let inFlight = false;
|
|
8340
8618
|
let stopped = false;
|
|
@@ -8342,7 +8620,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8342
8620
|
if (inFlight || stopped) return;
|
|
8343
8621
|
inFlight = true;
|
|
8344
8622
|
try {
|
|
8345
|
-
const head = (await this.
|
|
8623
|
+
const head = (await (await this.http()).rpc.chain.getHeader()).number.toNumber();
|
|
8346
8624
|
if (cursor === null) {
|
|
8347
8625
|
cursor = Math.max(head - 1 - lookbackBlocks, -1);
|
|
8348
8626
|
}
|
|
@@ -8351,7 +8629,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8351
8629
|
for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
|
|
8352
8630
|
if (stopped) return;
|
|
8353
8631
|
const orders = await this.getPhantomOrdersInBlock(blockNumber);
|
|
8354
|
-
|
|
8632
|
+
if (orders.length > 0) callback(orders);
|
|
8355
8633
|
cursor = blockNumber;
|
|
8356
8634
|
}
|
|
8357
8635
|
} catch (err) {
|
|
@@ -8361,12 +8639,31 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8361
8639
|
}
|
|
8362
8640
|
};
|
|
8363
8641
|
void tick();
|
|
8364
|
-
|
|
8642
|
+
let timer = null;
|
|
8643
|
+
const startTimer = (ms) => {
|
|
8644
|
+
if (stopped) return;
|
|
8645
|
+
timer = setInterval(() => void tick(), ms);
|
|
8646
|
+
};
|
|
8647
|
+
if (intervalMs !== void 0) startTimer(intervalMs);
|
|
8648
|
+
else void this.phantomPollIntervalMs().then(startTimer);
|
|
8365
8649
|
return () => {
|
|
8366
8650
|
stopped = true;
|
|
8367
|
-
clearInterval(timer);
|
|
8651
|
+
if (timer) clearInterval(timer);
|
|
8368
8652
|
};
|
|
8369
8653
|
}
|
|
8654
|
+
/**
|
|
8655
|
+
* The poll cadence for the runtime this instance is connected to: Gargantua polls every block,
|
|
8656
|
+
* everything else every 15s. Falls back to the slower cadence if the runtime cannot be read,
|
|
8657
|
+
* since an unreachable node is the poll's problem to report, not the cadence lookup's.
|
|
8658
|
+
*/
|
|
8659
|
+
async phantomPollIntervalMs() {
|
|
8660
|
+
try {
|
|
8661
|
+
const specName = (await this.http()).runtimeVersion.specName.toString();
|
|
8662
|
+
return specName === "gargantua" ? GARGANTUA_PHANTOM_POLL_INTERVAL_MS : PHANTOM_POLL_INTERVAL_MS;
|
|
8663
|
+
} catch {
|
|
8664
|
+
return PHANTOM_POLL_INTERVAL_MS;
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8370
8667
|
};
|
|
8371
8668
|
var TronChain = class _TronChain {
|
|
8372
8669
|
constructor(params, evm) {
|
|
@@ -11741,52 +12038,85 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
|
|
|
11741
12038
|
}
|
|
11742
12039
|
}
|
|
11743
12040
|
}`;
|
|
11744
|
-
var
|
|
11745
|
-
query
|
|
11746
|
-
|
|
12041
|
+
var AVAILABLE_LIQUIDITY = `
|
|
12042
|
+
query AvailableLiquidity(
|
|
12043
|
+
$poolId: String!
|
|
12044
|
+
$sourceChain: String!
|
|
12045
|
+
$destinationChain: String!
|
|
12046
|
+
$direction: String!
|
|
12047
|
+
) {
|
|
12048
|
+
poolChainLiquidities(
|
|
11747
12049
|
filter: {
|
|
11748
12050
|
and: [
|
|
11749
|
-
{
|
|
11750
|
-
{
|
|
12051
|
+
{ poolId: { equalToInsensitive: $poolId } }
|
|
12052
|
+
{ chain: { equalTo: $destinationChain } }
|
|
12053
|
+
{ direction: { equalTo: $direction } }
|
|
11751
12054
|
]
|
|
11752
12055
|
}
|
|
11753
|
-
orderBy: SNAPSHOT_TIME_DESC
|
|
11754
12056
|
first: 1
|
|
11755
12057
|
) {
|
|
11756
12058
|
nodes {
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
12059
|
+
depth
|
|
12060
|
+
bidCount
|
|
12061
|
+
unrestrictedDepth
|
|
12062
|
+
unrestrictedBidCount
|
|
12063
|
+
lastUpdatedAt
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
poolRoutes(
|
|
12067
|
+
filter: {
|
|
12068
|
+
and: [
|
|
12069
|
+
{ poolId: { equalToInsensitive: $poolId } }
|
|
12070
|
+
{ sourceChain: { equalTo: $sourceChain } }
|
|
12071
|
+
{ chain: { equalTo: $destinationChain } }
|
|
12072
|
+
{ direction: { equalTo: $direction } }
|
|
12073
|
+
]
|
|
12074
|
+
}
|
|
12075
|
+
first: 1
|
|
12076
|
+
) {
|
|
12077
|
+
nodes {
|
|
12078
|
+
depth
|
|
12079
|
+
bidCount
|
|
12080
|
+
lastUpdatedAt
|
|
11761
12081
|
}
|
|
11762
12082
|
}
|
|
11763
12083
|
}`;
|
|
11764
|
-
var
|
|
11765
|
-
query
|
|
11766
|
-
|
|
12084
|
+
var BUY_AND_SELL_RATES = `
|
|
12085
|
+
query BuyAndSellRates(
|
|
12086
|
+
$poolId: String!
|
|
12087
|
+
$directChain: String!
|
|
12088
|
+
$directDirection: String!
|
|
12089
|
+
$reverseChain: String!
|
|
12090
|
+
$reverseDirection: String!
|
|
12091
|
+
) {
|
|
12092
|
+
direct: poolChainLiquidities(
|
|
11767
12093
|
filter: {
|
|
11768
12094
|
and: [
|
|
11769
|
-
{
|
|
11770
|
-
{
|
|
12095
|
+
{ poolId: { equalToInsensitive: $poolId } }
|
|
12096
|
+
{ chain: { equalTo: $directChain } }
|
|
12097
|
+
{ direction: { equalTo: $directDirection } }
|
|
11771
12098
|
]
|
|
11772
12099
|
}
|
|
12100
|
+
first: 1
|
|
11773
12101
|
) {
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
}
|
|
11778
|
-
distinctCount {
|
|
11779
|
-
providerId
|
|
11780
|
-
}
|
|
12102
|
+
nodes {
|
|
12103
|
+
rate
|
|
12104
|
+
lastUpdatedAt
|
|
11781
12105
|
}
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
12106
|
+
}
|
|
12107
|
+
reverse: poolChainLiquidities(
|
|
12108
|
+
filter: {
|
|
12109
|
+
and: [
|
|
12110
|
+
{ poolId: { equalToInsensitive: $poolId } }
|
|
12111
|
+
{ chain: { equalTo: $reverseChain } }
|
|
12112
|
+
{ direction: { equalTo: $reverseDirection } }
|
|
12113
|
+
]
|
|
12114
|
+
}
|
|
12115
|
+
first: 1
|
|
12116
|
+
) {
|
|
12117
|
+
nodes {
|
|
12118
|
+
rate
|
|
12119
|
+
lastUpdatedAt
|
|
11790
12120
|
}
|
|
11791
12121
|
}
|
|
11792
12122
|
}`;
|
|
@@ -15168,6 +15498,12 @@ var CryptoUtils = class _CryptoUtils {
|
|
|
15168
15498
|
* signing infrastructure (hardware wallets, MPC/TEE policy engines) instead
|
|
15169
15499
|
* of an opaque 32-byte digest.
|
|
15170
15500
|
*
|
|
15501
|
+
* The payload must be a standard self-describing `eth_signTypedData_v4`
|
|
15502
|
+
* payload — `EIP712Domain` listed in `types`, `chainId` as a JSON number —
|
|
15503
|
+
* because some signing backends (e.g. MPC Vault) hash it server-side from
|
|
15504
|
+
* the JSON rather than locally via viem. viem ignores both details when
|
|
15505
|
+
* hashing, so the digest is unchanged for local signers.
|
|
15506
|
+
*
|
|
15171
15507
|
* @param userOp - The packed UserOperation to sign (signature field ignored).
|
|
15172
15508
|
* @param entryPoint - Address of the EntryPoint v0.8 contract.
|
|
15173
15509
|
* @param chainId - Chain ID of the network on which the operation will execute.
|
|
@@ -15178,10 +15514,22 @@ var CryptoUtils = class _CryptoUtils {
|
|
|
15178
15514
|
domain: {
|
|
15179
15515
|
name: "ERC4337",
|
|
15180
15516
|
version: "1",
|
|
15181
|
-
chainId
|
|
15517
|
+
// Runtime number so JSON.stringify emits a canonical v4 numeric chainId for
|
|
15518
|
+
// server-side hashers; viem's uint256 type mapping wants bigint but its
|
|
15519
|
+
// runtime accepts numbers, hence the cast.
|
|
15520
|
+
chainId: Number(chainId),
|
|
15182
15521
|
verifyingContract: entryPoint
|
|
15183
15522
|
},
|
|
15523
|
+
// `as const`: viem derives the domain's TYPE from `types.EIP712Domain`, so the
|
|
15524
|
+
// entries must stay string literals — widened `string` fields make viem's
|
|
15525
|
+
// typed-data generics reject the payload at every call site.
|
|
15184
15526
|
types: {
|
|
15527
|
+
EIP712Domain: [
|
|
15528
|
+
{ name: "name", type: "string" },
|
|
15529
|
+
{ name: "version", type: "string" },
|
|
15530
|
+
{ name: "chainId", type: "uint256" },
|
|
15531
|
+
{ name: "verifyingContract", type: "address" }
|
|
15532
|
+
],
|
|
15185
15533
|
PackedUserOperation: [
|
|
15186
15534
|
{ name: "sender", type: "address" },
|
|
15187
15535
|
{ name: "nonce", type: "uint256" },
|
|
@@ -17912,178 +18260,191 @@ var OrderStatusChecker = class {
|
|
|
17912
18260
|
return true;
|
|
17913
18261
|
}
|
|
17914
18262
|
};
|
|
17915
|
-
|
|
18263
|
+
|
|
18264
|
+
// src/protocols/intents/liquidity-pool.ts
|
|
18265
|
+
function sortPoolSymbols(symbolA, symbolB) {
|
|
18266
|
+
return symbolA.toLowerCase() <= symbolB.toLowerCase() ? [symbolA, symbolB] : [symbolB, symbolA];
|
|
18267
|
+
}
|
|
18268
|
+
function poolSlug(symbolA, symbolB) {
|
|
18269
|
+
return sortPoolSymbols(symbolA, symbolB).join("-");
|
|
18270
|
+
}
|
|
18271
|
+
function resolveLiquidityPool(symbolA, symbolB) {
|
|
18272
|
+
const [token0Symbol, token1Symbol] = sortPoolSymbols(symbolA, symbolB);
|
|
18273
|
+
return {
|
|
18274
|
+
poolId: `${token0Symbol}-${token1Symbol}`,
|
|
18275
|
+
token0Symbol,
|
|
18276
|
+
token1Symbol
|
|
18277
|
+
};
|
|
18278
|
+
}
|
|
18279
|
+
|
|
18280
|
+
// src/protocols/intents/LiquidityEngine.ts
|
|
18281
|
+
var INDEXER_FIXED_POINT_DECIMALS = 18;
|
|
18282
|
+
var POOL_RATE_SCALE = 10n ** 18n;
|
|
18283
|
+
var SELL = "SELL";
|
|
18284
|
+
var BUY = "BUY";
|
|
18285
|
+
var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT"]);
|
|
17916
18286
|
var LiquidityEngine = class {
|
|
17917
|
-
|
|
17918
|
-
* @param queryClient - Nexus GraphQL client attached to the gateway.
|
|
17919
|
-
* @param chainConfigService - Resolves token decimals for formatted results.
|
|
17920
|
-
*/
|
|
17921
|
-
constructor(queryClient, chainConfigService) {
|
|
18287
|
+
constructor(queryClient) {
|
|
17922
18288
|
this.queryClient = queryClient;
|
|
17923
|
-
this.chainConfigService = chainConfigService;
|
|
17924
18289
|
}
|
|
17925
18290
|
queryClient;
|
|
17926
|
-
chainConfigService;
|
|
17927
18291
|
/**
|
|
17928
|
-
*
|
|
17929
|
-
* indexed output-token liquidity.
|
|
18292
|
+
* Returns liquidity reachable from one source chain on one destination.
|
|
17930
18293
|
*
|
|
17931
|
-
*
|
|
17932
|
-
*
|
|
17933
|
-
*
|
|
17934
|
-
* that chain's token decimals. They describe `snapshotTime`, not live
|
|
17935
|
-
* reservations or fill guarantees.
|
|
18294
|
+
* The caller resolves chain-specific token addresses through chain
|
|
18295
|
+
* configuration; this layer only maps those configured symbols onto the
|
|
18296
|
+
* indexer's canonical pool and route fields.
|
|
17936
18297
|
*
|
|
17937
|
-
*
|
|
17938
|
-
*
|
|
17939
|
-
*
|
|
17940
|
-
* @
|
|
17941
|
-
*
|
|
17942
|
-
*/
|
|
17943
|
-
async
|
|
17944
|
-
const
|
|
17945
|
-
const
|
|
17946
|
-
const
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
const
|
|
17953
|
-
if (!
|
|
17954
|
-
throw new
|
|
17955
|
-
}
|
|
17956
|
-
if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
|
|
17957
|
-
throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
|
|
17958
|
-
}
|
|
17959
|
-
const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
|
|
17960
|
-
if (Number.isNaN(snapshotTime.getTime())) {
|
|
17961
|
-
throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
|
|
18298
|
+
* Destination, unrestricted, and explicit-route capacity are returned as
|
|
18299
|
+
* separate values so callers can apply their own source-chain policy.
|
|
18300
|
+
*
|
|
18301
|
+
* @returns `undefined` only when the indexer has not published a destination
|
|
18302
|
+
* pool sample yet.
|
|
18303
|
+
*/
|
|
18304
|
+
async getAvailableLiquidity(params) {
|
|
18305
|
+
const pool = resolveLiquidityPool(params.source.symbol, params.destination.symbol);
|
|
18306
|
+
const direction = params.source.symbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
|
|
18307
|
+
const variables = {
|
|
18308
|
+
poolId: pool.poolId,
|
|
18309
|
+
sourceChain: params.source.chain,
|
|
18310
|
+
destinationChain: params.destination.chain,
|
|
18311
|
+
direction
|
|
18312
|
+
};
|
|
18313
|
+
const response = await this.queryClient.request(AVAILABLE_LIQUIDITY, variables);
|
|
18314
|
+
if (!response?.poolChainLiquidities?.nodes || !response?.poolRoutes?.nodes) {
|
|
18315
|
+
throw new InvalidLiquidityIndexerResponseError("liquidity connections are missing");
|
|
17962
18316
|
}
|
|
17963
|
-
const
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
});
|
|
18317
|
+
const chainLiquidity = response.poolChainLiquidities.nodes[0];
|
|
18318
|
+
if (!chainLiquidity) return void 0;
|
|
18319
|
+
const route = response.poolRoutes.nodes[0];
|
|
17967
18320
|
return {
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
tokenAddress:
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
|
|
18321
|
+
sourceChain: params.source.chain,
|
|
18322
|
+
destinationChain: params.destination.chain,
|
|
18323
|
+
tokenAddress: normalizeEvmAddress(params.destination.address, "destination token"),
|
|
18324
|
+
updatedAt: readIndexerDate(chainLiquidity.lastUpdatedAt, "destination lastUpdatedAt"),
|
|
18325
|
+
destination: readLiquiditySlice(chainLiquidity.depth, chainLiquidity.bidCount, "destination"),
|
|
18326
|
+
unrestricted: readLiquiditySlice(
|
|
18327
|
+
chainLiquidity.unrestrictedDepth,
|
|
18328
|
+
chainLiquidity.unrestrictedBidCount,
|
|
18329
|
+
"unrestricted"
|
|
18330
|
+
),
|
|
18331
|
+
explicitRoute: route ? {
|
|
18332
|
+
...readLiquiditySlice(route.depth, route.bidCount, "explicit route"),
|
|
18333
|
+
updatedAt: readIndexerDate(route.lastUpdatedAt, "route lastUpdatedAt")
|
|
18334
|
+
} : null
|
|
17976
18335
|
};
|
|
17977
18336
|
}
|
|
17978
18337
|
/**
|
|
17979
|
-
*
|
|
17980
|
-
*
|
|
18338
|
+
* Returns chain-specific buy and sell rates in less-valued quote-token units
|
|
18339
|
+
* per one base token.
|
|
17981
18340
|
*
|
|
17982
|
-
*
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17988
|
-
);
|
|
17989
|
-
const
|
|
17990
|
-
const
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
}
|
|
17997
|
-
const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
|
|
17998
|
-
const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
|
|
17999
|
-
const liquidityByChain = connection.groupedAggregates.map((group, index) => {
|
|
18000
|
-
const [chain, tokenAddress] = group.keys;
|
|
18001
|
-
if (!chain?.trim() || !tokenAddress) {
|
|
18002
|
-
throw new InvalidAvailableLiquiditySnapshotError(
|
|
18003
|
-
params.commitment,
|
|
18004
|
-
`liquidity group ${index} has invalid keys`
|
|
18005
|
-
);
|
|
18006
|
-
}
|
|
18007
|
-
const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
|
|
18008
|
-
tokenAddress,
|
|
18009
|
-
params.commitment,
|
|
18010
|
-
`liquidity group ${index} tokenAddress`
|
|
18011
|
-
);
|
|
18012
|
-
if (normalizedTokenAddress !== params.tokenAddress) {
|
|
18013
|
-
throw new InvalidAvailableLiquiditySnapshotError(
|
|
18014
|
-
params.commitment,
|
|
18015
|
-
`liquidity group ${index} tokenAddress does not match the snapshot output token`
|
|
18016
|
-
);
|
|
18017
|
-
}
|
|
18018
|
-
return {
|
|
18019
|
-
chain: chain.trim(),
|
|
18020
|
-
tokenAddress: normalizedTokenAddress,
|
|
18021
|
-
totalLiquidity: parseSnapshotBigInt(
|
|
18022
|
-
group.sum.balance ?? "0",
|
|
18023
|
-
params.commitment,
|
|
18024
|
-
`liquidity group ${index} balance`
|
|
18025
|
-
),
|
|
18026
|
-
providerCount: parseProviderCount(
|
|
18027
|
-
group.distinctCount.providerId,
|
|
18028
|
-
params.commitment,
|
|
18029
|
-
`liquidity group ${index}`
|
|
18030
|
-
)
|
|
18031
|
-
};
|
|
18341
|
+
* The requested direction is read on the destination chain; its reverse is
|
|
18342
|
+
* read on the source chain. This mirrors where each direction's output token
|
|
18343
|
+
* must be delivered for a cross-chain trade.
|
|
18344
|
+
*/
|
|
18345
|
+
async getBuyAndSellRates(params) {
|
|
18346
|
+
const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
|
|
18347
|
+
const directDirection = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
|
|
18348
|
+
const reverseDirection = directDirection === SELL ? BUY : SELL;
|
|
18349
|
+
const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
|
|
18350
|
+
poolId: pool.poolId,
|
|
18351
|
+
directChain: params.destinationChain,
|
|
18352
|
+
directDirection,
|
|
18353
|
+
reverseChain: params.sourceChain,
|
|
18354
|
+
reverseDirection
|
|
18032
18355
|
});
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
|
|
18356
|
+
if (!response?.direct?.nodes || !response?.reverse?.nodes) {
|
|
18357
|
+
throw new InvalidLiquidityIndexerResponseError("rate connections are missing");
|
|
18358
|
+
}
|
|
18359
|
+
const direct = readIndexedRate(response.direct.nodes[0], "direct rate");
|
|
18360
|
+
const reverse = readIndexedRate(response.reverse.nodes[0], "reverse rate");
|
|
18361
|
+
if (!direct && !reverse) return void 0;
|
|
18362
|
+
const quoteTokenSymbol = resolveQuoteTokenSymbol(
|
|
18363
|
+
params.tokenInSymbol,
|
|
18364
|
+
params.tokenOutSymbol,
|
|
18365
|
+
direct?.scaledRate,
|
|
18366
|
+
reverse?.scaledRate
|
|
18367
|
+
);
|
|
18368
|
+
const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
|
|
18369
|
+
const buy = quoteIsTokenOut ? direct : reverse;
|
|
18370
|
+
const sell = quoteIsTokenOut ? reverse : direct;
|
|
18371
|
+
return {
|
|
18372
|
+
baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
|
|
18373
|
+
quoteTokenSymbol,
|
|
18374
|
+
sourceChain: params.sourceChain,
|
|
18375
|
+
destinationChain: params.destinationChain,
|
|
18376
|
+
buyRate: buy ? formatUnits(buy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
|
|
18377
|
+
sellRate: sell ? formatUnits(reciprocalRate(sell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
|
|
18378
|
+
buyRateUpdatedAt: buy?.updatedAt ?? null,
|
|
18379
|
+
sellRateUpdatedAt: sell?.updatedAt ?? null
|
|
18380
|
+
};
|
|
18041
18381
|
}
|
|
18042
|
-
|
|
18043
|
-
|
|
18044
|
-
|
|
18045
|
-
|
|
18046
|
-
|
|
18047
|
-
commitment,
|
|
18048
|
-
`token decimals are not configured for ${tokenAddress} on ${chain}`
|
|
18049
|
-
);
|
|
18050
|
-
}
|
|
18051
|
-
return formatUnits(amount, decimals);
|
|
18382
|
+
};
|
|
18383
|
+
var InvalidLiquidityIndexerResponseError = class extends Error {
|
|
18384
|
+
constructor(reason) {
|
|
18385
|
+
super(`Invalid liquidity indexer response: ${reason}`);
|
|
18386
|
+
this.name = "InvalidLiquidityIndexerResponseError";
|
|
18052
18387
|
}
|
|
18053
18388
|
};
|
|
18054
|
-
var
|
|
18055
|
-
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
this.name = "InvalidAvailableLiquiditySnapshotError";
|
|
18389
|
+
var UnsupportedLiquidityAssetError = class extends Error {
|
|
18390
|
+
constructor(chain, asset) {
|
|
18391
|
+
super(`No configured liquidity asset found for ${asset} on ${chain}`);
|
|
18392
|
+
this.name = "UnsupportedLiquidityAssetError";
|
|
18059
18393
|
}
|
|
18060
18394
|
};
|
|
18061
|
-
|
|
18395
|
+
var UnsupportedLiquidityChainError = class extends Error {
|
|
18396
|
+
constructor(chainId) {
|
|
18397
|
+
super(`No configured liquidity chain found for chain ID ${chainId}`);
|
|
18398
|
+
this.name = "UnsupportedLiquidityChainError";
|
|
18399
|
+
}
|
|
18400
|
+
};
|
|
18401
|
+
function readLiquiditySlice(depth, providerCount, label) {
|
|
18402
|
+
if (!Number.isSafeInteger(providerCount) || providerCount < 0) {
|
|
18403
|
+
throw new InvalidLiquidityIndexerResponseError(`${label} provider count is invalid`);
|
|
18404
|
+
}
|
|
18405
|
+
return { totalLiquidity: formatIndexerAmount(depth, `${label} depth`), providerCount };
|
|
18406
|
+
}
|
|
18407
|
+
function formatIndexerAmount(value, label) {
|
|
18062
18408
|
try {
|
|
18063
18409
|
const amount = BigInt(value);
|
|
18064
|
-
if (amount < 0n)
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
} catch (error) {
|
|
18069
|
-
if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
|
|
18070
|
-
throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
|
|
18410
|
+
if (amount < 0n) throw new Error();
|
|
18411
|
+
return formatUnits(amount, INDEXER_FIXED_POINT_DECIMALS);
|
|
18412
|
+
} catch {
|
|
18413
|
+
throw new InvalidLiquidityIndexerResponseError(`${label} is not a non-negative integer`);
|
|
18071
18414
|
}
|
|
18072
18415
|
}
|
|
18073
|
-
function
|
|
18074
|
-
const
|
|
18075
|
-
if (
|
|
18076
|
-
|
|
18077
|
-
}
|
|
18078
|
-
return count;
|
|
18416
|
+
function readIndexerDate(value, label) {
|
|
18417
|
+
const date = new Date(dateStringtoTimestamp(value));
|
|
18418
|
+
if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
|
|
18419
|
+
return date;
|
|
18079
18420
|
}
|
|
18080
|
-
function
|
|
18421
|
+
function readIndexedRate(node, label) {
|
|
18422
|
+
if (!node) return void 0;
|
|
18081
18423
|
try {
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18424
|
+
const scaledRate = BigInt(node.rate);
|
|
18425
|
+
if (scaledRate <= 0n) throw new Error();
|
|
18426
|
+
return {
|
|
18427
|
+
scaledRate,
|
|
18428
|
+
updatedAt: readIndexerDate(node.lastUpdatedAt, `${label} lastUpdatedAt`)
|
|
18429
|
+
};
|
|
18430
|
+
} catch (error) {
|
|
18431
|
+
if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
|
|
18432
|
+
throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
|
|
18085
18433
|
}
|
|
18086
18434
|
}
|
|
18435
|
+
function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
|
|
18436
|
+
const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
|
|
18437
|
+
const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
|
|
18438
|
+
if (inputIsUsdStable !== outputIsUsdStable) return inputIsUsdStable ? tokenOutSymbol : tokenInSymbol;
|
|
18439
|
+
if (directRate !== void 0) return directRate >= POOL_RATE_SCALE ? tokenOutSymbol : tokenInSymbol;
|
|
18440
|
+
if (reverseRate !== void 0) return reverseRate >= POOL_RATE_SCALE ? tokenInSymbol : tokenOutSymbol;
|
|
18441
|
+
throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
|
|
18442
|
+
}
|
|
18443
|
+
function reciprocalRate(rate, label) {
|
|
18444
|
+
const reciprocal = POOL_RATE_SCALE * POOL_RATE_SCALE / rate;
|
|
18445
|
+
if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
|
|
18446
|
+
return reciprocal;
|
|
18447
|
+
}
|
|
18087
18448
|
|
|
18088
18449
|
// src/protocols/intents/quote/types.ts
|
|
18089
18450
|
var UnsupportedIntentQuoteStrategyError = class extends Error {
|
|
@@ -18508,8 +18869,6 @@ var IntentGateway = class _IntentGateway {
|
|
|
18508
18869
|
gasEstimator;
|
|
18509
18870
|
/** Quote strategies for pricing orders before placement, keyed by strategy name. */
|
|
18510
18871
|
quoteStrategies;
|
|
18511
|
-
/** Resolves order tokens to canonical Phantom snapshot market pairs. */
|
|
18512
|
-
phantomSnapshotPairResolver;
|
|
18513
18872
|
/**
|
|
18514
18873
|
* Private constructor — use {@link IntentGateway.create} instead.
|
|
18515
18874
|
*
|
|
@@ -18553,7 +18912,6 @@ var IntentGateway = class _IntentGateway {
|
|
|
18553
18912
|
this.bidManager = bidManager;
|
|
18554
18913
|
this.gasEstimator = gasEstimator;
|
|
18555
18914
|
this._crypto = crypto;
|
|
18556
|
-
this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
|
|
18557
18915
|
this.quoteStrategies = {
|
|
18558
18916
|
phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
|
|
18559
18917
|
dest.configService,
|
|
@@ -18633,33 +18991,62 @@ var IntentGateway = class _IntentGateway {
|
|
|
18633
18991
|
return handler.quote({ ...params, strategy }, source, destination);
|
|
18634
18992
|
}
|
|
18635
18993
|
/**
|
|
18636
|
-
* Returns
|
|
18637
|
-
* Phantom snapshot for this gateway's source and destination.
|
|
18994
|
+
* Returns indexed destination liquidity and its source-routing slices.
|
|
18638
18995
|
*
|
|
18639
|
-
*
|
|
18640
|
-
*
|
|
18641
|
-
*
|
|
18642
|
-
*
|
|
18996
|
+
* Destination, unrestricted, and explicit-route capacity come exclusively
|
|
18997
|
+
* from the indexer's pair-centric liquidity entities. The SDK does not decide
|
|
18998
|
+
* whether unrestricted bidders cover the source chain. Amounts reflect the
|
|
18999
|
+
* latest rolling sample; they are not reservations or fill guarantees.
|
|
18643
19000
|
*
|
|
18644
19001
|
* Requires a prior call to {@link withQueryClient}.
|
|
18645
19002
|
*/
|
|
18646
19003
|
async queryAvailableLiquidity(params) {
|
|
18647
19004
|
const { queryClient } = this.requireIndexer();
|
|
18648
|
-
const sourceStateMachineId = this.source.config.stateMachineId;
|
|
18649
|
-
const destinationStateMachineId = this.dest.config.stateMachineId;
|
|
18650
|
-
|
|
18651
|
-
if (!
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
|
|
19005
|
+
const sourceStateMachineId = getConfigByStateMachineId(this.source.config.stateMachineId)?.stateMachineId;
|
|
19006
|
+
const destinationStateMachineId = getConfigByStateMachineId(this.dest.config.stateMachineId)?.stateMachineId;
|
|
19007
|
+
if (!sourceStateMachineId) throw new UnsupportedLiquidityChainError(this.source.config.stateMachineId);
|
|
19008
|
+
if (!destinationStateMachineId) throw new UnsupportedLiquidityChainError(this.dest.config.stateMachineId);
|
|
19009
|
+
const sourceToken = this.source.configService.getAssetMetadataByAddress(sourceStateMachineId, params.tokenIn);
|
|
19010
|
+
const destinationToken = this.dest.configService.getAssetMetadataByAddress(
|
|
19011
|
+
destinationStateMachineId,
|
|
19012
|
+
params.tokenOut
|
|
19013
|
+
);
|
|
19014
|
+
if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceStateMachineId, params.tokenIn);
|
|
19015
|
+
if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationStateMachineId, params.tokenOut);
|
|
19016
|
+
return new LiquidityEngine(queryClient).getAvailableLiquidity({
|
|
19017
|
+
source: {
|
|
19018
|
+
chain: sourceStateMachineId,
|
|
19019
|
+
...sourceToken
|
|
19020
|
+
},
|
|
19021
|
+
destination: {
|
|
19022
|
+
chain: destinationStateMachineId,
|
|
19023
|
+
...destinationToken
|
|
19024
|
+
}
|
|
19025
|
+
});
|
|
19026
|
+
}
|
|
19027
|
+
/**
|
|
19028
|
+
* Returns chain-specific buy and sell rates in less-valued quote-token units
|
|
19029
|
+
* without requiring token addresses. Symbols are matched case-insensitively;
|
|
19030
|
+
* chain IDs are numeric IDs for chains configured in the SDK.
|
|
19031
|
+
*/
|
|
19032
|
+
async queryBuyAndSellRates(params) {
|
|
19033
|
+
const { queryClient } = this.requireIndexer();
|
|
19034
|
+
const sourceChain = chainConfigs[params.sourceChainId]?.stateMachineId;
|
|
19035
|
+
const destinationChain = chainConfigs[params.destinationChainId]?.stateMachineId;
|
|
19036
|
+
if (!sourceChain) throw new UnsupportedLiquidityChainError(params.sourceChainId);
|
|
19037
|
+
if (!destinationChain) throw new UnsupportedLiquidityChainError(params.destinationChainId);
|
|
19038
|
+
const sourceToken = this.source.configService.getAssetMetadataBySymbol(sourceChain, params.tokenInSymbol);
|
|
19039
|
+
const destinationToken = this.dest.configService.getAssetMetadataBySymbol(
|
|
19040
|
+
destinationChain,
|
|
19041
|
+
params.tokenOutSymbol
|
|
19042
|
+
);
|
|
19043
|
+
if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceChain, params.tokenInSymbol);
|
|
19044
|
+
if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationChain, params.tokenOutSymbol);
|
|
19045
|
+
return new LiquidityEngine(queryClient).getBuyAndSellRates({
|
|
19046
|
+
sourceChain,
|
|
19047
|
+
destinationChain,
|
|
19048
|
+
tokenInSymbol: sourceToken.symbol,
|
|
19049
|
+
tokenOutSymbol: destinationToken.symbol
|
|
18663
19050
|
});
|
|
18664
19051
|
}
|
|
18665
19052
|
/**
|
|
@@ -19199,14 +19586,34 @@ var IntentGateway = class _IntentGateway {
|
|
|
19199
19586
|
}
|
|
19200
19587
|
}
|
|
19201
19588
|
};
|
|
19589
|
+
|
|
19590
|
+
// src/protocols/intents/phantom-aggregation.ts
|
|
19202
19591
|
var FILL_ORDER_ABI = IntentGatewayV2_default.ABI;
|
|
19203
|
-
var
|
|
19204
|
-
var
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19592
|
+
var DECLARATION_V1 = 1;
|
|
19593
|
+
var DECLARATION_V2 = 2;
|
|
19594
|
+
var MAX_DECLARED_ENTRIES = 255;
|
|
19595
|
+
var MAX_TOKEN_ID_BYTES = 32;
|
|
19596
|
+
function tokenIdToBytes(tokenId) {
|
|
19597
|
+
if (tokenId < 0n) throw new Error(`Uniswap V4 tokenId cannot be negative: ${tokenId}`);
|
|
19598
|
+
const bytes = [];
|
|
19599
|
+
let rest = tokenId;
|
|
19600
|
+
while (rest > 0n) {
|
|
19601
|
+
bytes.unshift(Number(rest & 0xffn));
|
|
19602
|
+
rest >>= 8n;
|
|
19603
|
+
}
|
|
19604
|
+
return bytes.length > 0 ? bytes : [0];
|
|
19605
|
+
}
|
|
19606
|
+
function encodePhantomBidDeclaration(declaration) {
|
|
19607
|
+
const chains2 = declaration.acceptedSourceChains ?? [];
|
|
19608
|
+
const positions = declaration.uniswapV4Positions ?? [];
|
|
19609
|
+
if (chains2.length > MAX_DECLARED_ENTRIES) {
|
|
19610
|
+
throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} source chains`);
|
|
19208
19611
|
}
|
|
19209
|
-
|
|
19612
|
+
if (positions.length > MAX_DECLARED_ENTRIES) {
|
|
19613
|
+
throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} Uniswap V4 positions`);
|
|
19614
|
+
}
|
|
19615
|
+
const version = positions.length > 0 ? DECLARATION_V2 : DECLARATION_V1;
|
|
19616
|
+
const bytes = [version, chains2.length];
|
|
19210
19617
|
for (const chain of chains2) {
|
|
19211
19618
|
const encoded = stringToU8a(chain);
|
|
19212
19619
|
if (encoded.length === 0 || encoded.length > 255) {
|
|
@@ -19214,25 +19621,59 @@ function encodeAcceptedSourceChains(chains2) {
|
|
|
19214
19621
|
}
|
|
19215
19622
|
bytes.push(encoded.length, ...encoded);
|
|
19216
19623
|
}
|
|
19624
|
+
if (version === DECLARATION_V2) {
|
|
19625
|
+
bytes.push(positions.length);
|
|
19626
|
+
for (const tokenId of positions) {
|
|
19627
|
+
const encoded = tokenIdToBytes(tokenId);
|
|
19628
|
+
if (encoded.length > MAX_TOKEN_ID_BYTES) {
|
|
19629
|
+
throw new Error(`Uniswap V4 tokenId exceeds uint256: ${tokenId}`);
|
|
19630
|
+
}
|
|
19631
|
+
bytes.push(encoded.length, ...encoded);
|
|
19632
|
+
}
|
|
19633
|
+
}
|
|
19217
19634
|
return u8aToHex(new Uint8Array(bytes));
|
|
19218
19635
|
}
|
|
19219
|
-
function
|
|
19220
|
-
|
|
19636
|
+
function decodePhantomBidDeclaration(paymasterAndData) {
|
|
19637
|
+
const absent = { acceptedSources: null, uniswapV4Positions: [] };
|
|
19638
|
+
if (!paymasterAndData || !isHex$1(paymasterAndData)) return absent;
|
|
19221
19639
|
const bytes = hexToU8a(paymasterAndData);
|
|
19222
|
-
if (bytes.length < 2
|
|
19223
|
-
const
|
|
19640
|
+
if (bytes.length < 2) return absent;
|
|
19641
|
+
const version = bytes[0];
|
|
19642
|
+
if (version !== DECLARATION_V1 && version !== DECLARATION_V2) return absent;
|
|
19224
19643
|
const chains2 = [];
|
|
19225
19644
|
let offset = 2;
|
|
19226
|
-
for (let entry = 0; entry <
|
|
19227
|
-
if (offset >= bytes.length) return
|
|
19645
|
+
for (let entry = 0; entry < bytes[1]; entry++) {
|
|
19646
|
+
if (offset >= bytes.length) return absent;
|
|
19228
19647
|
const length = bytes[offset];
|
|
19229
19648
|
offset += 1;
|
|
19230
|
-
if (length === 0 || offset + length > bytes.length) return
|
|
19649
|
+
if (length === 0 || offset + length > bytes.length) return absent;
|
|
19231
19650
|
chains2.push(u8aToString(bytes.subarray(offset, offset + length)));
|
|
19232
19651
|
offset += length;
|
|
19233
19652
|
}
|
|
19234
|
-
|
|
19235
|
-
|
|
19653
|
+
const positions = [];
|
|
19654
|
+
if (version === DECLARATION_V2) {
|
|
19655
|
+
if (offset >= bytes.length) return absent;
|
|
19656
|
+
const count = bytes[offset];
|
|
19657
|
+
offset += 1;
|
|
19658
|
+
for (let entry = 0; entry < count; entry++) {
|
|
19659
|
+
if (offset >= bytes.length) return absent;
|
|
19660
|
+
const length = bytes[offset];
|
|
19661
|
+
offset += 1;
|
|
19662
|
+
if (length === 0 || length > MAX_TOKEN_ID_BYTES || offset + length > bytes.length) return absent;
|
|
19663
|
+
let tokenId = 0n;
|
|
19664
|
+
for (const byte of bytes.subarray(offset, offset + length)) tokenId = tokenId << 8n | BigInt(byte);
|
|
19665
|
+
positions.push(tokenId);
|
|
19666
|
+
offset += length;
|
|
19667
|
+
}
|
|
19668
|
+
}
|
|
19669
|
+
if (offset !== bytes.length) return absent;
|
|
19670
|
+
return { acceptedSources: chains2, uniswapV4Positions: positions };
|
|
19671
|
+
}
|
|
19672
|
+
function encodeAcceptedSourceChains(chains2) {
|
|
19673
|
+
return encodePhantomBidDeclaration({ acceptedSourceChains: chains2 });
|
|
19674
|
+
}
|
|
19675
|
+
function decodeAcceptedSourceChains(paymasterAndData) {
|
|
19676
|
+
return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
|
|
19236
19677
|
}
|
|
19237
19678
|
FILL_ORDER_ABI.find(
|
|
19238
19679
|
(item) => item?.type === "function" && item?.name === "fillOrder"
|
|
@@ -23707,6 +24148,6 @@ async function teleportDot(param_) {
|
|
|
23707
24148
|
return stream;
|
|
23708
24149
|
}
|
|
23709
24150
|
|
|
23710
|
-
export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidPhantomSnapshotError, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, 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, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodeUserOpScale, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeISMPMessage, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
|
|
24151
|
+
export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, INCLUSION_TIMEOUT_MS, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidLiquidityIndexerResponseError, InvalidPhantomSnapshotError, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, 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, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodePhantomBidDeclaration, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeISMPMessage, encodePhantomBidDeclaration, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, 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, requestCommitmentKey, responseCommitmentKey, retryPromise, sortPoolSymbols, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
|
|
23711
24152
|
//# sourceMappingURL=index.js.map
|
|
23712
24153
|
//# sourceMappingURL=index.js.map
|