@gibs/bridge-sdk 1.10.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +234 -432
- package/dist/abis.d.ts +1 -0
- package/dist/abis.js +3 -0
- package/dist/bridge-limits.d.ts +103 -0
- package/dist/bridge-limits.js +84 -0
- package/dist/{src/chain-info.d.ts → chain-info.d.ts} +66 -27
- package/dist/{src/chain-info.js → chain-info.js} +106 -46
- package/dist/chain-names.d.ts +160 -0
- package/dist/chain-names.js +200 -0
- package/dist/chains.d.ts +65 -0
- package/dist/chains.js +284 -0
- package/dist/config.d.ts +216 -0
- package/dist/{src/config.js → config.js} +151 -44
- package/dist/consolidated-quote.d.ts +554 -0
- package/dist/consolidated-quote.js +773 -0
- package/dist/destination-calldata.d.ts +118 -0
- package/dist/destination-calldata.js +152 -0
- package/dist/ecosystems.d.ts +217 -0
- package/dist/ecosystems.js +288 -0
- package/dist/hyperliquid-execution.d.ts +152 -0
- package/dist/hyperliquid-execution.js +168 -0
- package/dist/image-links.d.ts +117 -0
- package/dist/image-links.js +142 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/lifi-execution-guard.d.ts +199 -0
- package/dist/lifi-execution-guard.js +400 -0
- package/dist/near-intents-conformance.d.ts +85 -0
- package/dist/near-intents-conformance.js +329 -0
- package/dist/near-intents-route.d.ts +47 -0
- package/dist/near-intents-route.js +265 -0
- package/dist/near-intents.d.ts +524 -0
- package/dist/near-intents.js +1055 -0
- package/dist/origin-deposit.d.ts +326 -0
- package/dist/origin-deposit.js +68 -0
- package/dist/pre-quote.d.ts +198 -0
- package/dist/pre-quote.js +175 -0
- package/dist/prices.d.ts +107 -0
- package/dist/prices.js +162 -0
- package/dist/providers.d.ts +221 -0
- package/dist/providers.js +157 -0
- package/dist/quote-fees.d.ts +102 -0
- package/dist/quote-fees.js +195 -0
- package/dist/referral.d.ts +77 -0
- package/dist/referral.js +72 -0
- package/dist/relay-currencies.d.ts +47 -0
- package/dist/relay-currencies.js +89 -0
- package/dist/relay-quote.d.ts +301 -0
- package/dist/relay-quote.js +983 -0
- package/dist/route-steps.d.ts +138 -0
- package/dist/route-steps.js +305 -0
- package/dist/routing/amounts.d.ts +80 -0
- package/dist/routing/amounts.js +120 -0
- package/dist/routing/candidate-route.d.ts +43 -0
- package/dist/routing/candidate-route.js +45 -0
- package/dist/routing/candidate.d.ts +237 -0
- package/dist/routing/candidate.js +96 -0
- package/dist/routing/carriers/ask-answer.d.ts +58 -0
- package/dist/routing/carriers/omnibridge.d.ts +137 -0
- package/dist/routing/carriers/omnibridge.js +419 -0
- package/dist/routing/carriers/pulsex.d.ts +111 -0
- package/dist/routing/carriers/pulsex.js +404 -0
- package/dist/routing/carriers/wrap.d.ts +25 -0
- package/dist/routing/carriers/wrap.js +115 -0
- package/dist/routing/composed-route-quality.d.ts +103 -0
- package/dist/routing/composed-route-quality.js +108 -0
- package/dist/routing/entry-chain-anchor.d.ts +88 -0
- package/dist/routing/entry-chain-anchor.js +55 -0
- package/dist/routing/format.d.ts +31 -0
- package/dist/routing/format.js +58 -0
- package/dist/routing/index.d.ts +34 -0
- package/dist/routing/index.js +42 -0
- package/dist/routing/producers.d.ts +770 -0
- package/dist/routing/producers.js +1564 -0
- package/dist/routing/providers.d.ts +26 -0
- package/dist/routing/providers.js +38 -0
- package/dist/routing/route-data.d.ts +355 -0
- package/dist/routing/route-data.js +1 -0
- package/dist/routing/search/compose.d.ts +168 -0
- package/dist/routing/search/compose.js +98 -0
- package/dist/routing/search/graph.d.ts +639 -0
- package/dist/routing/search/graph.js +445 -0
- package/dist/routing/search/plan-route-data.d.ts +244 -0
- package/dist/routing/search/plan-route-data.js +767 -0
- package/dist/routing/search/score.d.ts +324 -0
- package/dist/routing/search/score.js +400 -0
- package/dist/routing/usd-value.d.ts +17 -0
- package/dist/routing/usd-value.js +25 -0
- package/dist/settings.d.ts +692 -0
- package/dist/{src/settings.js → settings.js} +338 -71
- package/dist/ton-execution.d.ts +117 -0
- package/dist/ton-execution.js +247 -0
- package/dist/tron-execution.d.ts +146 -0
- package/dist/tron-execution.js +239 -0
- package/dist/{src/types.d.ts → types.d.ts} +8 -1
- package/dist/types.js +1 -0
- package/package.json +210 -23
- package/dist/src/abis.d.ts +0 -384
- package/dist/src/abis.js +0 -68
- package/dist/src/chains.d.ts +0 -6
- package/dist/src/chains.js +0 -86
- package/dist/src/config.d.ts +0 -136
- package/dist/src/image-links.d.ts +0 -41
- package/dist/src/image-links.js +0 -45
- package/dist/src/index.d.ts +0 -8
- package/dist/src/index.js +0 -8
- package/dist/src/settings.d.ts +0 -357
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/abis.ts +0 -70
- package/src/chain-info.ts +0 -464
- package/src/chains.ts +0 -88
- package/src/config.ts +0 -480
- package/src/fee-type.ts +0 -14
- package/src/image-links.ts +0 -61
- package/src/index.ts +0 -8
- package/src/settings.ts +0 -898
- package/src/types.ts +0 -48
- package/tsconfig.json +0 -16
- /package/dist/{src/fee-type.d.ts → fee-type.d.ts} +0 -0
- /package/dist/{src/fee-type.js → fee-type.js} +0 -0
- /package/dist/{src/types.js → routing/carriers/ask-answer.js} +0 -0
package/dist/abis.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@gibs/abis';
|
package/dist/abis.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { PublicClient } from 'viem';
|
|
2
|
+
import type { DestinationCall } from './destination-calldata.js';
|
|
3
|
+
/**
|
|
4
|
+
* The two bounds the entry mediator puts on a single transfer, read from the
|
|
5
|
+
* contract the transaction actually calls.
|
|
6
|
+
*
|
|
7
|
+
* BOTH ARE READ FROM THE ENTRY SIDE, NOT THE FAR SIDE. The mediator validates
|
|
8
|
+
* the amount before it emits anything, so the bound that decides whether a
|
|
9
|
+
* transfer happens at all belongs to the chain the funds leave. The far side
|
|
10
|
+
* keeps its own limits under the same names, and nothing makes the two agree —
|
|
11
|
+
* they are separately governed contracts. Measured 2026-08-31: raising
|
|
12
|
+
* `minPerTx` on the Ethereum mediator alone, with PulseChain untouched, makes
|
|
13
|
+
* the entry call revert. `bridge-limits.fork.test.ts` keeps that honest.
|
|
14
|
+
*/
|
|
15
|
+
export type EntryLimits = {
|
|
16
|
+
/** Smallest transfer the mediator will accept, in the token's base units. */
|
|
17
|
+
minimumPerTransaction: bigint;
|
|
18
|
+
/**
|
|
19
|
+
* Largest transfer the mediator will accept RIGHT NOW, in base units.
|
|
20
|
+
*
|
|
21
|
+
* This is not a constant ceiling. The mediator returns the lower of its
|
|
22
|
+
* per-transfer cap and what is left of today's allowance, so it falls as the
|
|
23
|
+
* day is used up and resets on its own schedule. Do not cache it.
|
|
24
|
+
*/
|
|
25
|
+
availablePerTransaction: bigint;
|
|
26
|
+
};
|
|
27
|
+
/** Why an amount cannot enter the bridge, or that it can. */
|
|
28
|
+
export type EntryAmountVerdict = {
|
|
29
|
+
fits: true;
|
|
30
|
+
} | {
|
|
31
|
+
fits: false;
|
|
32
|
+
reason: 'below-minimum';
|
|
33
|
+
minimumPerTransaction: bigint;
|
|
34
|
+
} | {
|
|
35
|
+
fits: false;
|
|
36
|
+
reason: 'above-available';
|
|
37
|
+
availablePerTransaction: bigint;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Exported so a batched reader (`routing/carriers/omnibridge.ts`'s `prepare`)
|
|
41
|
+
* can multicall the same two functions across many tokens in one call rather
|
|
42
|
+
* than duplicating this ABI.
|
|
43
|
+
*/
|
|
44
|
+
export declare const entryLimitsAbi: readonly [{
|
|
45
|
+
readonly name: "minPerTx";
|
|
46
|
+
readonly type: "function";
|
|
47
|
+
readonly stateMutability: "view";
|
|
48
|
+
readonly inputs: readonly [{
|
|
49
|
+
readonly type: "address";
|
|
50
|
+
readonly name: "token";
|
|
51
|
+
}];
|
|
52
|
+
readonly outputs: readonly [{
|
|
53
|
+
readonly type: "uint256";
|
|
54
|
+
}];
|
|
55
|
+
}, {
|
|
56
|
+
readonly name: "maxAvailablePerTx";
|
|
57
|
+
readonly type: "function";
|
|
58
|
+
readonly stateMutability: "view";
|
|
59
|
+
readonly inputs: readonly [{
|
|
60
|
+
readonly type: "address";
|
|
61
|
+
readonly name: "token";
|
|
62
|
+
}];
|
|
63
|
+
readonly outputs: readonly [{
|
|
64
|
+
readonly type: "uint256";
|
|
65
|
+
}];
|
|
66
|
+
}];
|
|
67
|
+
/**
|
|
68
|
+
* Reads the bounds the entry mediator will enforce on this exact call.
|
|
69
|
+
*
|
|
70
|
+
* IT TAKES THE CALL, NOT A MEDIATOR AND A TOKEN. The two travel together on the
|
|
71
|
+
* destination call precisely so a caller cannot pair one route's mediator with
|
|
72
|
+
* another route's token and get a plausible number back — the reads would
|
|
73
|
+
* succeed and the answer would be about a transfer nobody is making.
|
|
74
|
+
*
|
|
75
|
+
* @param destinationCall - the entry call, or null when none could be built
|
|
76
|
+
* @param entryChainClient - a client on the chain the funds leave
|
|
77
|
+
* @returns the two bounds, or null when there is no call to measure
|
|
78
|
+
*/
|
|
79
|
+
export declare const bridgeEntryLimits: ({ destinationCall, entryChainClient, }: {
|
|
80
|
+
destinationCall: Pick<DestinationCall, "entryMediator" | "entryTokenAddress"> | null;
|
|
81
|
+
entryChainClient: PublicClient;
|
|
82
|
+
}) => Promise<EntryLimits | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Decides whether an amount can enter the bridge.
|
|
85
|
+
*
|
|
86
|
+
* WHY THIS IS WORTH CHECKING BEFORE A ROUTE IS OFFERED: on a consolidated
|
|
87
|
+
* route the entry call is made by an aggregator's executor, not by the user.
|
|
88
|
+
* When the mediator rejects the amount the executor's call reverts, the
|
|
89
|
+
* aggregator has already delivered the funds, and they stop at the fallback
|
|
90
|
+
* address on the entry chain. The user is left holding an entry-chain token
|
|
91
|
+
* and a second transfer to make. Refusing the quote costs them nothing.
|
|
92
|
+
*
|
|
93
|
+
* The bounds are inclusive: the mediator accepts an amount equal to either.
|
|
94
|
+
*
|
|
95
|
+
* @param amount - the transfer, in the token's base units
|
|
96
|
+
* @param limits - the bounds from {@link bridgeEntryLimits}, or null when they
|
|
97
|
+
* could not be read
|
|
98
|
+
* @returns a verdict naming the bound that was missed
|
|
99
|
+
*/
|
|
100
|
+
export declare const checkEntryAmount: ({ amount, limits, }: {
|
|
101
|
+
amount: bigint;
|
|
102
|
+
limits: EntryLimits | null;
|
|
103
|
+
}) => EntryAmountVerdict;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { parseAbi } from 'viem';
|
|
2
|
+
/**
|
|
3
|
+
* Exported so a batched reader (`routing/carriers/omnibridge.ts`'s `prepare`)
|
|
4
|
+
* can multicall the same two functions across many tokens in one call rather
|
|
5
|
+
* than duplicating this ABI.
|
|
6
|
+
*/
|
|
7
|
+
export const entryLimitsAbi = parseAbi([
|
|
8
|
+
'function minPerTx(address token) external view returns(uint256)',
|
|
9
|
+
'function maxAvailablePerTx(address token) external view returns(uint256)',
|
|
10
|
+
]);
|
|
11
|
+
/**
|
|
12
|
+
* Reads the bounds the entry mediator will enforce on this exact call.
|
|
13
|
+
*
|
|
14
|
+
* IT TAKES THE CALL, NOT A MEDIATOR AND A TOKEN. The two travel together on the
|
|
15
|
+
* destination call precisely so a caller cannot pair one route's mediator with
|
|
16
|
+
* another route's token and get a plausible number back — the reads would
|
|
17
|
+
* succeed and the answer would be about a transfer nobody is making.
|
|
18
|
+
*
|
|
19
|
+
* @param destinationCall - the entry call, or null when none could be built
|
|
20
|
+
* @param entryChainClient - a client on the chain the funds leave
|
|
21
|
+
* @returns the two bounds, or null when there is no call to measure
|
|
22
|
+
*/
|
|
23
|
+
export const bridgeEntryLimits = async ({ destinationCall, entryChainClient, }) => {
|
|
24
|
+
if (!destinationCall) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const { entryMediator, entryTokenAddress } = destinationCall;
|
|
28
|
+
const [minimumPerTransaction, availablePerTransaction] = await Promise.all([
|
|
29
|
+
entryChainClient.readContract({
|
|
30
|
+
abi: entryLimitsAbi,
|
|
31
|
+
functionName: 'minPerTx',
|
|
32
|
+
args: [entryTokenAddress],
|
|
33
|
+
address: entryMediator,
|
|
34
|
+
}),
|
|
35
|
+
entryChainClient.readContract({
|
|
36
|
+
abi: entryLimitsAbi,
|
|
37
|
+
functionName: 'maxAvailablePerTx',
|
|
38
|
+
args: [entryTokenAddress],
|
|
39
|
+
address: entryMediator,
|
|
40
|
+
}),
|
|
41
|
+
]);
|
|
42
|
+
return { minimumPerTransaction, availablePerTransaction };
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Decides whether an amount can enter the bridge.
|
|
46
|
+
*
|
|
47
|
+
* WHY THIS IS WORTH CHECKING BEFORE A ROUTE IS OFFERED: on a consolidated
|
|
48
|
+
* route the entry call is made by an aggregator's executor, not by the user.
|
|
49
|
+
* When the mediator rejects the amount the executor's call reverts, the
|
|
50
|
+
* aggregator has already delivered the funds, and they stop at the fallback
|
|
51
|
+
* address on the entry chain. The user is left holding an entry-chain token
|
|
52
|
+
* and a second transfer to make. Refusing the quote costs them nothing.
|
|
53
|
+
*
|
|
54
|
+
* The bounds are inclusive: the mediator accepts an amount equal to either.
|
|
55
|
+
*
|
|
56
|
+
* @param amount - the transfer, in the token's base units
|
|
57
|
+
* @param limits - the bounds from {@link bridgeEntryLimits}, or null when they
|
|
58
|
+
* could not be read
|
|
59
|
+
* @returns a verdict naming the bound that was missed
|
|
60
|
+
*/
|
|
61
|
+
export const checkEntryAmount = ({ amount, limits, }) => {
|
|
62
|
+
// An unread limit is not a failed one. A network hiccup must not block a
|
|
63
|
+
// route that the mediator would accept — the transaction still gets the
|
|
64
|
+
// mediator's own answer, and this guard exists to save the user a wasted
|
|
65
|
+
// crossing, not to be the only thing standing between them and a revert.
|
|
66
|
+
if (!limits) {
|
|
67
|
+
return { fits: true };
|
|
68
|
+
}
|
|
69
|
+
if (amount < limits.minimumPerTransaction) {
|
|
70
|
+
return {
|
|
71
|
+
fits: false,
|
|
72
|
+
reason: 'below-minimum',
|
|
73
|
+
minimumPerTransaction: limits.minimumPerTransaction,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (amount > limits.availablePerTransaction) {
|
|
77
|
+
return {
|
|
78
|
+
fits: false,
|
|
79
|
+
reason: 'above-available',
|
|
80
|
+
availablePerTransaction: limits.availablePerTransaction,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { fits: true };
|
|
84
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type Hex, type PublicClient } from
|
|
2
|
-
import { Chains, type Pathway } from
|
|
3
|
-
import type { BridgeKey, Token } from
|
|
4
|
-
import _ from "lodash";
|
|
1
|
+
import { type Address, type GetContractReturnType, type Hex, multicall3Abi, type PublicClient } from 'viem';
|
|
2
|
+
import { type Chains, type Pathway } from './config.js';
|
|
3
|
+
import type { BridgeKey, Token } from './types.js';
|
|
5
4
|
export type TokenBridgeInfo = {
|
|
6
5
|
originationChainId: Chains;
|
|
7
6
|
assetInAddress: Hex;
|
|
@@ -15,20 +14,42 @@ export type TokenBridgeInfo = {
|
|
|
15
14
|
foreign: Hex;
|
|
16
15
|
};
|
|
17
16
|
};
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
* @param chainId - the chain id being targeted
|
|
21
|
-
* @param target - the target
|
|
22
|
-
* @param address - the address
|
|
23
|
-
* @param client - the client
|
|
24
|
-
* @returns the bridged token address and native token address
|
|
25
|
-
*/
|
|
26
|
-
export declare const links: (({ chainId, target, address, client }: {
|
|
17
|
+
/** The inputs of one {@link links} read. */
|
|
18
|
+
type LinksInputs = {
|
|
27
19
|
chainId: number;
|
|
28
20
|
target: Hex;
|
|
29
21
|
address: Hex;
|
|
30
22
|
client: PublicClient;
|
|
31
|
-
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Returns the bridged token address and native token address that a mediator
|
|
26
|
+
* holds for one token.
|
|
27
|
+
*
|
|
28
|
+
* A non-zero answer is cached for the life of the process: a mediator never
|
|
29
|
+
* changes a mapping once the first transfer creates it. A failed read, and a
|
|
30
|
+
* `[zero, zero]` answer (the token is not bridged yet), are not cached, so the
|
|
31
|
+
* next call reads the chain again. Call {@link clearChainInfoCaches} to drop
|
|
32
|
+
* every cached answer.
|
|
33
|
+
*
|
|
34
|
+
* @param inputs.chainId - the chain id being targeted
|
|
35
|
+
* @param inputs.target - the mediator to ask
|
|
36
|
+
* @param inputs.address - the token address
|
|
37
|
+
* @param inputs.client - a client for that chain
|
|
38
|
+
* @returns the bridged token address and native token address
|
|
39
|
+
*/
|
|
40
|
+
export declare const links: ((inputs: LinksInputs) => Promise<[Hex, Hex]>) & {
|
|
41
|
+
cache: {
|
|
42
|
+
delete: (key: string) => boolean;
|
|
43
|
+
clear?: () => void;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Drops every answer this module has cached, so the next read goes to the chain.
|
|
48
|
+
*
|
|
49
|
+
* Use it after a token's first transfer, in a long-running process that wants
|
|
50
|
+
* a fresh view, or between tests.
|
|
51
|
+
*/
|
|
52
|
+
export declare const clearChainInfoCaches: () => void;
|
|
32
53
|
/**
|
|
33
54
|
* Returns the token bridge info for the given bridge key, asset in, is prod, from chain client, and to chain client
|
|
34
55
|
* @param bridgeKey - the bridge key
|
|
@@ -83,18 +104,32 @@ export declare const fetchPriceCorrective: ({ bridgeKeyPartner, assetOut, measur
|
|
|
83
104
|
}) => bigint;
|
|
84
105
|
};
|
|
85
106
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
107
|
+
* Read the smallest amount the bridge will accept for this asset on this pathway.
|
|
108
|
+
*
|
|
109
|
+
* THE ENTRY MEDIATOR DECIDES, so this always reads `pathway.from` on the chain
|
|
110
|
+
* the funds leave. `feeManager` says which side prices the transfer; it has
|
|
111
|
+
* never said which side validates the amount, and using it for both is what
|
|
112
|
+
* produced the defect this replaces.
|
|
113
|
+
*
|
|
114
|
+
* Both mediators keep a `minPerTx` under the same name, and for a token that
|
|
115
|
+
* originates on the entry chain the two sides usually hold the same number. That
|
|
116
|
+
* agreement is a coincidence of configuration - each side is separately
|
|
117
|
+
* governed, and both happen to apply one blanket default. It breaks outright for
|
|
118
|
+
* a token that originates on the far side, because the two mediators key their
|
|
119
|
+
* limits by different addresses. Wrapped Pulse entering from Ethereum is the
|
|
120
|
+
* live case: the entry mediator enforces 50,000, and the destination mediator
|
|
121
|
+
* answers 0 for that same address because it files that token under its
|
|
122
|
+
* PulseChain address instead. Reading the destination got both the chain and the
|
|
123
|
+
* token key wrong, and reported no floor where a real one stood.
|
|
124
|
+
* @param assetIn - the asset the sender holds, on the chain it leaves from
|
|
125
|
+
* @param pathway - the pathway, with any per-asset overrides already applied
|
|
126
|
+
* @param fromPublicClient - a client for the chain the funds leave
|
|
127
|
+
* @returns the smallest amount the entry mediator will accept
|
|
92
128
|
*/
|
|
93
|
-
export declare const minBridgeAmountIn: ({ assetIn, pathway, fromPublicClient,
|
|
129
|
+
export declare const minBridgeAmountIn: ({ assetIn, pathway, fromPublicClient, }: {
|
|
94
130
|
assetIn: Token;
|
|
95
131
|
pathway: Pathway;
|
|
96
132
|
fromPublicClient: PublicClient;
|
|
97
|
-
toPublicClient: PublicClient;
|
|
98
133
|
}) => Promise<bigint>;
|
|
99
134
|
/** get the symbol of the native asset, unwrap it if it needs to be */
|
|
100
135
|
export declare const nativeSymbol: (asset: {
|
|
@@ -114,12 +149,15 @@ export type PathwayExtendableConfig = {
|
|
|
114
149
|
feeH2F: bigint;
|
|
115
150
|
feeF2H: bigint;
|
|
116
151
|
};
|
|
152
|
+
/** A typed Multicall3 contract instance for one chain. */
|
|
153
|
+
export type ChainMulticall = GetContractReturnType<typeof multicall3Abi, PublicClient, Address>;
|
|
117
154
|
/**
|
|
118
|
-
* Returns the
|
|
119
|
-
* @param client -
|
|
120
|
-
* @returns the
|
|
155
|
+
* Returns the Multicall3 contract for the client's chain.
|
|
156
|
+
* @param client - a client whose `chain` is one of {@link chainsMetadata}'s chains
|
|
157
|
+
* @returns the Multicall3 contract instance
|
|
158
|
+
* @throws when the client carries no chain, or the chain has no Multicall3 address
|
|
121
159
|
*/
|
|
122
|
-
export declare const chainMulticall: (client: PublicClient) =>
|
|
160
|
+
export declare const chainMulticall: (client: PublicClient) => ChainMulticall;
|
|
123
161
|
/**
|
|
124
162
|
* Load the bridge fees for the given pathway, from chain client, and to chain client
|
|
125
163
|
* @param pathway - the pathway
|
|
@@ -127,4 +165,5 @@ export declare const chainMulticall: (client: PublicClient) => any;
|
|
|
127
165
|
* @param toChainClient - the to chain client
|
|
128
166
|
* @returns the bridge fees
|
|
129
167
|
*/
|
|
130
|
-
export declare const loadBridgeFees: ({ pathway, fromChainClient, toChainClient }: InputLoadFeeFor) => Promise<PathwayExtendableConfig>;
|
|
168
|
+
export declare const loadBridgeFees: ({ pathway, fromChainClient, toChainClient, }: InputLoadFeeFor) => Promise<PathwayExtendableConfig>;
|
|
169
|
+
export {};
|
|
@@ -1,19 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
1
|
+
import { multicallRead } from '@gibs/common/multicall';
|
|
2
|
+
import * as _ from 'lodash-es';
|
|
3
|
+
import { encodeFunctionData, getContract, multicall3Abi, zeroAddress, } from 'viem';
|
|
4
|
+
import * as abis from './abis.js';
|
|
5
|
+
import { chainsMetadata } from './chains.js';
|
|
6
|
+
import { nativeAssetOut, pathway, toChain, uniV2Routers, } from './config.js';
|
|
7
|
+
/** Cache key for the links memoize: one entry per (chain, bridge, token) triple. */
|
|
8
|
+
const linksCacheKey = ({ chainId, target, address, }) => `${chainId}-${target}-${address}`.toLowerCase();
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @param
|
|
12
|
-
* @
|
|
10
|
+
* Whether a {@link links} answer says the token has no mapping on this bridge
|
|
11
|
+
* yet. That answer is temporary: the first transfer of the token creates the
|
|
12
|
+
* mapping, so it must not be cached.
|
|
13
|
+
* @param pair - the bridged and native addresses the mediator returned
|
|
14
|
+
* @returns true when both are the zero address
|
|
15
|
+
*/
|
|
16
|
+
const isUnmappedAnswer = ([bridged, native]) => bridged === zeroAddress && native === zeroAddress;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the bridged token address and native token address that a mediator
|
|
19
|
+
* holds for one token.
|
|
20
|
+
*
|
|
21
|
+
* A non-zero answer is cached for the life of the process: a mediator never
|
|
22
|
+
* changes a mapping once the first transfer creates it. A failed read, and a
|
|
23
|
+
* `[zero, zero]` answer (the token is not bridged yet), are not cached, so the
|
|
24
|
+
* next call reads the chain again. Call {@link clearChainInfoCaches} to drop
|
|
25
|
+
* every cached answer.
|
|
26
|
+
*
|
|
27
|
+
* @param inputs.chainId - the chain id being targeted
|
|
28
|
+
* @param inputs.target - the mediator to ask
|
|
29
|
+
* @param inputs.address - the token address
|
|
30
|
+
* @param inputs.client - a client for that chain
|
|
13
31
|
* @returns the bridged token address and native token address
|
|
14
32
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
// The annotation is required, not stylistic: `@types/lodash-es` re-exports its
|
|
34
|
+
// `MemoizedFunction` type from a nested `@types/lodash` that a generated
|
|
35
|
+
// declaration file cannot name (TS2742). The body reaches for `links.cache` to
|
|
36
|
+
// evict an answer, so the cache surface is spelled out to the extent this file
|
|
37
|
+
// uses it.
|
|
38
|
+
export const links = _.memoize(async (inputs) => {
|
|
39
|
+
const { chainId, target, address, client } = inputs;
|
|
40
|
+
const evict = () => links.cache.delete(linksCacheKey(inputs));
|
|
41
|
+
const pair = await multicallRead({
|
|
17
42
|
client,
|
|
18
43
|
chain: chainsMetadata[toChain(chainId)],
|
|
19
44
|
abi: abis.inputBridge,
|
|
@@ -22,8 +47,27 @@ export const links = _.memoize(async ({ chainId, target, address, client }) => {
|
|
|
22
47
|
{ functionName: 'bridgedTokenAddress', args: [address] },
|
|
23
48
|
{ functionName: 'nativeTokenAddress', args: [address] },
|
|
24
49
|
],
|
|
50
|
+
}).catch((error) => {
|
|
51
|
+
// a transient read failure must not poison the cache for the session
|
|
52
|
+
evict();
|
|
53
|
+
throw error;
|
|
25
54
|
});
|
|
26
|
-
|
|
55
|
+
if (isUnmappedAnswer(pair)) {
|
|
56
|
+
// the token may be bridged a minute from now; ask again next time
|
|
57
|
+
evict();
|
|
58
|
+
}
|
|
59
|
+
return pair;
|
|
60
|
+
}, linksCacheKey);
|
|
61
|
+
/**
|
|
62
|
+
* Drops every answer this module has cached, so the next read goes to the chain.
|
|
63
|
+
*
|
|
64
|
+
* Use it after a token's first transfer, in a long-running process that wants
|
|
65
|
+
* a fresh view, or between tests.
|
|
66
|
+
*/
|
|
67
|
+
export const clearChainInfoCaches = () => {
|
|
68
|
+
// lodash types mark `clear` optional; the default MapCache always has it
|
|
69
|
+
links.cache.clear?.();
|
|
70
|
+
};
|
|
27
71
|
/**
|
|
28
72
|
* Returns the token bridge info for the given bridge key, asset in, is prod, from chain client, and to chain client
|
|
29
73
|
* @param bridgeKey - the bridge key
|
|
@@ -36,7 +80,6 @@ export const links = _.memoize(async ({ chainId, target, address, client }) => {
|
|
|
36
80
|
export const tokenBridgeInfo = async ({ bridgeKey, assetIn, isProd, fromChainClient, toChainClient, }) => {
|
|
37
81
|
const bridgePathway = pathway(bridgeKey, isProd, assetIn?.address);
|
|
38
82
|
if (!assetIn || !bridgePathway) {
|
|
39
|
-
console.log('missing asset in or bridge pathway');
|
|
40
83
|
return null;
|
|
41
84
|
}
|
|
42
85
|
const [, fromChain, toChain] = bridgeKey;
|
|
@@ -228,43 +271,62 @@ export const fetchPriceCorrective = ({ bridgeKeyPartner, assetOut, measurementTo
|
|
|
228
271
|
: outputToken
|
|
229
272
|
: inputToken;
|
|
230
273
|
return result;
|
|
231
|
-
}
|
|
274
|
+
},
|
|
232
275
|
};
|
|
233
276
|
};
|
|
234
277
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
278
|
+
* Read the smallest amount the bridge will accept for this asset on this pathway.
|
|
279
|
+
*
|
|
280
|
+
* THE ENTRY MEDIATOR DECIDES, so this always reads `pathway.from` on the chain
|
|
281
|
+
* the funds leave. `feeManager` says which side prices the transfer; it has
|
|
282
|
+
* never said which side validates the amount, and using it for both is what
|
|
283
|
+
* produced the defect this replaces.
|
|
284
|
+
*
|
|
285
|
+
* Both mediators keep a `minPerTx` under the same name, and for a token that
|
|
286
|
+
* originates on the entry chain the two sides usually hold the same number. That
|
|
287
|
+
* agreement is a coincidence of configuration - each side is separately
|
|
288
|
+
* governed, and both happen to apply one blanket default. It breaks outright for
|
|
289
|
+
* a token that originates on the far side, because the two mediators key their
|
|
290
|
+
* limits by different addresses. Wrapped Pulse entering from Ethereum is the
|
|
291
|
+
* live case: the entry mediator enforces 50,000, and the destination mediator
|
|
292
|
+
* answers 0 for that same address because it files that token under its
|
|
293
|
+
* PulseChain address instead. Reading the destination got both the chain and the
|
|
294
|
+
* token key wrong, and reported no floor where a real one stood.
|
|
295
|
+
* @param assetIn - the asset the sender holds, on the chain it leaves from
|
|
296
|
+
* @param pathway - the pathway, with any per-asset overrides already applied
|
|
297
|
+
* @param fromPublicClient - a client for the chain the funds leave
|
|
298
|
+
* @returns the smallest amount the entry mediator will accept
|
|
241
299
|
*/
|
|
242
|
-
export const minBridgeAmountIn = async ({ assetIn, pathway, fromPublicClient,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
address: pathway[pathway.feeManager],
|
|
249
|
-
});
|
|
250
|
-
};
|
|
300
|
+
export const minBridgeAmountIn = async ({ assetIn, pathway, fromPublicClient, }) => fromPublicClient.readContract({
|
|
301
|
+
abi: abis.inputBridge,
|
|
302
|
+
functionName: 'minPerTx',
|
|
303
|
+
args: [assetIn.address],
|
|
304
|
+
address: pathway.from,
|
|
305
|
+
});
|
|
251
306
|
/** get the symbol of the native asset, unwrap it if it needs to be */
|
|
252
307
|
export const nativeSymbol = (asset, unwrap = false) => {
|
|
253
|
-
return asset
|
|
308
|
+
return asset
|
|
309
|
+
? unwrap && asset.address === nativeAssetOut[toChain(asset.chainId)]
|
|
310
|
+
? asset.symbol.slice(1)
|
|
311
|
+
: asset.symbol
|
|
312
|
+
: '';
|
|
254
313
|
};
|
|
255
314
|
/**
|
|
256
|
-
* Returns the
|
|
257
|
-
* @param client -
|
|
258
|
-
* @returns the
|
|
315
|
+
* Returns the Multicall3 contract for the client's chain.
|
|
316
|
+
* @param client - a client whose `chain` is one of {@link chainsMetadata}'s chains
|
|
317
|
+
* @returns the Multicall3 contract instance
|
|
318
|
+
* @throws when the client carries no chain, or the chain has no Multicall3 address
|
|
259
319
|
*/
|
|
260
320
|
export const chainMulticall = (client) => {
|
|
261
|
-
const chainId = client.chain
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
321
|
+
const chainId = client.chain?.id;
|
|
322
|
+
if (chainId === undefined) {
|
|
323
|
+
throw new Error('chainMulticall: the client has no chain configured');
|
|
324
|
+
}
|
|
325
|
+
const address = chainsMetadata[toChain(chainId)]?.contracts?.multicall3?.address;
|
|
326
|
+
if (!address) {
|
|
327
|
+
throw new Error(`chainMulticall: no Multicall3 address known for chain ${chainId}`);
|
|
328
|
+
}
|
|
329
|
+
return getContract({ abi: multicall3Abi, client, address });
|
|
268
330
|
};
|
|
269
331
|
/**
|
|
270
332
|
* Load the bridge fees for the given pathway, from chain client, and to chain client
|
|
@@ -273,10 +335,8 @@ export const chainMulticall = (client) => {
|
|
|
273
335
|
* @param toChainClient - the to chain client
|
|
274
336
|
* @returns the bridge fees
|
|
275
337
|
*/
|
|
276
|
-
export const loadBridgeFees = async ({ pathway, fromChainClient, toChainClient }) => {
|
|
277
|
-
const multicall = pathway.feeManager === 'from'
|
|
278
|
-
? chainMulticall(fromChainClient)
|
|
279
|
-
: chainMulticall(toChainClient);
|
|
338
|
+
export const loadBridgeFees = async ({ pathway, fromChainClient, toChainClient, }) => {
|
|
339
|
+
const multicall = pathway.feeManager === 'from' ? chainMulticall(fromChainClient) : chainMulticall(toChainClient);
|
|
280
340
|
const [feeManagerResponse] = await multicall.read.aggregate3([
|
|
281
341
|
[
|
|
282
342
|
{
|