@gibs/bridge-sdk 1.19.0 → 1.20.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/dist/abis.js +0 -2
- package/dist/bridge-limits.js +0 -38
- package/dist/chain-info.js +0 -116
- package/dist/chain-names.js +0 -151
- package/dist/chains.js +0 -198
- package/dist/cjs/abis.js +0 -2
- package/dist/cjs/bridge-limits.js +0 -38
- package/dist/cjs/chain-info.js +0 -116
- package/dist/cjs/chain-names.js +0 -151
- package/dist/cjs/chains.js +0 -198
- package/dist/cjs/claims.js +0 -60
- package/dist/cjs/config.js +1 -208
- package/dist/cjs/consolidated-quote.js +0 -465
- package/dist/cjs/destination-calldata.js +0 -71
- package/dist/cjs/ecosystems.js +0 -183
- package/dist/cjs/execution.js +0 -92
- package/dist/cjs/fee-type.js +0 -1
- package/dist/cjs/hyperliquid-execution.js +0 -98
- package/dist/cjs/image-links.js +0 -108
- package/dist/cjs/lifi-execution-guard.js +65 -172
- package/dist/cjs/near-intents-conformance.js +0 -110
- package/dist/cjs/near-intents-route.js +0 -149
- package/dist/cjs/near-intents.js +0 -562
- package/dist/cjs/origin-deposit.js +0 -56
- package/dist/cjs/pre-quote.js +0 -80
- package/dist/cjs/prices.js +0 -113
- package/dist/cjs/providers.js +0 -83
- package/dist/cjs/quote-fees.js +0 -80
- package/dist/cjs/referral.js +0 -75
- package/dist/cjs/relay-currencies.js +0 -31
- package/dist/cjs/relay-depository.js +52 -77
- package/dist/cjs/relay-quote.js +0 -394
- package/dist/cjs/route-steps.js +0 -100
- package/dist/cjs/routing/amounts.js +0 -83
- package/dist/cjs/routing/candidate-route.js +0 -73
- package/dist/cjs/routing/candidate.js +0 -46
- package/dist/cjs/routing/carrier-provider-key.js +0 -27
- package/dist/cjs/routing/carriers/omnibridge.js +0 -120
- package/dist/cjs/routing/carriers/pulsex-paths.js +0 -57
- package/dist/cjs/routing/carriers/pulsex-split.js +0 -92
- package/dist/cjs/routing/carriers/pulsex.js +0 -279
- package/dist/cjs/routing/carriers/wrap.js +0 -57
- package/dist/cjs/routing/composed-route-quality.js +0 -95
- package/dist/cjs/routing/entry-chain-anchor.js +0 -27
- package/dist/cjs/routing/format.js +0 -50
- package/dist/cjs/routing/index.js +0 -28
- package/dist/cjs/routing/producers.js +0 -854
- package/dist/cjs/routing/providers.js +0 -27
- package/dist/cjs/routing/search/compose.js +0 -56
- package/dist/cjs/routing/search/graph.js +0 -364
- package/dist/cjs/routing/search/plan-route-data.js +0 -508
- package/dist/cjs/routing/search/score.js +0 -344
- package/dist/cjs/routing/swap-transactions.js +0 -74
- package/dist/cjs/routing/usd-value.js +0 -12
- package/dist/cjs/settings.js +1 -456
- package/dist/cjs/swap-venues.js +0 -17
- package/dist/cjs/ton-connect.js +0 -82
- package/dist/cjs/ton-execution.js +0 -6
- package/dist/cjs/ton.js +0 -42
- package/dist/cjs/tron-execution.js +0 -145
- package/dist/claims.js +0 -60
- package/dist/config.js +1 -211
- package/dist/consolidated-quote.js +0 -465
- package/dist/destination-calldata.js +0 -71
- package/dist/ecosystems.js +0 -183
- package/dist/execution.js +0 -92
- package/dist/fee-type.js +0 -2
- package/dist/hyperliquid-execution.js +0 -98
- package/dist/image-links.js +0 -108
- package/dist/lifi-execution-guard.js +65 -172
- package/dist/near-intents-conformance.js +0 -110
- package/dist/near-intents-route.js +0 -149
- package/dist/near-intents.js +0 -562
- package/dist/origin-deposit.js +0 -56
- package/dist/pre-quote.js +0 -80
- package/dist/prices.js +0 -113
- package/dist/providers.js +0 -83
- package/dist/quote-fees.js +0 -80
- package/dist/referral.js +0 -75
- package/dist/relay-currencies.js +0 -31
- package/dist/relay-depository.js +52 -77
- package/dist/relay-quote.js +0 -394
- package/dist/route-steps.js +0 -100
- package/dist/routing/amounts.js +0 -83
- package/dist/routing/candidate-route.js +0 -73
- package/dist/routing/candidate.js +0 -46
- package/dist/routing/carrier-provider-key.js +0 -27
- package/dist/routing/carriers/omnibridge.js +0 -120
- package/dist/routing/carriers/pulsex-paths.js +0 -57
- package/dist/routing/carriers/pulsex-split.js +0 -92
- package/dist/routing/carriers/pulsex.js +0 -279
- package/dist/routing/carriers/wrap.js +0 -57
- package/dist/routing/composed-route-quality.js +0 -95
- package/dist/routing/entry-chain-anchor.js +0 -27
- package/dist/routing/format.js +0 -50
- package/dist/routing/index.js +0 -28
- package/dist/routing/producers.js +0 -854
- package/dist/routing/providers.js +0 -27
- package/dist/routing/search/compose.js +0 -56
- package/dist/routing/search/graph.js +0 -364
- package/dist/routing/search/plan-route-data.js +0 -508
- package/dist/routing/search/score.js +0 -344
- package/dist/routing/swap-transactions.js +0 -74
- package/dist/routing/usd-value.js +0 -12
- package/dist/settings.js +1 -456
- package/dist/swap-venues.js +0 -17
- package/dist/ton-connect.js +0 -82
- package/dist/ton-execution.js +0 -6
- package/dist/ton.js +0 -42
- package/dist/tron-execution.js +0 -145
- package/package.json +3 -3
package/dist/near-intents.js
CHANGED
|
@@ -5,163 +5,17 @@ import { depositAddressChainIds, isDepositAddressChain, relayChainIds, } from '.
|
|
|
5
5
|
import { isNativeAsset, isValidAddressIn, sameAddress } from './ecosystems.js';
|
|
6
6
|
import { ecosystemByChainId } from './relay-quote.js';
|
|
7
7
|
import { resolveAttribution } from './referral.js';
|
|
8
|
-
/**
|
|
9
|
-
* NEAR Intents, through its "one click" quoting service.
|
|
10
|
-
*
|
|
11
|
-
* THIS PROVIDER CANNOT EXECUTE ON THE DESTINATION CHAIN, AND IS REGISTERED
|
|
12
|
-
* ANYWAY. Read both halves of that before changing anything here.
|
|
13
|
-
*
|
|
14
|
-
* The service validates its request body against a closed property list and
|
|
15
|
-
* answers `property destinationCall should not exist` for every name a call
|
|
16
|
-
* could travel under — `destinationCall`, `calldata`, `msg`, `txs`, `hook`,
|
|
17
|
-
* `action`, `postSwapAction`. The only thing nameable on the destination side
|
|
18
|
-
* is an address: `recipient`, with a `recipientType` of `DESTINATION_CHAIN`,
|
|
19
|
-
* `INTENTS` or `CONFIDENTIAL_INTENTS`. That is measured, not inferred from
|
|
20
|
-
* silence. `docs/near-intents-capability-survey.md` holds the evidence.
|
|
21
|
-
*
|
|
22
|
-
* DELIVERING TO THE MEDIATOR INSTEAD IS NOT A WAY AROUND IT, and the reason is
|
|
23
|
-
* structural rather than a matter of configuration. The service will happily
|
|
24
|
-
* deliver to a contract — a quote naming a well-known router as `recipient` is
|
|
25
|
-
* accepted. But every omnibridge entry point learns WHO THE READER IS from a
|
|
26
|
-
* data field: `transferAndCall` carries the recipient in its third argument,
|
|
27
|
-
* `relayTokens` in its second, `wrapAndRelayTokens` in its first. A plain
|
|
28
|
-
* token transfer has no data field. So a bare arrival cannot name a recipient,
|
|
29
|
-
* which means it can only ever credit the sender — the solver — or nobody at
|
|
30
|
-
* all. Do not attempt it.
|
|
31
|
-
*
|
|
32
|
-
* SO THE ROUTE TAKES TWO SIGNATURES, and the interface says so on the route
|
|
33
|
-
* strip rather than in a tooltip. The reader signs the origin transfer; NEAR
|
|
34
|
-
* Intents lands the anchor token in THEIR OWN WALLET on the entry chain; then
|
|
35
|
-
* they sign the ordinary bridge leg themselves. `near-intents-route.ts` builds
|
|
36
|
-
* that shape, and `executesDestinationCalls: false` on the descriptor is what
|
|
37
|
-
* keeps this provider out of the single-signature machinery, including the
|
|
38
|
-
* entry-anchor pre-quote — that phase feeds an exact-output target to the
|
|
39
|
-
* providers that DO execute, and a target only a non-executing provider can
|
|
40
|
-
* reach is a target nobody can fill.
|
|
41
|
-
*
|
|
42
|
-
* TWO SIZE FLOORS MAKE THIS A ROUTING RULE, NOT A FOOTNOTE. Binance Smart
|
|
43
|
-
* Chain — one of the two omnibridge entry chains — refuses any transfer worth
|
|
44
|
-
* one thousand United States dollars or less, in either direction, and Polygon
|
|
45
|
-
* and Tron carry floors of their own. The service imposed these on 2026-09-09
|
|
46
|
-
* and calls them temporary. Only asking reveals them, so a route has to be
|
|
47
|
-
* WITHDRAWN on a refusal rather than left to fail: see
|
|
48
|
-
* {@link NearIntentsQuoteRefusal} and `near-intents-route.ts`.
|
|
49
|
-
*/
|
|
50
|
-
/** Base address for the NEAR Intents one-click service. */
|
|
51
8
|
export const nearIntentsApiBaseUrl = 'https://1click.chaindefuser.com';
|
|
52
|
-
/** Quote endpoint. Verified live: no key required, and a key-less request is answered in full. */
|
|
53
9
|
export const nearIntentsQuoteUrl = `${nearIntentsApiBaseUrl}/v0/quote`;
|
|
54
|
-
/** The list of assets the service can carry, keyed by its own asset identifiers. */
|
|
55
10
|
export const nearIntentsTokensUrl = `${nearIntentsApiBaseUrl}/v0/tokens`;
|
|
56
|
-
/**
|
|
57
|
-
* Optional notification that the reader's deposit has been sent. The service
|
|
58
|
-
* detects the deposit on its own; telling it the transaction hash only makes
|
|
59
|
-
* the detection faster. Takes `{ txHash, depositAddress }`.
|
|
60
|
-
*/
|
|
61
11
|
export const nearIntentsDepositSubmitUrl = `${nearIntentsApiBaseUrl}/v0/deposit/submit`;
|
|
62
|
-
/**
|
|
63
|
-
* What a key-less request costs, in basis points of the input amount.
|
|
64
|
-
*
|
|
65
|
-
* NOT A FEE THIS CODE CHARGES OR CAN DECLINE. The service inserts it into the
|
|
66
|
-
* echoed request as an `appFees` entry payable to its own account whenever no
|
|
67
|
-
* key is presented, so a reader on a key-less route pays a quarter of a percent
|
|
68
|
-
* that no part of this repository asked for. It is named here so the parser can
|
|
69
|
-
* surface it as a charge rather than let it disappear into the delivered
|
|
70
|
-
* amount. A partner key reduces it; obtaining one is an owner decision.
|
|
71
|
-
*/
|
|
72
12
|
export const nearIntentsUnauthenticatedFeeBasisPoints = 25;
|
|
73
|
-
/**
|
|
74
|
-
* What a partner-key request costs instead, in basis points of the input
|
|
75
|
-
* amount, for an ordinary (non-stablecoin) pair.
|
|
76
|
-
*
|
|
77
|
-
* Read from the service's own current documentation on 2026-09-18:
|
|
78
|
-
* `docs.near-intents.org/integration/distribution-channels/1click-api/authentication`.
|
|
79
|
-
* See `docs/near-intents-partner-key.md` for the full process of obtaining a
|
|
80
|
-
* key and what else changes once one is configured.
|
|
81
|
-
*/
|
|
82
13
|
export const nearIntentsAuthenticatedFeeBasisPoints = 20;
|
|
83
|
-
/**
|
|
84
|
-
* What a partner-key request costs on a stablecoin pair or a same-asset
|
|
85
|
-
* multi-chain route, in basis points of the input amount.
|
|
86
|
-
*
|
|
87
|
-
* Read from the same page as {@link nearIntentsAuthenticatedFeeBasisPoints} on
|
|
88
|
-
* 2026-09-18. Kept as its own constant rather than a special case of the other
|
|
89
|
-
* one because the service documents it as a distinct figure for a distinct
|
|
90
|
-
* class of route, not a discount computed from the ordinary rate.
|
|
91
|
-
*/
|
|
92
14
|
export const nearIntentsAuthenticatedStablecoinFeeBasisPoints = 1;
|
|
93
|
-
/**
|
|
94
|
-
* Builds the status-poll address for one quote.
|
|
95
|
-
*
|
|
96
|
-
* The deposit address IS the lookup key here, unlike every other provider in
|
|
97
|
-
* this repository, which key by a request identifier. Each quote is issued its
|
|
98
|
-
* own deposit address, so the two are one and the same fact — see
|
|
99
|
-
* {@link parseNearIntentsQuote}, which puts the deposit address in `quoteId`
|
|
100
|
-
* for exactly this reason.
|
|
101
|
-
*
|
|
102
|
-
* @param options.quoteId - the quote's deposit address
|
|
103
|
-
* @returns the fully-formed status address
|
|
104
|
-
*/
|
|
105
15
|
export const nearIntentsStatusUrl = ({ quoteId }) => {
|
|
106
16
|
return buildUrl(`${nearIntentsApiBaseUrl}/v0/status`, { depositAddress: quoteId });
|
|
107
17
|
};
|
|
108
|
-
/**
|
|
109
|
-
* What this repository calls itself when it asks the one-click service.
|
|
110
|
-
*
|
|
111
|
-
* SENT BECAUSE THE SERVICE REFUSES SOME CALLERS BY NAME, and until now the
|
|
112
|
-
* name was whatever the runtime happened to supply. The service sits behind
|
|
113
|
-
* Cloudflare, which answers a refused caller with HTTP 403 and a body reading
|
|
114
|
-
* `error code: 1010` — a page, not a quote, so a parser sees a shape it cannot
|
|
115
|
-
* read and the whole provider looks like an outage.
|
|
116
|
-
*
|
|
117
|
-
* MEASURED against the live quote endpoint on 2026-09-21, one identical body,
|
|
118
|
-
* one header changed:
|
|
119
|
-
*
|
|
120
|
-
* | user agent | answer |
|
|
121
|
-
* | ------------------------- | ------ |
|
|
122
|
-
* | none (Node 26 default) | 201 |
|
|
123
|
-
* | `undici` | 201 |
|
|
124
|
-
* | `node` | 201 |
|
|
125
|
-
* | `curl/8.7.1` | 201 |
|
|
126
|
-
* | `python-requests/2.32` | 201 |
|
|
127
|
-
* | `Python-urllib/3.14` | 403 |
|
|
128
|
-
* | this value | 201 |
|
|
129
|
-
*
|
|
130
|
-
* So nothing is blocked today. The point is that whether we are blocked was
|
|
131
|
-
* being decided by a list we do not control, matched against a string we never
|
|
132
|
-
* chose — and a runtime upgrade could change that string without anyone here
|
|
133
|
-
* touching a line. Naming ourselves fixes the input to that decision.
|
|
134
|
-
*
|
|
135
|
-
* A BROWSER IGNORES THIS, and that is fine. `User-Agent` is a forbidden header
|
|
136
|
-
* name for `fetch`, so the browser drops it silently and sends its own. The
|
|
137
|
-
* value therefore governs Node alone: the live test harness, the deposit
|
|
138
|
-
* poller, and any script written later.
|
|
139
|
-
*/
|
|
140
18
|
export const nearIntentsUserAgent = 'gibs-bridge/1.0 (+https://gibs.finance)';
|
|
141
|
-
/**
|
|
142
|
-
* Request headers for the one-click service.
|
|
143
|
-
*
|
|
144
|
-
* THE KEY IS A JSON WEB TOKEN, NOT A PLAIN IDENTIFIER LIKE RELAY'S. The
|
|
145
|
-
* service's own documentation accepts it as `Authorization: Bearer` as well as
|
|
146
|
-
* `X-API-Key`, calls it a "JWT token" throughout, and tells an integrator to
|
|
147
|
-
* "store your JWT token securely and never commit it to version control. Use
|
|
148
|
-
* environment variables or secure secret management systems." Relay's
|
|
149
|
-
* documentation says none of that about its key. `X-API-Key` is the form sent
|
|
150
|
-
* here because the service accepts it and it matches the header name Relay
|
|
151
|
-
* already uses, so one code path serves both — but the caller of this
|
|
152
|
-
* function decides where the value comes from, and that decision is where the
|
|
153
|
-
* secrecy question has to be answered. See `packages/ui/src/lib/config.ts`
|
|
154
|
-
* and `docs/near-intents-partner-key.md`.
|
|
155
|
-
*
|
|
156
|
-
* THE DEFAULT `User-Agent` NAMES GIBS FINANCE, AND THAT COSTS A CALLER
|
|
157
|
-
* NOTHING: it carries no key and no rate-limit budget, and a browser drops it.
|
|
158
|
-
* Pass `options.userAgent` to name yourself instead, or `null` to send none and
|
|
159
|
-
* let the runtime use its own.
|
|
160
|
-
*
|
|
161
|
-
* @param apiKey - a partner key, when one is configured
|
|
162
|
-
* @param options - `userAgent`: your own value, or `null` for none (defaults to {@link nearIntentsUserAgent})
|
|
163
|
-
* @returns the headers, carrying the key only when there is one to carry
|
|
164
|
-
*/
|
|
165
19
|
export const nearIntentsRequestHeaders = (apiKey, options = {}) => {
|
|
166
20
|
const userAgent = resolveAttribution(options.userAgent, nearIntentsUserAgent);
|
|
167
21
|
return {
|
|
@@ -170,24 +24,6 @@ export const nearIntentsRequestHeaders = (apiKey, options = {}) => {
|
|
|
170
24
|
...(apiKey ? { 'X-API-Key': apiKey } : {}),
|
|
171
25
|
};
|
|
172
26
|
};
|
|
173
|
-
/**
|
|
174
|
-
* This repository's chain identifiers, keyed by the service's short chain name.
|
|
175
|
-
*
|
|
176
|
-
* The Ethereum-virtual-machine entries are read from viem rather than written
|
|
177
|
-
* down, because a chain identifier typed from memory is a hypothesis that every
|
|
178
|
-
* later reader will trust — and a wrong one here does not fail loudly, it
|
|
179
|
-
* simply quotes a different chain's token.
|
|
180
|
-
*
|
|
181
|
-
* The non-Ethereum entries are this repository's own identifiers for those
|
|
182
|
-
* families, which are Relay's numbers (see {@link relayChainIds}); the service
|
|
183
|
-
* uses short names and has no numeric identifier of its own to reconcile with.
|
|
184
|
-
*
|
|
185
|
-
* DELIBERATELY SHORTER THAN THE SERVICE'S CHAIN LIST. The service names about
|
|
186
|
-
* thirty-five chains. Only the ones this bridge can actually originate from or
|
|
187
|
-
* enter are listed, because an unlisted chain resolves to no asset and
|
|
188
|
-
* therefore to no route, which is the honest outcome, whereas a guessed
|
|
189
|
-
* identifier resolves to a route on the wrong chain.
|
|
190
|
-
*/
|
|
191
27
|
export const chainIdByNearIntentsChain = new Map([
|
|
192
28
|
['eth', mainnet.id],
|
|
193
29
|
['bsc', bsc.id],
|
|
@@ -203,10 +39,6 @@ export const chainIdByNearIntentsChain = new Map([
|
|
|
203
39
|
['tron', relayChainIds.tron],
|
|
204
40
|
['ton', relayChainIds.ton],
|
|
205
41
|
['hypercore', relayChainIds.hyperliquid],
|
|
206
|
-
// THE ONE ENTRY WITH NO WALLET BEHIND IT. Zcash is not a Relay chain and not
|
|
207
|
-
// a LI.FI chain; this repository names it itself, in `chain-names.ts`, so
|
|
208
|
-
// that a route from it can be priced and drawn. The reader pays the address
|
|
209
|
-
// this service issues, by hand, from wherever they hold the coin.
|
|
210
42
|
['zec', depositAddressChainIds.zcash],
|
|
211
43
|
]);
|
|
212
44
|
const asRecord = (value) => {
|
|
@@ -215,17 +47,6 @@ const asRecord = (value) => {
|
|
|
215
47
|
}
|
|
216
48
|
return value;
|
|
217
49
|
};
|
|
218
|
-
/**
|
|
219
|
-
* Reads `GET /v0/tokens` into the asset records the identifier lookup needs.
|
|
220
|
-
*
|
|
221
|
-
* An entry missing any field the lookup reads is dropped rather than patched
|
|
222
|
-
* with a default: an asset whose decimals were invented would price every
|
|
223
|
-
* amount wrong by orders of magnitude, and an asset whose identifier is
|
|
224
|
-
* missing cannot be quoted at all.
|
|
225
|
-
*
|
|
226
|
-
* @param payload - the raw JSON body from `GET /v0/tokens`
|
|
227
|
-
* @returns every readable asset, in the order the service listed them
|
|
228
|
-
*/
|
|
229
50
|
export const parseNearIntentsTokens = (payload) => {
|
|
230
51
|
if (!Array.isArray(payload)) {
|
|
231
52
|
return [];
|
|
@@ -255,24 +76,6 @@ export const parseNearIntentsTokens = (payload) => {
|
|
|
255
76
|
}
|
|
256
77
|
return assets;
|
|
257
78
|
};
|
|
258
|
-
/**
|
|
259
|
-
* Finds the service's identifier for one token on one chain.
|
|
260
|
-
*
|
|
261
|
-
* THE IDENTIFIER CANNOT BE CONSTRUCTED, ONLY LOOKED UP. It reads like a
|
|
262
|
-
* pattern — a chain name, the token's address, a fixed suffix — and for an
|
|
263
|
-
* Ethereum-virtual-machine token it currently is one. It is not one for the
|
|
264
|
-
* other families: Solana's USD Coin carries a hexadecimal handle that is
|
|
265
|
-
* nothing like its base58 mint. Building the string from its parts therefore
|
|
266
|
-
* produces an identifier that is well-formed and refers to nothing, so the
|
|
267
|
-
* asset list is the only source.
|
|
268
|
-
*
|
|
269
|
-
* @param options.assets - the parsed asset list
|
|
270
|
-
* @param options.chainId - this repository's identifier for the chain
|
|
271
|
-
* @param options.tokenAddress - the token's address on that chain
|
|
272
|
-
* @param options.ecosystem - the chain's virtual-machine family, which decides
|
|
273
|
-
* whether the native-coin test and the address comparison are meaningful
|
|
274
|
-
* @returns the matching asset, or null when the service does not carry it
|
|
275
|
-
*/
|
|
276
79
|
export const findNearIntentsAsset = ({ assets, chainId, tokenAddress, ecosystem, }) => {
|
|
277
80
|
const onChain = assets.filter((asset) => chainIdByNearIntentsChain.get(asset.blockchain) === chainId);
|
|
278
81
|
if (onChain.length === 0) {
|
|
@@ -281,55 +84,15 @@ export const findNearIntentsAsset = ({ assets, chainId, tokenAddress, ecosystem,
|
|
|
281
84
|
if (isNativeAsset(tokenAddress, ecosystem)) {
|
|
282
85
|
return onChain.find((asset) => asset.contractAddress === null) ?? null;
|
|
283
86
|
}
|
|
284
|
-
// Only an Ethereum-virtual-machine chain reports a token's real address here,
|
|
285
|
-
// so a match on any other family would be a coincidence of hexadecimal.
|
|
286
87
|
if (ecosystem !== 'evm') {
|
|
287
88
|
return null;
|
|
288
89
|
}
|
|
289
90
|
const match = onChain.find((asset) => asset.contractAddress !== null && sameAddress(asset.contractAddress, tokenAddress, 'evm'));
|
|
290
91
|
return match ?? null;
|
|
291
92
|
};
|
|
292
|
-
/**
|
|
293
|
-
* How far ahead the requested refund deadline is set, in milliseconds.
|
|
294
|
-
*
|
|
295
|
-
* WHAT IS SENT, NOT WHAT COMES BACK. The service does not honour this value: it
|
|
296
|
-
* returns a `deadline` of exactly three days after the one asked for, and
|
|
297
|
-
* returns `timeWhenInactive` equal to that same instant rather than to the
|
|
298
|
-
* earlier moment the documentation describes. Both are visible in the checked-in
|
|
299
|
-
* capture — asked 2026-09-17T23:49:52.805Z, answered 2026-09-20T23:49:52.805Z.
|
|
300
|
-
*
|
|
301
|
-
* So anything that needs the real clock must read `quote.deadline` off the
|
|
302
|
-
* answer. Reading `quoteRequest.deadline` back gives this number, which is the
|
|
303
|
-
* request echoed, not a commitment; and treating `deadline` and
|
|
304
|
-
* `timeWhenInactive` as two milestones reads a distinction the service is not
|
|
305
|
-
* currently making.
|
|
306
|
-
*/
|
|
307
93
|
const defaultDeadlineMilliseconds = 30 * 60 * 1000;
|
|
308
|
-
/**
|
|
309
|
-
* Slippage THIS MODULE sends when the caller states none, in basis points.
|
|
310
|
-
*
|
|
311
|
-
* Not a default the service holds. `slippageTolerance` is a required property —
|
|
312
|
-
* the service rejects a request without it — so there is no "leave it to them"
|
|
313
|
-
* option here as there is with the other providers, and one percent is a value
|
|
314
|
-
* chosen on this side.
|
|
315
|
-
*/
|
|
316
94
|
const defaultSlippageBasisPoints = 100;
|
|
317
|
-
/**
|
|
318
|
-
* Converts a slippage fraction into the basis points the service expects.
|
|
319
|
-
*
|
|
320
|
-
* @param slippage - tolerance as a fraction (0.005 means half a percent)
|
|
321
|
-
* @returns basis points (0.005 becomes 50)
|
|
322
|
-
*/
|
|
323
95
|
export const slippageToNearIntentsBasisPoints = (slippage) => Math.round(slippage * 10_000);
|
|
324
|
-
/**
|
|
325
|
-
* Reads the build inputs out of the shared provider context.
|
|
326
|
-
*
|
|
327
|
-
* Returns null when the asset list is absent, and that is not a defensive
|
|
328
|
-
* check — without the list no asset identifier can be resolved, so there is no
|
|
329
|
-
* request to form. Answering null leaves the route to the providers that can
|
|
330
|
-
* express it, which is the same thing every other builder here does when it
|
|
331
|
-
* cannot express a request.
|
|
332
|
-
*/
|
|
333
96
|
const buildOptionsFrom = (context, { dry }) => {
|
|
334
97
|
const assets = context?.nearIntentsAssets;
|
|
335
98
|
if (assets === undefined || assets.length === 0) {
|
|
@@ -337,46 +100,6 @@ const buildOptionsFrom = (context, { dry }) => {
|
|
|
337
100
|
}
|
|
338
101
|
return { assets, now: context?.now ?? new Date(), referral: context?.referral, dry };
|
|
339
102
|
};
|
|
340
|
-
/**
|
|
341
|
-
* Where a refund goes, and it is a different answer for a chain the reader
|
|
342
|
-
* cannot sign on.
|
|
343
|
-
*
|
|
344
|
-
* THE ORDINARY CASE. A refund lands back on the ORIGIN chain, at the reader's
|
|
345
|
-
* own origin-chain address — never the delivery address, which could not
|
|
346
|
-
* receive one there.
|
|
347
|
-
*
|
|
348
|
-
* THE PAY-BY-HAND CASE, WHICH IS THE WHOLE REASON THIS FLOW IS POSSIBLE. A
|
|
349
|
-
* reader with no Zcash wallet has no Zcash address to refund to, and
|
|
350
|
-
* `refundType` offers no destination-chain option: the three values are
|
|
351
|
-
* `ORIGIN_CHAIN`, `INTENTS` and `CONFIDENTIAL_INTENTS`. `INTENTS` credits the
|
|
352
|
-
* refund to a ledger row inside the settlement contract `intents.near`, and the
|
|
353
|
-
* row may be named for the reader's own Ethereum address, which the same
|
|
354
|
-
* Ethereum key controls. So the reader types nothing, and the refund fee falls
|
|
355
|
-
* from 32,000 base units to zero: crediting a balance inside a contract needs
|
|
356
|
-
* no Zcash transaction. `docs/near-implicit-account-refunds.md` checks the
|
|
357
|
-
* claim at the protocol level.
|
|
358
|
-
*
|
|
359
|
-
* TWO TRAPS, BOTH MEASURED, BOTH CAPABLE OF COSTING SOMEBODY THEIR MONEY.
|
|
360
|
-
*
|
|
361
|
-
* FIRST, LOWER CASE IS NOT COSMETIC. The account name must be lower case. The
|
|
362
|
-
* same address in its checksummed spelling is refused outright with `refundTo
|
|
363
|
-
* is not valid` — the capture is
|
|
364
|
-
* `__fixtures__/near-intents/quote-error-zcash-refund-mixed-case.json`.
|
|
365
|
-
*
|
|
366
|
-
* SECOND, ACCEPTANCE PROVES NOTHING. On the intents path the service checks
|
|
367
|
-
* only that the string looks like a NEAR account name — the literal
|
|
368
|
-
* `not-an-address-at-all` quotes happily. A wrong value therefore passes
|
|
369
|
-
* quoting and is discovered only when a swap is refunded to nobody. That is
|
|
370
|
-
* why this reads `connectedEthereumAccount`, whose contract is that it comes
|
|
371
|
-
* from the wallet connection, and why it builds nothing when that is absent
|
|
372
|
-
* or is not an Ethereum address.
|
|
373
|
-
*
|
|
374
|
-
* @param options.fromChainId - the origin chain
|
|
375
|
-
* @param options.fromAddress - the reader's address on the origin chain
|
|
376
|
-
* @param options.connectedEthereumAccount - the reader's connected Ethereum account
|
|
377
|
-
* @returns the two refund properties, or null when a pay-by-hand route has no
|
|
378
|
-
* connected Ethereum account to name
|
|
379
|
-
*/
|
|
380
103
|
const refundFields = ({ fromChainId, fromAddress, connectedEthereumAccount, }) => {
|
|
381
104
|
if (!isDepositAddressChain(fromChainId)) {
|
|
382
105
|
return { refundTo: fromAddress, refundType: 'ORIGIN_CHAIN' };
|
|
@@ -386,79 +109,19 @@ const refundFields = ({ fromChainId, fromAddress, connectedEthereumAccount, }) =
|
|
|
386
109
|
}
|
|
387
110
|
return { refundTo: connectedEthereumAccount.toLowerCase(), refundType: 'INTENTS' };
|
|
388
111
|
};
|
|
389
|
-
/**
|
|
390
|
-
* The smallest Zcash deposit that can actually be credited, in base units:
|
|
391
|
-
* 0.01 Zcash.
|
|
392
|
-
*
|
|
393
|
-
* THE LARGER OF TWO FIGURES THAT DISAGREE. The quoting service prices a Zcash
|
|
394
|
-
* input down to about two cents — the smaller floor, read live from its
|
|
395
|
-
* refusal by {@link parseNearIntentsQuoteRefusal}. But Zcash crosses on the
|
|
396
|
-
* Proof of Authority bridge, not the Omni Bridge, and that bridge publishes
|
|
397
|
-
* `min_deposit_amount: 1000000` for native Zcash. A quote between the two is
|
|
398
|
-
* priced by one surface and below the other's floor, so it takes the money and
|
|
399
|
-
* stalls — and the published stuck-deposit recovery flow is written for the
|
|
400
|
-
* Omni Bridge, so it does not cover Zcash. Measured 2026-09-21; see
|
|
401
|
-
* `docs/near-intents-capability-survey.md`.
|
|
402
|
-
*/
|
|
403
112
|
export const zcashWorkingFloorBaseUnits = 1000000n;
|
|
404
|
-
/**
|
|
405
|
-
* The working floor for each chain a reader pays by hand, in base units of
|
|
406
|
-
* that chain's native asset, one entry per member of {@link DepositAddressChain}.
|
|
407
|
-
* Typed on the union rather than on `number` so a pay-by-hand chain added to
|
|
408
|
-
* {@link depositAddressChainIds} without a floor here is a compile error.
|
|
409
|
-
*/
|
|
410
113
|
const workingFloorBaseUnitsByChain = {
|
|
411
114
|
zcash: zcashWorkingFloorBaseUnits,
|
|
412
115
|
};
|
|
413
|
-
/** {@link workingFloorBaseUnitsByChain}, keyed by chain id for lookup. */
|
|
414
116
|
const workingFloorByChainId = new Map(Object.keys(depositAddressChainIds).map((chain) => [
|
|
415
117
|
depositAddressChainIds[chain],
|
|
416
118
|
workingFloorBaseUnitsByChain[chain],
|
|
417
119
|
]));
|
|
418
|
-
/**
|
|
419
|
-
* The working floor for a pay-by-hand chain, in base units, or null when the
|
|
420
|
-
* chain has none registered.
|
|
421
|
-
*
|
|
422
|
-
* EXPORTED SO A SCREEN NEVER KEEPS ITS OWN COPY. {@link isBelowWorkingFloor} is
|
|
423
|
-
* the decision this library enforces when quoting; this is the SAME table,
|
|
424
|
-
* exposed so a payment screen can PRINT the figure it refuses under instead of
|
|
425
|
-
* hand-copying the table — a copy that would silently disagree with quoting
|
|
426
|
-
* the moment a second pay-by-hand chain gets a floor here and not there.
|
|
427
|
-
*
|
|
428
|
-
* @param chainId - the origin chain
|
|
429
|
-
* @returns the floor in base units, or null when the chain has none
|
|
430
|
-
*/
|
|
431
120
|
export const workingFloorFor = (chainId) => workingFloorByChainId.get(chainId) ?? null;
|
|
432
|
-
/**
|
|
433
|
-
* Whether an amount is below the floor a deposit on this chain can actually
|
|
434
|
-
* be credited at.
|
|
435
|
-
*
|
|
436
|
-
* ASK IT BEFORE ASKING FOR A LIVE QUOTE. The quote request builder already
|
|
437
|
-
* declines such an amount, so no deposit address is ever issued for money that
|
|
438
|
-
* would stall. This predicate lets a surface say WHY there is no route, and
|
|
439
|
-
* what size would work.
|
|
440
|
-
*
|
|
441
|
-
* @param options.chainId - the origin chain
|
|
442
|
-
* @param options.amount - the input, in base units of the origin asset
|
|
443
|
-
* @returns whether the amount is below that chain's working floor
|
|
444
|
-
*/
|
|
445
121
|
export const isBelowWorkingFloor = ({ chainId, amount, }) => {
|
|
446
122
|
const floor = workingFloorByChainId.get(chainId);
|
|
447
123
|
return floor !== undefined && amount < floor;
|
|
448
124
|
};
|
|
449
|
-
/**
|
|
450
|
-
* Builds the request body every quote and pre-quote shares.
|
|
451
|
-
*
|
|
452
|
-
* THE ORIGIN ECOSYSTEM IS DERIVED FROM THE CHAIN ID, NOT ACCEPTED AS AN
|
|
453
|
-
* ARGUMENT. It used to be read off the request's own `originVmType` while the
|
|
454
|
-
* pay-by-hand `swapType` decision two lines below read `fromChainId` through
|
|
455
|
-
* {@link isDepositAddressChain} — two signals that a caller could set to
|
|
456
|
-
* disagree, most easily by simply leaving `originVmType` unset on a
|
|
457
|
-
* deposit-address request. `near-intents.test.ts` certified exactly that
|
|
458
|
-
* contradiction as a valid Zcash request before this was fixed. Deriving both
|
|
459
|
-
* decisions from the same chain id, in the same function, makes the
|
|
460
|
-
* disagreement impossible to construct rather than merely unlikely.
|
|
461
|
-
*/
|
|
462
125
|
const buildBody = ({ fromChainId, fromTokenAddress, fromAddress, connectedEthereumAccount, toChainId, toTokenAddress, recipient, amount, swapType, slippage, options, }) => {
|
|
463
126
|
if (amount <= 0n) {
|
|
464
127
|
return null;
|
|
@@ -475,8 +138,6 @@ const buildBody = ({ fromChainId, fromTokenAddress, fromAddress, connectedEthere
|
|
|
475
138
|
if (originAsset === null) {
|
|
476
139
|
return null;
|
|
477
140
|
}
|
|
478
|
-
// The delivered side of every route this module can express lands on an
|
|
479
|
-
// entry chain, which is always an Ethereum Virtual Machine chain.
|
|
480
141
|
const destinationAsset = findNearIntentsAsset({
|
|
481
142
|
assets: options.assets,
|
|
482
143
|
chainId: toChainId,
|
|
@@ -492,8 +153,6 @@ const buildBody = ({ fromChainId, fromTokenAddress, fromAddress, connectedEthere
|
|
|
492
153
|
}
|
|
493
154
|
return {
|
|
494
155
|
dry: options.dry,
|
|
495
|
-
// A reader paying by hand gets the flexible band; a reader whose wallet
|
|
496
|
-
// composes the transfer to the exact base unit does not need one.
|
|
497
156
|
swapType: isDepositAddressChain(fromChainId) ? 'FLEX_INPUT' : swapType,
|
|
498
157
|
slippageTolerance: slippage === undefined
|
|
499
158
|
? defaultSlippageBasisPoints
|
|
@@ -509,24 +168,7 @@ const buildBody = ({ fromChainId, fromTokenAddress, fromAddress, connectedEthere
|
|
|
509
168
|
...(options.referral === undefined ? {} : { referral: options.referral }),
|
|
510
169
|
};
|
|
511
170
|
};
|
|
512
|
-
/**
|
|
513
|
-
* Builds the forward-solving origin-to-anchor pre-quote body.
|
|
514
|
-
*
|
|
515
|
-
* READ THE HEAD OF THIS MODULE BEFORE CALLING THIS FROM THE ROUTER. The
|
|
516
|
-
* entry-anchor pre-quote takes the largest delivered amount across providers
|
|
517
|
-
* and hands it to the providers that execute, so a pre-quote from a provider
|
|
518
|
-
* that cannot execute raises a target nobody can reach. This builder exists
|
|
519
|
-
* because the request shape is real and testable, not because that wiring is
|
|
520
|
-
* wanted.
|
|
521
|
-
*
|
|
522
|
-
* @param request - the provider-agnostic pre-quote request
|
|
523
|
-
* @param options - the asset list, the clock, and the referral tag
|
|
524
|
-
* @returns the request body, or null when the service carries neither asset or
|
|
525
|
-
* the amount is not positive
|
|
526
|
-
*/
|
|
527
171
|
export const buildNearIntentsPreQuoteRequest = (request, context) => {
|
|
528
|
-
// A pre-quote prices a route it will never execute, so it must not reserve a
|
|
529
|
-
// deposit address; asking dry also keeps it out of the service's records.
|
|
530
172
|
const options = buildOptionsFrom(context, { dry: true });
|
|
531
173
|
if (options === null) {
|
|
532
174
|
return null;
|
|
@@ -536,8 +178,6 @@ export const buildNearIntentsPreQuoteRequest = (request, context) => {
|
|
|
536
178
|
fromChainId: request.fromChainId,
|
|
537
179
|
fromTokenAddress: request.fromTokenAddress,
|
|
538
180
|
fromAddress: request.fromAddress,
|
|
539
|
-
// A pre-quote carries no connected account, so a pay-by-hand origin builds
|
|
540
|
-
// no pre-quote. None is wanted: this provider never bids in the pre-quote.
|
|
541
181
|
connectedEthereumAccount: undefined,
|
|
542
182
|
toChainId: request.toChainId,
|
|
543
183
|
toTokenAddress: request.toTokenAddress,
|
|
@@ -548,24 +188,6 @@ export const buildNearIntentsPreQuoteRequest = (request, context) => {
|
|
|
548
188
|
options: { ...options, dry: true },
|
|
549
189
|
});
|
|
550
190
|
};
|
|
551
|
-
/**
|
|
552
|
-
* Builds the execution quote body for a crossing with nothing to run on arrival.
|
|
553
|
-
*
|
|
554
|
-
* REFUSES ANY REQUEST CARRYING A DESTINATION CALL, and that refusal is the
|
|
555
|
-
* whole point of it. The service has no field a call can travel in, so a
|
|
556
|
-
* request built without the call would look like a valid quote for the same
|
|
557
|
-
* route and would deliver the reader's funds to an address instead of into the
|
|
558
|
-
* omnibridge — a silent, funds-affecting difference. Returning null leaves that
|
|
559
|
-
* route to the providers that execute.
|
|
560
|
-
*
|
|
561
|
-
* @param request - the provider-agnostic consolidated request
|
|
562
|
-
* @param context - carries the asset list, the clock and the referral tag
|
|
563
|
-
* @param options.dry - price only, reserving nothing. What a route ON DISPLAY
|
|
564
|
-
* must ask; a live quote reserves a deposit address for three days, so it is
|
|
565
|
-
* asked only when the reader commits. See `UnreservedDeposit`.
|
|
566
|
-
* @returns the request body, or null when the route needs a destination call,
|
|
567
|
-
* names no input amount, or the service carries neither asset
|
|
568
|
-
*/
|
|
569
191
|
export const buildNearIntentsQuoteRequest = (request, context, { dry = false } = {}) => {
|
|
570
192
|
if (request.destinationCall !== undefined) {
|
|
571
193
|
return null;
|
|
@@ -603,17 +225,6 @@ const parseAmount = (value) => {
|
|
|
603
225
|
return null;
|
|
604
226
|
}
|
|
605
227
|
};
|
|
606
|
-
/**
|
|
607
|
-
* Parses a quote response into the delivered amounts a pre-quote reports.
|
|
608
|
-
*
|
|
609
|
-
* `amountOut` is the expected delivery and `minAmountOut` the guaranteed floor
|
|
610
|
-
* after slippage, which is the same pair the other providers' pre-quote parsers
|
|
611
|
-
* produce, so the three rank on like figures.
|
|
612
|
-
*
|
|
613
|
-
* @param payload - the raw JSON body from `POST /v0/quote`
|
|
614
|
-
* @returns the delivered amounts, or null when the payload is an error or
|
|
615
|
-
* otherwise not a quote
|
|
616
|
-
*/
|
|
617
228
|
export const parseNearIntentsPreQuote = (payload) => {
|
|
618
229
|
const quote = asRecord(asRecord(payload)?.quote);
|
|
619
230
|
if (!quote) {
|
|
@@ -626,33 +237,7 @@ export const parseNearIntentsPreQuote = (payload) => {
|
|
|
626
237
|
const minimumAmount = parseAmount(quote.minAmountOut);
|
|
627
238
|
return { expectedAmount, minimumAmount: minimumAmount ?? expectedAmount };
|
|
628
239
|
};
|
|
629
|
-
/**
|
|
630
|
-
* Builds the transfer the reader signs to fund a quote.
|
|
631
|
-
*
|
|
632
|
-
* THE SERVICE RETURNS NO TRANSACTION, ONLY AN ADDRESS. Every other provider in
|
|
633
|
-
* this repository hands back a signable payload; this one hands back a deposit
|
|
634
|
-
* address and watches the chain for an ordinary transfer into it. So the origin
|
|
635
|
-
* deposit is CONSTRUCTED here from the request, not read off the response, and
|
|
636
|
-
* the response alone is not enough to produce one.
|
|
637
|
-
*
|
|
638
|
-
* There is never an approval. Nothing pulls from the reader's account — they
|
|
639
|
-
* send — so a spender allowance has nothing to authorize.
|
|
640
|
-
*
|
|
641
|
-
* @param options.depositAddress - the address the quote reserved
|
|
642
|
-
* @param options.amount - the input amount, in the origin token's base units
|
|
643
|
-
* @param options.request - the request the quote answered
|
|
644
|
-
* @returns the Ethereum deposit, or null for an origin family this cannot build
|
|
645
|
-
*/
|
|
646
240
|
const nearIntentsOriginDeposit = ({ depositAddress, amount, request, }) => {
|
|
647
|
-
// Only Ethereum-virtual-machine origins are built. The other families need a
|
|
648
|
-
// transfer composed in that family's own encoding — a Solana instruction, a
|
|
649
|
-
// Bitcoin transaction — and no signer in this repository accepts a transfer
|
|
650
|
-
// this module composed. Returning null declines the route rather than
|
|
651
|
-
// handing a wallet a payload meant for a different chain.
|
|
652
|
-
//
|
|
653
|
-
// Derived from the chain id, never from the request's own `originVmType` —
|
|
654
|
-
// see the note on {@link buildBody}, which fixes the same signal for the
|
|
655
|
-
// same reason.
|
|
656
241
|
if (ecosystemByChainId(request.fromChainId) !== 'evm') {
|
|
657
242
|
return null;
|
|
658
243
|
}
|
|
@@ -683,37 +268,6 @@ const nearIntentsOriginDeposit = ({ depositAddress, amount, request, }) => {
|
|
|
683
268
|
gasLimit: null,
|
|
684
269
|
};
|
|
685
270
|
};
|
|
686
|
-
/**
|
|
687
|
-
* Builds the deposit for a chain the reader pays by hand: an address, an
|
|
688
|
-
* amount, a tolerance and a clock, and nothing to sign.
|
|
689
|
-
*
|
|
690
|
-
* EVERY FIELD IS READ OFF THE LIVE ANSWER, and each one refuses rather than
|
|
691
|
-
* defaults, because a wrong figure here is shown to a person who then sends
|
|
692
|
-
* money by hand:
|
|
693
|
-
*
|
|
694
|
-
* - the address must be a well-formed address ON THE ORIGIN CHAIN. An answer
|
|
695
|
-
* that issued an Ethereum-shaped address for a Zcash route is not a Zcash
|
|
696
|
-
* deposit, whatever else it says.
|
|
697
|
-
* - the displayed minimum is the LARGER of two floors, never `minAmountIn`
|
|
698
|
-
* alone. `minAmountIn` is the service's flexible-input floor, priced about
|
|
699
|
-
* one percent under the quoted amount; {@link isBelowWorkingFloor} is the
|
|
700
|
-
* bridge's own floor, which does not move with the quote and can sit above
|
|
701
|
-
* `minAmountIn` for a small Zcash amount. A reader told to send the smaller
|
|
702
|
-
* figure sends a deposit the bridge will not credit, and it stalls with no
|
|
703
|
-
* published recovery — see {@link zcashWorkingFloorBaseUnits}. The combined
|
|
704
|
-
* minimum still may not exceed the quoted amount; one that did would tell
|
|
705
|
-
* the reader to send more than asked, so that is refused rather than shown.
|
|
706
|
-
* - `deadline` is the answer's own. The request's echoed deadline is ignored
|
|
707
|
-
* by the service, which substitutes one three days out — see
|
|
708
|
-
* {@link defaultDeadlineMilliseconds}.
|
|
709
|
-
*
|
|
710
|
-
* @param options.quote - the `quote` record of a live answer
|
|
711
|
-
* @param options.depositAddress - the address that answer issued
|
|
712
|
-
* @param options.amount - the quoted input, in base units of the origin asset
|
|
713
|
-
* @param options.chainId - the origin chain
|
|
714
|
-
* @param options.decimals - the origin asset's decimal places
|
|
715
|
-
* @returns the deposit, or null when any field is missing or malformed
|
|
716
|
-
*/
|
|
717
271
|
const depositAddressOriginDeposit = ({ quote, depositAddress, amount, chainId, decimals, }) => {
|
|
718
272
|
if (!isValidAddressIn(depositAddress, ecosystemByChainId(chainId))) {
|
|
719
273
|
return null;
|
|
@@ -741,16 +295,6 @@ const depositAddressOriginDeposit = ({ quote, depositAddress, amount, chainId, d
|
|
|
741
295
|
expiresAtMs,
|
|
742
296
|
};
|
|
743
297
|
};
|
|
744
|
-
/**
|
|
745
|
-
* Reads what the route charges.
|
|
746
|
-
*
|
|
747
|
-
* TWO OF THE THREE NUMBERS THE SERVICE REPORTS ARE COSTS, AND ONE IS NOT.
|
|
748
|
-
* `withdrawFee` is taken out of the delivered amount on every successful route,
|
|
749
|
-
* and the key-less surcharge in the echoed `appFees` is taken out of the input,
|
|
750
|
-
* so both are real. `refundFee` is charged ONLY when a swap is refunded — it is
|
|
751
|
-
* the price of a route that did not happen — so counting it would overstate
|
|
752
|
-
* what a completed crossing costs, and it is deliberately left out.
|
|
753
|
-
*/
|
|
754
298
|
const nearIntentsQuoteFees = ({ quote, quoteRequest, originToken, destinationToken, }) => {
|
|
755
299
|
const fees = [];
|
|
756
300
|
const withdrawFee = parseAmount(quote.withdrawFee);
|
|
@@ -761,8 +305,6 @@ const nearIntentsQuoteFees = ({ quote, quoteRequest, originToken, destinationTok
|
|
|
761
305
|
amount: withdrawFee,
|
|
762
306
|
token: destinationToken,
|
|
763
307
|
amountUsd: null,
|
|
764
|
-
// Taken out of what is delivered rather than added to what is sent, so
|
|
765
|
-
// the reader never pays it on top.
|
|
766
308
|
includedInFromAmount: true,
|
|
767
309
|
});
|
|
768
310
|
}
|
|
@@ -787,34 +329,10 @@ const nearIntentsQuoteFees = ({ quote, quoteRequest, originToken, destinationTok
|
|
|
787
329
|
}
|
|
788
330
|
return fees;
|
|
789
331
|
};
|
|
790
|
-
/**
|
|
791
|
-
* Normalizes a quote response into the shared consolidated quote shape.
|
|
792
|
-
*
|
|
793
|
-
* READS THE REQUEST AS WELL AS THE RESPONSE, unlike every other parser here,
|
|
794
|
-
* and it has to: the response names a deposit address and no transaction, so
|
|
795
|
-
* the origin chain, the origin token and the ecosystem — everything needed to
|
|
796
|
-
* build the transfer the reader signs — exist only on the request. That is why
|
|
797
|
-
* {@link ProviderQuoteContext} carries a request at all.
|
|
798
|
-
*
|
|
799
|
-
* A DRY QUOTE IS REFUSED. It carries prices and no deposit address, so it can
|
|
800
|
-
* never be executed, and a quote that cannot be executed must not be returned
|
|
801
|
-
* as one.
|
|
802
|
-
*
|
|
803
|
-
* @param payload - the raw JSON body from `POST /v0/quote`
|
|
804
|
-
* @param context - carries the request this answers and both tokens' metadata
|
|
805
|
-
* @returns the normalized quote, or null when the payload cannot produce one
|
|
806
|
-
*/
|
|
807
|
-
/**
|
|
808
|
-
* The parts of an answer every NEAR Intents quote needs, reserved or not.
|
|
809
|
-
*
|
|
810
|
-
* @returns the pieces, or null when the context or the body is unusable
|
|
811
|
-
*/
|
|
812
332
|
const quoteEssentials = (payload, context) => {
|
|
813
333
|
const request = context?.request;
|
|
814
334
|
const originToken = context?.originToken;
|
|
815
335
|
const destinationToken = context?.destinationToken;
|
|
816
|
-
// Without the request there is no chain and no token to compose a transfer
|
|
817
|
-
// from, so there is no quote to return — the same answer as a malformed body.
|
|
818
336
|
if (request === undefined || originToken === undefined || destinationToken === undefined) {
|
|
819
337
|
return null;
|
|
820
338
|
}
|
|
@@ -832,11 +350,6 @@ const quoteEssentials = (payload, context) => {
|
|
|
832
350
|
}
|
|
833
351
|
return { request, originToken, destinationToken, body, quote, fromAmount, timeEstimate: quote.timeEstimate };
|
|
834
352
|
};
|
|
835
|
-
/**
|
|
836
|
-
* Assembles the shared quote shape around a deposit.
|
|
837
|
-
*
|
|
838
|
-
* @returns the normalized quote
|
|
839
|
-
*/
|
|
840
353
|
const nearIntentsQuoteShape = ({ essentials: { body, quote, fromAmount, timeEstimate, originToken, destinationToken }, quoteId, originDeposit, }) => {
|
|
841
354
|
const toAmount = parseAmount(quote.amountOut);
|
|
842
355
|
return {
|
|
@@ -848,10 +361,8 @@ const nearIntentsQuoteShape = ({ essentials: { body, quote, fromAmount, timeEsti
|
|
|
848
361
|
...(toAmount === null ? {} : { toAmount }),
|
|
849
362
|
fromAmountUsd: typeof quote.amountInUsd === 'string' ? quote.amountInUsd : null,
|
|
850
363
|
...(typeof quote.amountOutUsd === 'string' ? { toAmountUsd: quote.amountOutUsd } : {}),
|
|
851
|
-
// Nothing pulls from the reader's account, so no spender is ever approved.
|
|
852
364
|
approvalAddress: null,
|
|
853
365
|
executionDurationSeconds: timeEstimate,
|
|
854
|
-
// The service reports no per-leg breakdown, only the two ends of the trade.
|
|
855
366
|
steps: [],
|
|
856
367
|
originDeposit,
|
|
857
368
|
fees: nearIntentsQuoteFees({
|
|
@@ -862,19 +373,6 @@ const nearIntentsQuoteShape = ({ essentials: { body, quote, fromAmount, timeEsti
|
|
|
862
373
|
}),
|
|
863
374
|
};
|
|
864
375
|
};
|
|
865
|
-
/**
|
|
866
|
-
* Normalizes a DRY answer into a quote that can be drawn and not paid.
|
|
867
|
-
*
|
|
868
|
-
* Same figures, fees and timing as a live quote; its deposit is
|
|
869
|
-
* {@link UnreservedDeposit} and its `quoteId` is empty, because a dry answer
|
|
870
|
-
* reserves no address and the address is a quote's identity. A caller that
|
|
871
|
-
* reaches the commit asks live with `buildNearIntentsQuoteRequest(…, { dry:
|
|
872
|
-
* false })` and pays that answer instead.
|
|
873
|
-
*
|
|
874
|
-
* @param payload - the raw JSON body of a `dry: true` quote
|
|
875
|
-
* @param context - the same context the live parser reads
|
|
876
|
-
* @returns the priced quote, or null when the body is not a quote
|
|
877
|
-
*/
|
|
878
376
|
export const parseNearIntentsPriceQuote = (payload, context) => {
|
|
879
377
|
const essentials = quoteEssentials(payload, context);
|
|
880
378
|
if (essentials === null) {
|
|
@@ -908,30 +406,10 @@ export const parseNearIntentsQuote = (payload, context) => {
|
|
|
908
406
|
if (originDeposit === null) {
|
|
909
407
|
return null;
|
|
910
408
|
}
|
|
911
|
-
// The deposit address is this quote's identity: the service issues a fresh
|
|
912
|
-
// one per quote and the status endpoint is keyed by it. See
|
|
913
|
-
// {@link nearIntentsStatusUrl}.
|
|
914
409
|
return nearIntentsQuoteShape({ essentials, quoteId: depositAddress, originDeposit });
|
|
915
410
|
};
|
|
916
|
-
// "Temporary swap limits: minimum swap amount is $1,000" — thousands separators
|
|
917
|
-
// and all. Captured live against Binance Smart Chain, Polygon and Tron.
|
|
918
411
|
const chainMinimumPattern = /minimum swap amount is \$([\d,]+(?:\.\d+)?)/;
|
|
919
|
-
// "Amount is too low for bridge, try at least 303824" — base units of the
|
|
920
|
-
// origin asset, quoted live and different on consecutive requests.
|
|
921
412
|
const bridgeMinimumPattern = /try at least (\d+)/;
|
|
922
|
-
/**
|
|
923
|
-
* Reads why a quote request was declined.
|
|
924
|
-
*
|
|
925
|
-
* WORTH READING RATHER THAN TREATING AS A FAILURE. A route below one of these
|
|
926
|
-
* floors is not a fault to retry — it is a route this provider will never
|
|
927
|
-
* carry at that size, so a router can drop the provider from the competition
|
|
928
|
-
* and a surface can say what size would work. Left as a bare rejection, the
|
|
929
|
-
* same route simply produces a slower answer with one fewer bidder and nothing
|
|
930
|
-
* on screen to explain it.
|
|
931
|
-
*
|
|
932
|
-
* @param payload - the raw JSON body of a declined `POST /v0/quote`
|
|
933
|
-
* @returns the refusal, or null when the body carries no message to read
|
|
934
|
-
*/
|
|
935
413
|
export const parseNearIntentsQuoteRefusal = (payload) => {
|
|
936
414
|
const message = asRecord(payload)?.message;
|
|
937
415
|
if (typeof message !== 'string') {
|
|
@@ -950,10 +428,6 @@ export const parseNearIntentsQuoteRefusal = (payload) => {
|
|
|
950
428
|
}
|
|
951
429
|
return { reason: 'other' };
|
|
952
430
|
};
|
|
953
|
-
/**
|
|
954
|
-
* The lifecycle states the status endpoint reports. `SUCCESS`, `REFUNDED` and
|
|
955
|
-
* `FAILED` are terminal; the rest are still in flight.
|
|
956
|
-
*/
|
|
957
431
|
const nearIntentsInFlightStatuses = [
|
|
958
432
|
'KNOWN_DEPOSIT_TX',
|
|
959
433
|
'PENDING_DEPOSIT',
|
|
@@ -961,15 +435,6 @@ const nearIntentsInFlightStatuses = [
|
|
|
961
435
|
'PROCESSING',
|
|
962
436
|
];
|
|
963
437
|
const isEthereumTransactionHash = (value) => typeof value === 'string' && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
964
|
-
/**
|
|
965
|
-
* Finds the destination-chain transaction hash, which is what hands a route
|
|
966
|
-
* over from provider polling to the omnibridge indexer.
|
|
967
|
-
*
|
|
968
|
-
* The hashes arrive as records carrying a `hash` and an explorer address, and
|
|
969
|
-
* the destination chain may not be an Ethereum one, so anything that is not a
|
|
970
|
-
* full thirty-two-byte hash collapses to null rather than being passed on as
|
|
971
|
-
* though it were a transaction.
|
|
972
|
-
*/
|
|
973
438
|
const destinationTransactionHash = (swapDetails) => {
|
|
974
439
|
const hashes = swapDetails?.destinationChainTxHashes;
|
|
975
440
|
if (!Array.isArray(hashes)) {
|
|
@@ -983,25 +448,6 @@ const destinationTransactionHash = (swapDetails) => {
|
|
|
983
448
|
}
|
|
984
449
|
return null;
|
|
985
450
|
};
|
|
986
|
-
/**
|
|
987
|
-
* Where a refund was credited, when it went into an account inside NEAR
|
|
988
|
-
* Intents rather than back to the origin chain.
|
|
989
|
-
*
|
|
990
|
-
* READ FROM THE PROVIDER'S OWN ECHO OF THE REQUEST. The status response carries
|
|
991
|
-
* the quote request it answered, so the refund's account, type and asset come
|
|
992
|
-
* from what the provider says it did — not from a guess on our side about
|
|
993
|
-
* which asset a chain's refund would be in. The amount is the provider's own
|
|
994
|
-
* `refundedAmount`.
|
|
995
|
-
*
|
|
996
|
-
* NULL FOR AN `ORIGIN_CHAIN` REFUND, which lands back in the reader's own
|
|
997
|
-
* origin wallet and needs nothing from them, and null whenever any field is
|
|
998
|
-
* missing: a refund step that names the wrong account or asset would send the
|
|
999
|
-
* reader looking in the wrong place.
|
|
1000
|
-
*
|
|
1001
|
-
* @param body - the whole status response
|
|
1002
|
-
* @param swapDetails - its `swapDetails` record
|
|
1003
|
-
* @returns the account refund, or null
|
|
1004
|
-
*/
|
|
1005
451
|
const intentsAccountRefund = (body, swapDetails) => {
|
|
1006
452
|
const request = asRecord(asRecord(body?.quoteResponse)?.quoteRequest);
|
|
1007
453
|
if (request?.refundType !== 'INTENTS') {
|
|
@@ -1017,12 +463,6 @@ const intentsAccountRefund = (body, swapDetails) => {
|
|
|
1017
463
|
}
|
|
1018
464
|
return { account: refundTo.toLowerCase(), assetId: originAsset, baseUnits: refundedAmount };
|
|
1019
465
|
};
|
|
1020
|
-
/**
|
|
1021
|
-
* Normalizes a `GET /v0/status` response.
|
|
1022
|
-
*
|
|
1023
|
-
* @param payload - the raw JSON body
|
|
1024
|
-
* @returns the normalized status, or null when the payload names no status
|
|
1025
|
-
*/
|
|
1026
466
|
export const parseNearIntentsStatus = (payload) => {
|
|
1027
467
|
const body = asRecord(payload);
|
|
1028
468
|
const status = body?.status;
|
|
@@ -1048,7 +488,5 @@ export const parseNearIntentsStatus = (payload) => {
|
|
|
1048
488
|
if (nearIntentsInFlightStatuses.some((inFlight) => inFlight === status)) {
|
|
1049
489
|
return { status: 'PENDING', substatus: status, receivingTxHash };
|
|
1050
490
|
}
|
|
1051
|
-
// An unrecognized state is treated as still settling rather than terminal, so
|
|
1052
|
-
// a new state the service adds never reads as a completed transfer.
|
|
1053
491
|
return { status: 'NOT_FOUND', substatus: status, receivingTxHash };
|
|
1054
492
|
};
|