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