@gibs/bridge-sdk 1.12.0 → 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/bridge-limits.d.ts +103 -0
- package/dist/bridge-limits.js +84 -0
- package/dist/chain-info.d.ts +169 -0
- package/dist/chain-info.js +410 -0
- 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/config.js +493 -0
- 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/fee-type.d.ts +10 -0
- package/dist/fee-type.js +9 -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/ask-answer.js +1 -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/settings.js +909 -0
- 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/types.d.ts +51 -0
- package/dist/types.js +1 -0
- package/package.json +203 -23
- package/src/chain-info.test.ts +0 -968
- package/src/chain-info.ts +0 -498
- package/src/chains.test.ts +0 -123
- package/src/chains.ts +0 -88
- package/src/config.test.ts +0 -679
- package/src/config.ts +0 -500
- package/src/fee-type.test.ts +0 -45
- package/src/fee-type.ts +0 -14
- package/src/image-links.test.ts +0 -160
- package/src/image-links.ts +0 -65
- package/src/index.ts +0 -8
- package/src/settings.test.ts +0 -2754
- package/src/settings.ts +0 -875
- package/src/types.ts +0 -48
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -13
- /package/{src/abis.ts → dist/abis.js} +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { getAddress, isAddress } from 'viem';
|
|
2
|
+
// Solana public keys are base58 (no 0, O, I, l) and 32–44 characters long.
|
|
3
|
+
const base58Solana = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
4
|
+
// Bitcoin mainnet: bech32 (bc1…) or base58 pay-to-key/script (starts 1 or 3).
|
|
5
|
+
const bech32Bitcoin = /^bc1[0-9ac-hj-np-z]{6,87}$/;
|
|
6
|
+
const base58Bitcoin = /^[13][1-9A-HJ-NP-Za-km-z]{25,39}$/;
|
|
7
|
+
// Tron base58check addresses are always 34 characters and always begin with a
|
|
8
|
+
// capital T (the encoding of the 0x41 mainnet prefix byte).
|
|
9
|
+
const base58Tron = /^T[1-9A-HJ-NP-Za-km-z]{33}$/;
|
|
10
|
+
// The Open Network has two interchangeable address encodings, and Relay uses
|
|
11
|
+
// both: the 48-character "user-friendly" base64url form (EQ/UQ on mainnet,
|
|
12
|
+
// kQ/0Q on testnet), and the "raw" form of a workchain, a colon, and a
|
|
13
|
+
// 64-character hex account identifier.
|
|
14
|
+
const friendlyTon = /^[EUk0]Q[A-Za-z0-9_-]{46}$/;
|
|
15
|
+
const rawTon = /^-?\d+:[0-9a-fA-F]{64}$/;
|
|
16
|
+
// HyperCore token identifiers are hex of a length Ethereum never uses: sixteen
|
|
17
|
+
// bytes for most tokens, nineteen for a few. Twenty is allowed too so a token
|
|
18
|
+
// that is addressed by a full Ethereum address still resolves.
|
|
19
|
+
const hyperliquidTokenId = /^0x[0-9a-fA-F]{32,40}$/;
|
|
20
|
+
// Zcash TRANSPARENT addresses only, and the restriction is the service's, not
|
|
21
|
+
// ours: a shielded (`zs1…`) or unified (`u1…`) address is refused at the door
|
|
22
|
+
// with `refundTo is not valid`, before a solver is ever reached. Both
|
|
23
|
+
// transparent forms are base58check over a two-byte prefix, which always
|
|
24
|
+
// encodes to exactly 35 characters: `t1` pays to a public key hash, `t3` to a
|
|
25
|
+
// script hash. The pattern rejects the shielded forms by construction, because
|
|
26
|
+
// neither begins with `t`.
|
|
27
|
+
const transparentZcash = /^t[13][1-9A-HJ-NP-Za-km-z]{33}$/;
|
|
28
|
+
const evmDescriptor = {
|
|
29
|
+
id: 'evm',
|
|
30
|
+
label: 'Ethereum Virtual Machine',
|
|
31
|
+
caipNamespace: 'eip155',
|
|
32
|
+
// The all-zero address, exactly as the Relay builder uses across every
|
|
33
|
+
// Ethereum Virtual Machine chain for the native gas token.
|
|
34
|
+
nativeAssetSentinel: '0x0000000000000000000000000000000000000000',
|
|
35
|
+
// The only proof the product holds. The bridge's own contracts run on
|
|
36
|
+
// PulseChain, Ethereum and Binance Smart Chain, and every aggregator origin
|
|
37
|
+
// LI.FI carries is an Ethereum Virtual Machine chain, so this is the family
|
|
38
|
+
// real transfers have crossed.
|
|
39
|
+
endToEndProof: 'verified',
|
|
40
|
+
isValidAddress: (value) => isAddress(value),
|
|
41
|
+
// getAddress applies the mixed-case checksum; it throws on a non-address, so
|
|
42
|
+
// fall back to the raw input to keep normalize total and side-effect-free.
|
|
43
|
+
normalizeAddress: (value) => (isAddress(value) ? getAddress(value) : value),
|
|
44
|
+
addressCasing: 'insensitive',
|
|
45
|
+
};
|
|
46
|
+
const svmDescriptor = {
|
|
47
|
+
id: 'svm',
|
|
48
|
+
label: 'Solana',
|
|
49
|
+
caipNamespace: 'solana',
|
|
50
|
+
// Solana native currency (the System Program address), as Relay expects it.
|
|
51
|
+
nativeAssetSentinel: '11111111111111111111111111111111',
|
|
52
|
+
// Connects through the Reown AppKit Solana adapter and signs for real
|
|
53
|
+
// (`useSolanaDepositSigner` builds a versioned transaction and calls the
|
|
54
|
+
// wallet's sign-and-send). Every test around it drives a mocked wallet, and
|
|
55
|
+
// Relay — the only provider that quotes this origin — has never had its
|
|
56
|
+
// on-chain execution confirmed by a funded transfer.
|
|
57
|
+
endToEndProof: 'pending',
|
|
58
|
+
isValidAddress: (value) => base58Solana.test(value),
|
|
59
|
+
// base58 is case-significant; canonical form is the input unchanged.
|
|
60
|
+
normalizeAddress: (value) => value,
|
|
61
|
+
addressCasing: 'significant',
|
|
62
|
+
};
|
|
63
|
+
const bvmDescriptor = {
|
|
64
|
+
id: 'bvm',
|
|
65
|
+
label: 'Bitcoin',
|
|
66
|
+
caipNamespace: 'bip122',
|
|
67
|
+
// Bitcoin native currency string, as Relay expects it.
|
|
68
|
+
nativeAssetSentinel: 'bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8',
|
|
69
|
+
// The best-evidenced of the four, and still short of proof. A partially
|
|
70
|
+
// signed transaction the pipeline builds is accepted by a real node, but
|
|
71
|
+
// that test runs against a local regtest node behind `BITCOIN_REGTEST=1`
|
|
72
|
+
// and never in the normal suite. The browser wallet that signs in
|
|
73
|
+
// production is driven by nothing but a mock, and no funded mainnet
|
|
74
|
+
// transfer has completed.
|
|
75
|
+
endToEndProof: 'pending',
|
|
76
|
+
isValidAddress: (value) => bech32Bitcoin.test(value) || base58Bitcoin.test(value),
|
|
77
|
+
// bech32 and base58 are case-significant; canonical form is the input unchanged.
|
|
78
|
+
normalizeAddress: (value) => value,
|
|
79
|
+
addressCasing: 'significant',
|
|
80
|
+
};
|
|
81
|
+
const tvmDescriptor = {
|
|
82
|
+
id: 'tvm',
|
|
83
|
+
label: 'Tron',
|
|
84
|
+
caipNamespace: 'tron',
|
|
85
|
+
// Tron native currency (TRX), as Relay expects it. Not a real contract: it is
|
|
86
|
+
// the placeholder address Relay publishes for the chain's native asset.
|
|
87
|
+
nativeAssetSentinel: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb',
|
|
88
|
+
// The first wallet the interface reaches outside Reown AppKit: it reads an
|
|
89
|
+
// injected provider directly. That is more of the connection path owned
|
|
90
|
+
// here, and therefore more of it unexercised outside a mock. No funded
|
|
91
|
+
// transfer has completed.
|
|
92
|
+
endToEndProof: 'pending',
|
|
93
|
+
isValidAddress: (value) => base58Tron.test(value),
|
|
94
|
+
// base58check is case-significant; canonical form is the input unchanged.
|
|
95
|
+
normalizeAddress: (value) => value,
|
|
96
|
+
addressCasing: 'significant',
|
|
97
|
+
};
|
|
98
|
+
const tonvmDescriptor = {
|
|
99
|
+
id: 'tonvm',
|
|
100
|
+
label: 'The Open Network',
|
|
101
|
+
caipNamespace: 'ton',
|
|
102
|
+
// Relay publishes Gram — NOT the Toncoin gas asset — as this chain's native
|
|
103
|
+
// currency, so a native-asset route on The Open Network is denominated in
|
|
104
|
+
// Gram. Kept byte-equal to Relay's own value rather than "corrected".
|
|
105
|
+
nativeAssetSentinel: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c',
|
|
106
|
+
// The second wallet outside Reown AppKit, on a separate connection library,
|
|
107
|
+
// and the one origin whose deposit identifier is derived from the returned
|
|
108
|
+
// cell rather than handed back by the wallet. Only a mock has ever returned
|
|
109
|
+
// that cell. No funded transfer has completed.
|
|
110
|
+
endToEndProof: 'pending',
|
|
111
|
+
isValidAddress: (value) => friendlyTon.test(value) || rawTon.test(value),
|
|
112
|
+
// both encodings are case-significant; canonical form is the input unchanged.
|
|
113
|
+
normalizeAddress: (value) => value,
|
|
114
|
+
addressCasing: 'significant',
|
|
115
|
+
};
|
|
116
|
+
const hypevmDescriptor = {
|
|
117
|
+
id: 'hypevm',
|
|
118
|
+
label: 'Hyperliquid',
|
|
119
|
+
caipNamespace: 'hyperliquid',
|
|
120
|
+
// Relay publishes USD Coin — NOT the Hype gas asset — as this chain's native
|
|
121
|
+
// currency, and addresses it by a sixteen-byte HyperCore token identifier
|
|
122
|
+
// rather than the twenty-byte all-zero address every Ethereum Virtual Machine
|
|
123
|
+
// chain uses. The two sentinels are therefore different strings.
|
|
124
|
+
nativeAssetSentinel: '0x00000000000000000000000000000000',
|
|
125
|
+
// THE WORST OF THE SIX, and the one that was actively misleading. The
|
|
126
|
+
// interface's source picker never offers Hyperliquid — nothing can quote it
|
|
127
|
+
// — yet the landing page drew a pill for it that set the source chain to
|
|
128
|
+
// Hyperliquid on click. A reader who pressed it arrived at a chain the form
|
|
129
|
+
// does not list and no provider answers. Removing the pill removes that dead
|
|
130
|
+
// end; the descriptor stays because the route layer still has to be able to
|
|
131
|
+
// NAME the chain.
|
|
132
|
+
endToEndProof: 'pending',
|
|
133
|
+
// This predicate checks TOKEN addresses, and HyperCore identifies a token by a
|
|
134
|
+
// hex string SHORTER than an Ethereum address — sixteen bytes for most,
|
|
135
|
+
// nineteen for a few — so the twenty-byte `isAddress` check would reject the
|
|
136
|
+
// very identifiers it exists to accept. Its only consumer silently falls back
|
|
137
|
+
// to the native sentinel when this returns false, so an over-strict pattern
|
|
138
|
+
// would quietly swallow valid token links instead of surfacing an error.
|
|
139
|
+
// Accounts are a different namespace (ordinary twenty-byte Ethereum
|
|
140
|
+
// addresses) and are validated by the wallet layer, not here.
|
|
141
|
+
isValidAddress: (value) => hyperliquidTokenId.test(value),
|
|
142
|
+
// Hex is case-insensitive and Relay emits lower case, so lower case is the
|
|
143
|
+
// canonical form. It matters: the token-equality check compares non-Ethereum
|
|
144
|
+
// addresses exactly, and only a single canonical case makes that correct.
|
|
145
|
+
normalizeAddress: (value) => hyperliquidTokenId.test(value) ? value.toLowerCase() : value,
|
|
146
|
+
addressCasing: 'significant',
|
|
147
|
+
};
|
|
148
|
+
const zcashDescriptor = {
|
|
149
|
+
id: 'zcash',
|
|
150
|
+
label: 'Zcash',
|
|
151
|
+
// Zcash is not a Relay chain and has no widely-registered chain-agnostic
|
|
152
|
+
// namespace. `zcash` is this repository's own key, and nothing maps a
|
|
153
|
+
// connected wallet through it, because no wallet connects to this family.
|
|
154
|
+
caipNamespace: 'zcash',
|
|
155
|
+
// DERIVED, NOT INVENTED: the transparent pay-to-public-key-hash address of
|
|
156
|
+
// an all-zero key hash, base58check over the mainnet prefix `1cb8` followed
|
|
157
|
+
// by twenty zero bytes. Nobody holds a key for it. It follows the pattern of
|
|
158
|
+
// Relay's Bitcoin sentinel, an all-zero witness program, and for the same
|
|
159
|
+
// reason: the sentinel stands where a token address is expected, so it must
|
|
160
|
+
// pass this family's own address check. Zcash carries no tokens on this
|
|
161
|
+
// route, so the native asset is the only asset.
|
|
162
|
+
nativeAssetSentinel: 't1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs',
|
|
163
|
+
// THE FIRST ECOSYSTEM WITH NO WALLET AT ALL, and therefore the first whose
|
|
164
|
+
// hold cannot be cleared by connecting one. The reader pays an address by
|
|
165
|
+
// hand from wherever they hold the coin, so every part of the journey that
|
|
166
|
+
// this repository owns — the quote, the address, the amount, the deadline,
|
|
167
|
+
// the status poll — is exercised only by a fixture. Nothing funded has
|
|
168
|
+
// crossed. Clearing this is one word, after one transfer.
|
|
169
|
+
endToEndProof: 'pending',
|
|
170
|
+
isValidAddress: (value) => transparentZcash.test(value),
|
|
171
|
+
// base58check is case-significant; canonical form is the input unchanged.
|
|
172
|
+
normalizeAddress: (value) => value,
|
|
173
|
+
addressCasing: 'significant',
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Every ecosystem the bridge can originate from. LI.FI-compatible one first.
|
|
177
|
+
*
|
|
178
|
+
* Registration means the library can MODEL and QUOTE the ecosystem, not that an
|
|
179
|
+
* interface can sign it — see the note on {@link Ecosystem}.
|
|
180
|
+
*
|
|
181
|
+
* It means less than that again where a public claim is concerned: read
|
|
182
|
+
* {@link EndToEndProof} before listing this registry anywhere a reader will
|
|
183
|
+
* take it as an offer.
|
|
184
|
+
*/
|
|
185
|
+
export const ecosystemRegistry = [
|
|
186
|
+
evmDescriptor,
|
|
187
|
+
svmDescriptor,
|
|
188
|
+
bvmDescriptor,
|
|
189
|
+
tvmDescriptor,
|
|
190
|
+
tonvmDescriptor,
|
|
191
|
+
hypevmDescriptor,
|
|
192
|
+
zcashDescriptor,
|
|
193
|
+
];
|
|
194
|
+
const byId = new Map(ecosystemRegistry.map((descriptor) => [descriptor.id, descriptor]));
|
|
195
|
+
/**
|
|
196
|
+
* Looks up the descriptor for an ecosystem id. Total over the `Ecosystem` union;
|
|
197
|
+
* throws only if the registry is ever left incomplete (a programming error the
|
|
198
|
+
* round-trip test would catch).
|
|
199
|
+
*
|
|
200
|
+
* @param id - the ecosystem key
|
|
201
|
+
* @returns the descriptor whose `id` equals `id`
|
|
202
|
+
*/
|
|
203
|
+
export const ecosystemById = (id) => {
|
|
204
|
+
const descriptor = byId.get(id);
|
|
205
|
+
if (descriptor === undefined) {
|
|
206
|
+
throw new Error(`no ecosystem descriptor registered for id "${id}"`);
|
|
207
|
+
}
|
|
208
|
+
return descriptor;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Whether a transfer has been carried through this ecosystem end to end.
|
|
212
|
+
*
|
|
213
|
+
* The predicate a surface calls before ADVERTISING an origin. It is separate
|
|
214
|
+
* from anything the router asks, and deliberately so: the router must keep
|
|
215
|
+
* quoting and naming every registered ecosystem, because a reader who reaches
|
|
216
|
+
* an unproven chain through a link still needs the page to work. This governs
|
|
217
|
+
* only what the product offers unprompted.
|
|
218
|
+
*
|
|
219
|
+
* @param id - the ecosystem key
|
|
220
|
+
* @returns true when a real transfer has completed through it
|
|
221
|
+
*/
|
|
222
|
+
export const isEcosystemProvenEndToEnd = (id) => ecosystemById(id).endToEndProof === 'verified';
|
|
223
|
+
const byNamespace = new Map(ecosystemRegistry.map((descriptor) => [descriptor.caipNamespace, descriptor]));
|
|
224
|
+
/**
|
|
225
|
+
* Resolves an ecosystem from a chain-agnostic identifier namespace.
|
|
226
|
+
*
|
|
227
|
+
* @param namespace - the leading segment of a "namespace:reference:address" id
|
|
228
|
+
* @returns the matching descriptor, or undefined when the namespace is unknown
|
|
229
|
+
*/
|
|
230
|
+
export const ecosystemByCaipNamespace = (namespace) => byNamespace.get(namespace);
|
|
231
|
+
/**
|
|
232
|
+
* Whether two addresses in the same ecosystem name the same thing.
|
|
233
|
+
*
|
|
234
|
+
* THE ONE PLACE THIS QUESTION IS ANSWERED. It was answered in two: this
|
|
235
|
+
* registry, which knows each family's address rules, and a ternary in the
|
|
236
|
+
* interface's routing module that hard-coded "Ethereum is case-insensitive,
|
|
237
|
+
* everything else is not". Two homes means a family added here is still
|
|
238
|
+
* compared wrongly there, silently, because a loose comparison does not fail —
|
|
239
|
+
* it merges two different accounts into one.
|
|
240
|
+
*
|
|
241
|
+
* @param a - an address
|
|
242
|
+
* @param b - an address, in the same ecosystem
|
|
243
|
+
* @param ecosystem - the ecosystem both belong to
|
|
244
|
+
* @returns whether they are the same address
|
|
245
|
+
*/
|
|
246
|
+
export const sameAddress = (a, b, ecosystem) => ecosystemById(ecosystem).addressCasing === 'insensitive'
|
|
247
|
+
? a.toLowerCase() === b.toLowerCase()
|
|
248
|
+
: a === b;
|
|
249
|
+
/**
|
|
250
|
+
* Whether an address is the ecosystem's own native asset rather than a token.
|
|
251
|
+
*
|
|
252
|
+
* Every ecosystem already declares its `nativeAssetSentinel`, and this is the
|
|
253
|
+
* predicate that reads it. The comparison goes through {@link sameAddress}
|
|
254
|
+
* rather than `toLowerCase()`, which matters for exactly the families where the
|
|
255
|
+
* hand-written checks were wrong to begin with: Solana's, Bitcoin's and The
|
|
256
|
+
* Open Network's sentinels are case-significant, so lower-casing them before
|
|
257
|
+
* comparing can match an address that is not the sentinel.
|
|
258
|
+
*
|
|
259
|
+
* @param value - the address to test
|
|
260
|
+
* @param ecosystem - the ecosystem it belongs to
|
|
261
|
+
* @returns whether it names the native asset
|
|
262
|
+
*/
|
|
263
|
+
export const isNativeAsset = (value, ecosystem) => sameAddress(value, ecosystemById(ecosystem).nativeAssetSentinel, ecosystem);
|
|
264
|
+
/**
|
|
265
|
+
* Whether a string is a well-formed address in this ecosystem.
|
|
266
|
+
*
|
|
267
|
+
* A free function over the registry, so a caller needs the ecosystem key and
|
|
268
|
+
* nothing else. Reaching for viem's `isAddress` directly is the thing this
|
|
269
|
+
* replaces: it answers only for Ethereum and answers FALSE — confidently — for
|
|
270
|
+
* every valid Solana, Bitcoin, Tron and Open Network address.
|
|
271
|
+
*
|
|
272
|
+
* @param value - the string to test
|
|
273
|
+
* @param ecosystem - the ecosystem to test it against
|
|
274
|
+
* @returns whether the string is a valid address there
|
|
275
|
+
*/
|
|
276
|
+
export const isValidAddressIn = (value, ecosystem) => ecosystemById(ecosystem).isValidAddress(value);
|
|
277
|
+
/**
|
|
278
|
+
* The ecosystem's canonical spelling of an address.
|
|
279
|
+
*
|
|
280
|
+
* Total and side-effect-free: an input this ecosystem cannot parse comes back
|
|
281
|
+
* unchanged rather than throwing, because normalizing is something callers do
|
|
282
|
+
* on the way to a comparison and a throw there would be a crash on a typo.
|
|
283
|
+
*
|
|
284
|
+
* @param value - the address to normalize
|
|
285
|
+
* @param ecosystem - the ecosystem it belongs to
|
|
286
|
+
* @returns the canonical form, or the input when it cannot be parsed
|
|
287
|
+
*/
|
|
288
|
+
export const normalizeAddressIn = (value, ecosystem) => ecosystemById(ecosystem).normalizeAddress(value);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The type of fee to use for the bridge */
|
|
2
|
+
export declare enum FeeType {
|
|
3
|
+
PERCENT = "%",
|
|
4
|
+
GAS_TIP = "gas+%",
|
|
5
|
+
FIXED = "fixed"
|
|
6
|
+
}
|
|
7
|
+
/** The keys of the fee type */
|
|
8
|
+
export type FeeTypeKeys = keyof typeof FeeType;
|
|
9
|
+
/** A map of fee type values to keys */
|
|
10
|
+
export declare const feeTypeValToKeyMap: Map<FeeType, "PERCENT" | "GAS_TIP" | "FIXED">;
|
package/dist/fee-type.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The type of fee to use for the bridge */
|
|
2
|
+
export var FeeType;
|
|
3
|
+
(function (FeeType) {
|
|
4
|
+
FeeType["PERCENT"] = "%";
|
|
5
|
+
FeeType["GAS_TIP"] = "gas+%";
|
|
6
|
+
FeeType["FIXED"] = "fixed";
|
|
7
|
+
})(FeeType || (FeeType = {}));
|
|
8
|
+
/** A map of fee type values to keys */
|
|
9
|
+
export const feeTypeValToKeyMap = new Map(Object.keys(FeeType).map((key) => [FeeType[key], key]));
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { type Hex } from 'viem';
|
|
2
|
+
import type { HyperliquidAuthorization, HyperliquidDeposit, TypedDataField } from './origin-deposit.js';
|
|
3
|
+
/**
|
|
4
|
+
* Executing a Hyperliquid deposit, as pure data.
|
|
5
|
+
*
|
|
6
|
+
* Hyperliquid is the one origin whose deposit is never broadcast to a chain.
|
|
7
|
+
* The user signs a typed-data ACTION and that signed action is posted to
|
|
8
|
+
* Hyperliquid's own exchange API; Relay then observes the transfer and fills the
|
|
9
|
+
* route. Two details make this impossible to derive from the quote alone, and
|
|
10
|
+
* both are encoded here rather than rediscovered at a call site:
|
|
11
|
+
*
|
|
12
|
+
* 1. Relay's quote supplies `eip712Types`, `eip712PrimaryType` and the action,
|
|
13
|
+
* but NOT the signing domain. The domain is fixed by Hyperliquid and is
|
|
14
|
+
* reconstructed by {@link buildHyperliquidTypedData}.
|
|
15
|
+
* 2. Hyperliquid wants the signature split into `r`/`s`/`v` components, not the
|
|
16
|
+
* concatenated hex string a wallet returns.
|
|
17
|
+
*
|
|
18
|
+
* Kept free of transport so it is fully testable: nothing here signs, fetches,
|
|
19
|
+
* or reads a clock. The caller supplies the signature and performs the request.
|
|
20
|
+
*
|
|
21
|
+
* The shape mirrors Relay's own client, which is the only reference
|
|
22
|
+
* implementation of this exchange — their public documentation covers the
|
|
23
|
+
* generic `signature` step but not this action-bearing `transaction` step.
|
|
24
|
+
*/
|
|
25
|
+
/** Where a signed Hyperliquid action is submitted. Hyperliquid's API, not Relay's. */
|
|
26
|
+
export declare const hyperliquidExchangeUrl = "https://api.hyperliquid.xyz/exchange";
|
|
27
|
+
/** The signing domain Hyperliquid requires. Fixed; never supplied by the quote. */
|
|
28
|
+
export declare const hyperliquidDomainName = "HyperliquidSignTransaction";
|
|
29
|
+
/** The signing domain's version. Fixed alongside {@link hyperliquidDomainName}. */
|
|
30
|
+
export declare const hyperliquidDomainVersion = "1";
|
|
31
|
+
/**
|
|
32
|
+
* The domain's `verifyingContract`. Hyperliquid actions are verified off-chain,
|
|
33
|
+
* so no contract is involved and the field is the all-zero address.
|
|
34
|
+
*/
|
|
35
|
+
export declare const hyperliquidVerifyingContract: "0x0000000000000000000000000000000000000000";
|
|
36
|
+
/** A complete typed-data payload, ready to hand to a wallet's typed-data signing call. */
|
|
37
|
+
export type HyperliquidTypedData = {
|
|
38
|
+
domain: {
|
|
39
|
+
name: string;
|
|
40
|
+
version: string;
|
|
41
|
+
chainId: number;
|
|
42
|
+
verifyingContract: Hex;
|
|
43
|
+
};
|
|
44
|
+
types: Record<string, TypedDataField[]>;
|
|
45
|
+
primaryType: string;
|
|
46
|
+
/**
|
|
47
|
+
* The signed message. This exact object is ALSO the `action` submitted to the
|
|
48
|
+
* exchange, so the bytes signed and the bytes submitted cannot drift apart.
|
|
49
|
+
*/
|
|
50
|
+
message: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Builds the typed-data payload for a Hyperliquid deposit.
|
|
54
|
+
*
|
|
55
|
+
* The message is the action's parameters plus the action `type` and a
|
|
56
|
+
* `signatureChainId`; the latter is the signing chain in hex and MUST agree
|
|
57
|
+
* with `domain.chainId`, because Hyperliquid recovers the signer using both.
|
|
58
|
+
* They are derived from the single `signatureChainId` argument so they cannot
|
|
59
|
+
* disagree.
|
|
60
|
+
*
|
|
61
|
+
* @param deposit - the parsed Hyperliquid origin deposit
|
|
62
|
+
* @param options.signatureChainId - the chain id the connected wallet signs
|
|
63
|
+
* under. Not the Relay origin chain id: it identifies the signing domain, and
|
|
64
|
+
* Relay's own client uses the wallet's active chain here.
|
|
65
|
+
* @returns the payload to sign, whose `message` doubles as the submitted action
|
|
66
|
+
*/
|
|
67
|
+
export declare const buildHyperliquidTypedData: (deposit: HyperliquidDeposit, { signatureChainId }: {
|
|
68
|
+
signatureChainId: number;
|
|
69
|
+
}) => HyperliquidTypedData;
|
|
70
|
+
/** The JSON body Hyperliquid's exchange endpoint expects for a signed action. */
|
|
71
|
+
export type HyperliquidExchangeRequest = {
|
|
72
|
+
/** The signature split into components; the concatenated hex form is rejected. */
|
|
73
|
+
signature: {
|
|
74
|
+
r: Hex;
|
|
75
|
+
s: Hex;
|
|
76
|
+
v: number;
|
|
77
|
+
};
|
|
78
|
+
/** Replay-protection nonce, which must match the nonce inside the signed action. */
|
|
79
|
+
nonce: number;
|
|
80
|
+
/** The signed message, submitted verbatim. */
|
|
81
|
+
action: Record<string, unknown>;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Builds the exchange request for an action the user has signed.
|
|
85
|
+
*
|
|
86
|
+
* The nonce is read back out of the signed message rather than passed in: it is
|
|
87
|
+
* covered by the signature, so any independently supplied value could disagree
|
|
88
|
+
* with what was signed and be rejected.
|
|
89
|
+
*
|
|
90
|
+
* @param options.typedData - the payload that was signed
|
|
91
|
+
* @param options.signature - the wallet's signature over it
|
|
92
|
+
* @returns the request body, or null when the signed action carries no usable nonce
|
|
93
|
+
*/
|
|
94
|
+
export declare const buildHyperliquidExchangeRequest: ({ typedData, signature, }: {
|
|
95
|
+
typedData: HyperliquidTypedData;
|
|
96
|
+
signature: Hex;
|
|
97
|
+
}) => HyperliquidExchangeRequest | null;
|
|
98
|
+
/**
|
|
99
|
+
* Whether an exchange response reports success. Hyperliquid answers with HTTP
|
|
100
|
+
* 200 even for a rejected action, so the body's own status is the only reliable
|
|
101
|
+
* signal — treating the status code as the outcome would report a failed
|
|
102
|
+
* transfer as a completed one.
|
|
103
|
+
*
|
|
104
|
+
* @param payload - the parsed response body
|
|
105
|
+
* @returns true only when the body explicitly reports success
|
|
106
|
+
*/
|
|
107
|
+
export declare const isHyperliquidExchangeSuccess: (payload: unknown) => boolean;
|
|
108
|
+
/**
|
|
109
|
+
* The typed-data payload for the authorization that must be completed BEFORE
|
|
110
|
+
* the action is submitted.
|
|
111
|
+
*
|
|
112
|
+
* Relay builds the authorization against a chain it picks when quoting, but the
|
|
113
|
+
* signature is produced by whichever chain the wallet is actually on. Both the
|
|
114
|
+
* domain here and `signatureChainId` in {@link buildHyperliquidAuthorizationRequest}
|
|
115
|
+
* are therefore overridden with the active wallet chain; leaving Relay's value in
|
|
116
|
+
* place makes the recovered signer disagree with the submitted one and the
|
|
117
|
+
* authorization is rejected.
|
|
118
|
+
*
|
|
119
|
+
* @param authorization - the authorization carried by the deposit
|
|
120
|
+
* @param options.signatureChainId - the chain id the connected wallet signs under
|
|
121
|
+
* @returns the payload to sign
|
|
122
|
+
*/
|
|
123
|
+
export declare const buildHyperliquidAuthorizationTypedData: (authorization: HyperliquidAuthorization, { signatureChainId }: {
|
|
124
|
+
signatureChainId: number;
|
|
125
|
+
}) => HyperliquidTypedData;
|
|
126
|
+
/** A prepared authorization submission: where to send it, and what to send. */
|
|
127
|
+
export type HyperliquidAuthorizationRequest = {
|
|
128
|
+
/**
|
|
129
|
+
* The absolute URL, INCLUDING the signature as a query parameter. Relay reads
|
|
130
|
+
* the signature from the query string, not the body — a request carrying it in
|
|
131
|
+
* the body is accepted and then fails to authorize.
|
|
132
|
+
*/
|
|
133
|
+
url: string;
|
|
134
|
+
method: string;
|
|
135
|
+
body: Record<string, unknown>;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Builds the authorization submission for a signed authorization.
|
|
139
|
+
*
|
|
140
|
+
* @param options.authorization - the authorization that was signed
|
|
141
|
+
* @param options.signature - the wallet's signature over its typed data
|
|
142
|
+
* @param options.signatureChainId - the chain id the wallet signed under, which
|
|
143
|
+
* replaces the value Relay quoted so the body agrees with the signature
|
|
144
|
+
* @param options.baseUrl - Relay's API root; the step's endpoint is relative
|
|
145
|
+
* @returns the request to perform, or null when the endpoint is not a usable path
|
|
146
|
+
*/
|
|
147
|
+
export declare const buildHyperliquidAuthorizationRequest: ({ authorization, signature, signatureChainId, baseUrl, }: {
|
|
148
|
+
authorization: HyperliquidAuthorization;
|
|
149
|
+
signature: Hex;
|
|
150
|
+
signatureChainId: number;
|
|
151
|
+
baseUrl?: string;
|
|
152
|
+
}) => HyperliquidAuthorizationRequest | null;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { parseSignature } from 'viem';
|
|
2
|
+
import { relayApiBaseUrl } from './relay-quote.js';
|
|
3
|
+
/**
|
|
4
|
+
* Executing a Hyperliquid deposit, as pure data.
|
|
5
|
+
*
|
|
6
|
+
* Hyperliquid is the one origin whose deposit is never broadcast to a chain.
|
|
7
|
+
* The user signs a typed-data ACTION and that signed action is posted to
|
|
8
|
+
* Hyperliquid's own exchange API; Relay then observes the transfer and fills the
|
|
9
|
+
* route. Two details make this impossible to derive from the quote alone, and
|
|
10
|
+
* both are encoded here rather than rediscovered at a call site:
|
|
11
|
+
*
|
|
12
|
+
* 1. Relay's quote supplies `eip712Types`, `eip712PrimaryType` and the action,
|
|
13
|
+
* but NOT the signing domain. The domain is fixed by Hyperliquid and is
|
|
14
|
+
* reconstructed by {@link buildHyperliquidTypedData}.
|
|
15
|
+
* 2. Hyperliquid wants the signature split into `r`/`s`/`v` components, not the
|
|
16
|
+
* concatenated hex string a wallet returns.
|
|
17
|
+
*
|
|
18
|
+
* Kept free of transport so it is fully testable: nothing here signs, fetches,
|
|
19
|
+
* or reads a clock. The caller supplies the signature and performs the request.
|
|
20
|
+
*
|
|
21
|
+
* The shape mirrors Relay's own client, which is the only reference
|
|
22
|
+
* implementation of this exchange — their public documentation covers the
|
|
23
|
+
* generic `signature` step but not this action-bearing `transaction` step.
|
|
24
|
+
*/
|
|
25
|
+
/** Where a signed Hyperliquid action is submitted. Hyperliquid's API, not Relay's. */
|
|
26
|
+
export const hyperliquidExchangeUrl = 'https://api.hyperliquid.xyz/exchange';
|
|
27
|
+
/** The signing domain Hyperliquid requires. Fixed; never supplied by the quote. */
|
|
28
|
+
export const hyperliquidDomainName = 'HyperliquidSignTransaction';
|
|
29
|
+
/** The signing domain's version. Fixed alongside {@link hyperliquidDomainName}. */
|
|
30
|
+
export const hyperliquidDomainVersion = '1';
|
|
31
|
+
/**
|
|
32
|
+
* The domain's `verifyingContract`. Hyperliquid actions are verified off-chain,
|
|
33
|
+
* so no contract is involved and the field is the all-zero address.
|
|
34
|
+
*/
|
|
35
|
+
export const hyperliquidVerifyingContract = '0x0000000000000000000000000000000000000000';
|
|
36
|
+
/** The standard typed-data domain type, which the action's type table must include. */
|
|
37
|
+
const eip712DomainType = [
|
|
38
|
+
{ name: 'name', type: 'string' },
|
|
39
|
+
{ name: 'version', type: 'string' },
|
|
40
|
+
{ name: 'chainId', type: 'uint256' },
|
|
41
|
+
{ name: 'verifyingContract', type: 'address' },
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Builds the typed-data payload for a Hyperliquid deposit.
|
|
45
|
+
*
|
|
46
|
+
* The message is the action's parameters plus the action `type` and a
|
|
47
|
+
* `signatureChainId`; the latter is the signing chain in hex and MUST agree
|
|
48
|
+
* with `domain.chainId`, because Hyperliquid recovers the signer using both.
|
|
49
|
+
* They are derived from the single `signatureChainId` argument so they cannot
|
|
50
|
+
* disagree.
|
|
51
|
+
*
|
|
52
|
+
* @param deposit - the parsed Hyperliquid origin deposit
|
|
53
|
+
* @param options.signatureChainId - the chain id the connected wallet signs
|
|
54
|
+
* under. Not the Relay origin chain id: it identifies the signing domain, and
|
|
55
|
+
* Relay's own client uses the wallet's active chain here.
|
|
56
|
+
* @returns the payload to sign, whose `message` doubles as the submitted action
|
|
57
|
+
*/
|
|
58
|
+
export const buildHyperliquidTypedData = (deposit, { signatureChainId }) => ({
|
|
59
|
+
domain: {
|
|
60
|
+
name: hyperliquidDomainName,
|
|
61
|
+
version: hyperliquidDomainVersion,
|
|
62
|
+
chainId: signatureChainId,
|
|
63
|
+
verifyingContract: hyperliquidVerifyingContract,
|
|
64
|
+
},
|
|
65
|
+
types: {
|
|
66
|
+
...deposit.eip712Types,
|
|
67
|
+
EIP712Domain: eip712DomainType,
|
|
68
|
+
},
|
|
69
|
+
primaryType: deposit.eip712PrimaryType,
|
|
70
|
+
message: {
|
|
71
|
+
...deposit.action.parameters,
|
|
72
|
+
type: deposit.action.type,
|
|
73
|
+
signatureChainId: `0x${signatureChainId.toString(16)}`,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Builds the exchange request for an action the user has signed.
|
|
78
|
+
*
|
|
79
|
+
* The nonce is read back out of the signed message rather than passed in: it is
|
|
80
|
+
* covered by the signature, so any independently supplied value could disagree
|
|
81
|
+
* with what was signed and be rejected.
|
|
82
|
+
*
|
|
83
|
+
* @param options.typedData - the payload that was signed
|
|
84
|
+
* @param options.signature - the wallet's signature over it
|
|
85
|
+
* @returns the request body, or null when the signed action carries no usable nonce
|
|
86
|
+
*/
|
|
87
|
+
export const buildHyperliquidExchangeRequest = ({ typedData, signature, }) => {
|
|
88
|
+
// Hyperliquid names it `nonce`; the older action shapes name it `time`.
|
|
89
|
+
const rawNonce = typedData.message.nonce ?? typedData.message.time;
|
|
90
|
+
if (typeof rawNonce !== 'number' && typeof rawNonce !== 'bigint') {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const { r, s, v, yParity } = parseSignature(signature);
|
|
94
|
+
return {
|
|
95
|
+
// The exchange wants the 27/28 form. A signature whose final byte is the
|
|
96
|
+
// PARITY form (0 or 1) — which some hardware wallets and smart-account flows
|
|
97
|
+
// produce — parses with `v` absent and the parity carried separately, so
|
|
98
|
+
// reading `v` alone and defaulting would send recovery value 0: not a value
|
|
99
|
+
// the exchange accepts, and the deposit dies for those wallets only. The
|
|
100
|
+
// parity is the same fact in the other encoding, so shift it rather than
|
|
101
|
+
// guess.
|
|
102
|
+
signature: { r, s, v: v === undefined ? yParity + 27 : Number(v) },
|
|
103
|
+
nonce: Number(rawNonce),
|
|
104
|
+
action: typedData.message,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Whether an exchange response reports success. Hyperliquid answers with HTTP
|
|
109
|
+
* 200 even for a rejected action, so the body's own status is the only reliable
|
|
110
|
+
* signal — treating the status code as the outcome would report a failed
|
|
111
|
+
* transfer as a completed one.
|
|
112
|
+
*
|
|
113
|
+
* @param payload - the parsed response body
|
|
114
|
+
* @returns true only when the body explicitly reports success
|
|
115
|
+
*/
|
|
116
|
+
export const isHyperliquidExchangeSuccess = (payload) => {
|
|
117
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return payload.status === 'ok';
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The typed-data payload for the authorization that must be completed BEFORE
|
|
124
|
+
* the action is submitted.
|
|
125
|
+
*
|
|
126
|
+
* Relay builds the authorization against a chain it picks when quoting, but the
|
|
127
|
+
* signature is produced by whichever chain the wallet is actually on. Both the
|
|
128
|
+
* domain here and `signatureChainId` in {@link buildHyperliquidAuthorizationRequest}
|
|
129
|
+
* are therefore overridden with the active wallet chain; leaving Relay's value in
|
|
130
|
+
* place makes the recovered signer disagree with the submitted one and the
|
|
131
|
+
* authorization is rejected.
|
|
132
|
+
*
|
|
133
|
+
* @param authorization - the authorization carried by the deposit
|
|
134
|
+
* @param options.signatureChainId - the chain id the connected wallet signs under
|
|
135
|
+
* @returns the payload to sign
|
|
136
|
+
*/
|
|
137
|
+
export const buildHyperliquidAuthorizationTypedData = (authorization, { signatureChainId }) => ({
|
|
138
|
+
domain: {
|
|
139
|
+
...authorization.sign.domain,
|
|
140
|
+
chainId: signatureChainId,
|
|
141
|
+
},
|
|
142
|
+
types: authorization.sign.types,
|
|
143
|
+
primaryType: authorization.sign.primaryType,
|
|
144
|
+
message: authorization.sign.value,
|
|
145
|
+
});
|
|
146
|
+
/**
|
|
147
|
+
* Builds the authorization submission for a signed authorization.
|
|
148
|
+
*
|
|
149
|
+
* @param options.authorization - the authorization that was signed
|
|
150
|
+
* @param options.signature - the wallet's signature over its typed data
|
|
151
|
+
* @param options.signatureChainId - the chain id the wallet signed under, which
|
|
152
|
+
* replaces the value Relay quoted so the body agrees with the signature
|
|
153
|
+
* @param options.baseUrl - Relay's API root; the step's endpoint is relative
|
|
154
|
+
* @returns the request to perform, or null when the endpoint is not a usable path
|
|
155
|
+
*/
|
|
156
|
+
export const buildHyperliquidAuthorizationRequest = ({ authorization, signature, signatureChainId, baseUrl = relayApiBaseUrl, }) => {
|
|
157
|
+
const { endpoint, method, body } = authorization.post;
|
|
158
|
+
if (!endpoint.startsWith('/')) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const url = new URL(`${baseUrl}${endpoint}`);
|
|
162
|
+
url.searchParams.set('signature', signature);
|
|
163
|
+
return {
|
|
164
|
+
url: url.href,
|
|
165
|
+
method,
|
|
166
|
+
body: { ...body, signatureChainId },
|
|
167
|
+
};
|
|
168
|
+
};
|