@gibs/bridge-sdk 1.13.1 → 1.18.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.
Files changed (228) hide show
  1. package/README.md +295 -18
  2. package/abis/package.json +6 -0
  3. package/bridge-limits/package.json +6 -0
  4. package/chain-info/package.json +6 -0
  5. package/chain-names/package.json +6 -0
  6. package/chains/package.json +6 -0
  7. package/claims/package.json +6 -0
  8. package/config/package.json +6 -0
  9. package/consolidated-quote/package.json +6 -0
  10. package/destination-calldata/package.json +6 -0
  11. package/dist/chain-info.d.ts +1 -1
  12. package/dist/chain-info.js +11 -12
  13. package/dist/chains.d.ts +25 -0
  14. package/dist/chains.js +55 -20
  15. package/dist/cjs/abis.d.ts +1 -0
  16. package/dist/cjs/abis.js +19 -0
  17. package/dist/cjs/bridge-limits.d.ts +103 -0
  18. package/dist/cjs/bridge-limits.js +89 -0
  19. package/dist/cjs/chain-info.d.ts +169 -0
  20. package/dist/cjs/chain-info.js +452 -0
  21. package/dist/cjs/chain-names.d.ts +160 -0
  22. package/dist/cjs/chain-names.js +207 -0
  23. package/dist/cjs/chains.d.ts +90 -0
  24. package/dist/cjs/chains.js +357 -0
  25. package/dist/cjs/claims.d.ts +198 -0
  26. package/dist/cjs/claims.js +182 -0
  27. package/dist/cjs/config.d.ts +255 -0
  28. package/dist/cjs/config.js +569 -0
  29. package/dist/cjs/consolidated-quote.d.ts +648 -0
  30. package/dist/cjs/consolidated-quote.js +1048 -0
  31. package/dist/cjs/destination-calldata.d.ts +118 -0
  32. package/dist/cjs/destination-calldata.js +156 -0
  33. package/dist/cjs/ecosystems.d.ts +217 -0
  34. package/dist/cjs/ecosystems.js +298 -0
  35. package/dist/cjs/execution.d.ts +359 -0
  36. package/dist/cjs/execution.js +495 -0
  37. package/dist/cjs/fee-type.d.ts +10 -0
  38. package/dist/cjs/fee-type.js +10 -0
  39. package/dist/cjs/hyperliquid-execution.d.ts +152 -0
  40. package/dist/cjs/hyperliquid-execution.js +175 -0
  41. package/dist/cjs/image-links.d.ts +117 -0
  42. package/dist/cjs/image-links.js +153 -0
  43. package/dist/cjs/index.d.ts +24 -0
  44. package/dist/cjs/index.js +40 -0
  45. package/dist/cjs/lifi-execution-guard.d.ts +210 -0
  46. package/dist/cjs/lifi-execution-guard.js +409 -0
  47. package/dist/cjs/near-intents-conformance.d.ts +85 -0
  48. package/dist/cjs/near-intents-conformance.js +337 -0
  49. package/dist/cjs/near-intents-route.d.ts +47 -0
  50. package/dist/cjs/near-intents-route.js +303 -0
  51. package/dist/cjs/near-intents.d.ts +524 -0
  52. package/dist/cjs/near-intents.js +1071 -0
  53. package/dist/cjs/origin-deposit.d.ts +326 -0
  54. package/dist/cjs/origin-deposit.js +82 -0
  55. package/dist/cjs/package.json +3 -0
  56. package/dist/cjs/pre-quote.d.ts +198 -0
  57. package/dist/cjs/pre-quote.js +180 -0
  58. package/dist/cjs/prices.d.ts +117 -0
  59. package/dist/cjs/prices.js +224 -0
  60. package/dist/cjs/providers.d.ts +238 -0
  61. package/dist/cjs/providers.js +171 -0
  62. package/dist/cjs/quote-fees.d.ts +102 -0
  63. package/dist/cjs/quote-fees.js +201 -0
  64. package/dist/cjs/referral.d.ts +85 -0
  65. package/dist/cjs/referral.js +85 -0
  66. package/dist/cjs/relay-currencies.d.ts +47 -0
  67. package/dist/cjs/relay-currencies.js +95 -0
  68. package/dist/cjs/relay-depository.d.ts +2 -0
  69. package/dist/cjs/relay-depository.js +85 -0
  70. package/dist/cjs/relay-quote.d.ts +301 -0
  71. package/dist/cjs/relay-quote.js +996 -0
  72. package/dist/cjs/route-steps.d.ts +138 -0
  73. package/dist/cjs/route-steps.js +310 -0
  74. package/dist/cjs/routing/amounts.d.ts +80 -0
  75. package/dist/cjs/routing/amounts.js +126 -0
  76. package/dist/cjs/routing/candidate-route.d.ts +43 -0
  77. package/dist/cjs/routing/candidate-route.js +50 -0
  78. package/dist/cjs/routing/candidate.d.ts +237 -0
  79. package/dist/cjs/routing/candidate.js +100 -0
  80. package/dist/cjs/routing/carrier-provider-key.d.ts +31 -0
  81. package/dist/cjs/routing/carrier-provider-key.js +42 -0
  82. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  83. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  84. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  85. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  86. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  87. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  88. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  89. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  90. package/dist/cjs/routing/carriers/pulsex.d.ts +184 -0
  91. package/dist/cjs/routing/carriers/pulsex.js +615 -0
  92. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  93. package/dist/cjs/routing/carriers/wrap.js +119 -0
  94. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  95. package/dist/cjs/routing/composed-route-quality.js +144 -0
  96. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  97. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  98. package/dist/cjs/routing/format.d.ts +31 -0
  99. package/dist/cjs/routing/format.js +64 -0
  100. package/dist/cjs/routing/index.d.ts +42 -0
  101. package/dist/cjs/routing/index.js +142 -0
  102. package/dist/cjs/routing/producers.d.ts +770 -0
  103. package/dist/cjs/routing/producers.js +1617 -0
  104. package/dist/cjs/routing/providers.d.ts +31 -0
  105. package/dist/cjs/routing/providers.js +48 -0
  106. package/dist/cjs/routing/route-data.d.ts +397 -0
  107. package/dist/cjs/routing/route-data.js +2 -0
  108. package/dist/cjs/routing/search/compose.d.ts +168 -0
  109. package/dist/cjs/routing/search/compose.js +104 -0
  110. package/dist/cjs/routing/search/graph.d.ts +668 -0
  111. package/dist/cjs/routing/search/graph.js +461 -0
  112. package/dist/cjs/routing/search/plan-route-data.d.ts +285 -0
  113. package/dist/cjs/routing/search/plan-route-data.js +871 -0
  114. package/dist/cjs/routing/search/score.d.ts +356 -0
  115. package/dist/cjs/routing/search/score.js +445 -0
  116. package/dist/cjs/routing/swap-transactions.d.ts +95 -0
  117. package/dist/cjs/routing/swap-transactions.js +133 -0
  118. package/dist/cjs/routing/usd-value.d.ts +17 -0
  119. package/dist/cjs/routing/usd-value.js +29 -0
  120. package/dist/cjs/settings.d.ts +785 -0
  121. package/dist/cjs/settings.js +1074 -0
  122. package/dist/cjs/swap-venues.d.ts +43 -0
  123. package/dist/cjs/swap-venues.js +29 -0
  124. package/dist/cjs/ton-connect.d.ts +98 -0
  125. package/dist/cjs/ton-connect.js +206 -0
  126. package/dist/cjs/ton-execution.d.ts +7 -0
  127. package/dist/cjs/ton-execution.js +23 -0
  128. package/dist/cjs/ton.d.ts +37 -0
  129. package/dist/cjs/ton.js +83 -0
  130. package/dist/cjs/tron-execution.d.ts +146 -0
  131. package/dist/cjs/tron-execution.js +248 -0
  132. package/dist/cjs/types.d.ts +51 -0
  133. package/dist/cjs/types.js +2 -0
  134. package/dist/claims.d.ts +198 -0
  135. package/dist/claims.js +140 -0
  136. package/dist/config.d.ts +42 -3
  137. package/dist/config.js +87 -21
  138. package/dist/consolidated-quote.d.ts +109 -15
  139. package/dist/consolidated-quote.js +310 -52
  140. package/dist/execution.d.ts +359 -0
  141. package/dist/execution.js +455 -0
  142. package/dist/hyperliquid-execution.js +2 -3
  143. package/dist/index.d.ts +4 -0
  144. package/dist/index.js +4 -0
  145. package/dist/lifi-execution-guard.d.ts +13 -2
  146. package/dist/lifi-execution-guard.js +4 -0
  147. package/dist/near-intents.js +2 -3
  148. package/dist/pre-quote.d.ts +6 -6
  149. package/dist/pre-quote.js +10 -14
  150. package/dist/prices.d.ts +10 -0
  151. package/dist/prices.js +52 -1
  152. package/dist/providers.d.ts +17 -0
  153. package/dist/providers.js +6 -0
  154. package/dist/referral.d.ts +12 -4
  155. package/dist/referral.js +12 -4
  156. package/dist/relay-depository.d.ts +2 -0
  157. package/dist/relay-depository.js +82 -0
  158. package/dist/relay-quote.js +2 -3
  159. package/dist/routing/candidate.d.ts +3 -3
  160. package/dist/routing/candidate.js +3 -3
  161. package/dist/routing/carrier-provider-key.d.ts +31 -0
  162. package/dist/routing/carrier-provider-key.js +37 -0
  163. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  164. package/dist/routing/carriers/omnibridge.js +23 -21
  165. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  166. package/dist/routing/carriers/pulsex-paths.js +92 -0
  167. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  168. package/dist/routing/carriers/pulsex-split.js +185 -0
  169. package/dist/routing/carriers/pulsex.d.ts +81 -46
  170. package/dist/routing/carriers/pulsex.js +267 -148
  171. package/dist/routing/composed-route-quality.d.ts +28 -6
  172. package/dist/routing/composed-route-quality.js +36 -5
  173. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  174. package/dist/routing/entry-chain-anchor.js +1 -1
  175. package/dist/routing/index.d.ts +11 -3
  176. package/dist/routing/index.js +8 -2
  177. package/dist/routing/producers.js +1 -1
  178. package/dist/routing/providers.d.ts +5 -0
  179. package/dist/routing/providers.js +5 -0
  180. package/dist/routing/route-data.d.ts +47 -5
  181. package/dist/routing/search/graph.d.ts +15 -0
  182. package/dist/routing/search/plan-route-data.d.ts +42 -1
  183. package/dist/routing/search/plan-route-data.js +68 -9
  184. package/dist/routing/search/score.d.ts +3 -0
  185. package/dist/routing/search/score.js +11 -3
  186. package/dist/routing/swap-transactions.d.ts +95 -0
  187. package/dist/routing/swap-transactions.js +127 -0
  188. package/dist/settings.d.ts +94 -1
  189. package/dist/settings.js +95 -3
  190. package/dist/swap-venues.d.ts +43 -0
  191. package/dist/swap-venues.js +24 -0
  192. package/dist/ton-connect.d.ts +98 -0
  193. package/dist/ton-connect.js +200 -0
  194. package/dist/ton-execution.d.ts +4 -114
  195. package/dist/ton-execution.js +4 -244
  196. package/dist/ton.d.ts +37 -0
  197. package/dist/ton.js +65 -0
  198. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  199. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  200. package/docs/examples/shared.ts +261 -0
  201. package/docs/examples/tsconfig.json +18 -0
  202. package/docs/integration-guide.md +617 -0
  203. package/ecosystems/package.json +6 -0
  204. package/execution/package.json +6 -0
  205. package/fee-type/package.json +6 -0
  206. package/hyperliquid-execution/package.json +6 -0
  207. package/image-links/package.json +6 -0
  208. package/lifi-execution-guard/package.json +6 -0
  209. package/llms.txt +75 -10
  210. package/near-intents/package.json +6 -0
  211. package/near-intents-route/package.json +6 -0
  212. package/origin-deposit/package.json +6 -0
  213. package/package.json +817 -10
  214. package/pre-quote/package.json +6 -0
  215. package/prices/package.json +6 -0
  216. package/providers/package.json +6 -0
  217. package/quote-fees/package.json +6 -0
  218. package/referral/package.json +6 -0
  219. package/relay-currencies/package.json +6 -0
  220. package/relay-quote/package.json +6 -0
  221. package/route-steps/package.json +6 -0
  222. package/routing/package.json +6 -0
  223. package/settings/package.json +6 -0
  224. package/ton/package.json +6 -0
  225. package/ton-connect/package.json +6 -0
  226. package/ton-execution/package.json +6 -0
  227. package/tron-execution/package.json +6 -0
  228. package/types/package.json +6 -0
package/dist/prices.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { formatUnits } from 'viem';
2
- import { Chains, nativeAssetOut } from './config.js';
2
+ import { Chains, nativeAssetOut, toChain } from './config.js';
3
3
  /**
4
4
  * PulseChain mainnet numeric chain id (`Chains.PLS` = `0x171`). Tokens on this
5
5
  * chain (and its testnet sibling below) are priced on-chain via PulseX rather
@@ -138,6 +138,57 @@ export const computeUsdPriceFromAmountsOut = ({ results, stableDecimals = PULSEC
138
138
  * @returns the USD price as a number, or null when no source can price it
139
139
  */
140
140
  export const resolveTokenPriceUsd = (token, { pulsexPrices, lifiPrices, }) => {
141
+ const wrappedNative = sameChainWrappedNative(token);
142
+ if (wrappedNative !== null) {
143
+ return resolveNativeFamilyPriceUsd(token, wrappedNative, { pulsexPrices, lifiPrices });
144
+ }
145
+ return resolveOwnPriceUsd(token, { pulsexPrices, lifiPrices });
146
+ };
147
+ /**
148
+ * The chain's canonical wrapped native address when `token` is the native
149
+ * coin (zero address) or that wrapper itself, and null for every other token.
150
+ * Only the SAME chain's pair is returned: a bridged copy of a wrapper on
151
+ * another chain is a different token and keeps its own price.
152
+ *
153
+ * @param token - the token to test
154
+ * @returns the wrapped native address on the token's own chain, or null
155
+ */
156
+ export const sameChainWrappedNative = (token) => {
157
+ const wrapped = nativeAssetOut[toChain(token.chainId)];
158
+ if (wrapped === undefined) {
159
+ return null;
160
+ }
161
+ const isNativeCoin = sameAddress(token.address, NATIVE_TOKEN_ADDRESS);
162
+ return isNativeCoin || sameAddress(token.address, wrapped) ? wrapped : null;
163
+ };
164
+ /**
165
+ * One price for a native coin and its canonical wrapper. The contract wraps
166
+ * and unwraps 1:1, so two prices would show a value loss on a wrap step that
167
+ * loses nothing.
168
+ *
169
+ * Off PulseChain the native coin's market feed (LI.FI) wins. The wrapper's
170
+ * own price there is the PulseX price of its bridged copy (it carries a
171
+ * PulseChain origin link), which is a derivative market with its own
172
+ * premium or discount. The coin's feed prices the underlying asset. When the
173
+ * feed has no native price, the wrapper's feed price, then its own resolved
174
+ * price, stand in.
175
+ *
176
+ * On PulseChain both are priced by PulseX through the WPLS path, so the
177
+ * wrapper's entry is used and the coin's entry stands in when the wrapper was
178
+ * not in the priced set.
179
+ */
180
+ const resolveNativeFamilyPriceUsd = (token, wrappedNative, maps) => {
181
+ const wrapped = { ...token, address: wrappedNative };
182
+ if (isPulsechain(token.chainId)) {
183
+ return (resolveOwnPriceUsd(wrapped, maps) ??
184
+ resolveOwnPriceUsd({ ...token, address: NATIVE_TOKEN_ADDRESS }, maps));
185
+ }
186
+ return (maps.lifiPrices.get(lifiPriceKey(token.chainId, NATIVE_TOKEN_ADDRESS)) ??
187
+ maps.lifiPrices.get(lifiPriceKey(token.chainId, wrappedNative)) ??
188
+ resolveOwnPriceUsd(wrapped, maps));
189
+ };
190
+ /** The token's price by its own address, with no native-wrapper pairing. */
191
+ const resolveOwnPriceUsd = (token, { pulsexPrices, lifiPrices, }) => {
141
192
  if (isPulsechain(token.chainId)) {
142
193
  if (sameAddress(token.address, PULSECHAIN_USD_ANCHOR_ADDRESS)) {
143
194
  return 1;
@@ -132,6 +132,23 @@ export type ProviderDescriptor = {
132
132
  * `providers.test.ts`).
133
133
  */
134
134
  destinationCallCarriesNativeValue: boolean;
135
+ /**
136
+ * How this provider takes an Ethereum token from the reader's wallet.
137
+ *
138
+ * - `'allowance'` — the provider's contract pulls the token, so the wallet
139
+ * must first approve that contract as a spender. The execution quote names
140
+ * the spender (`ConsolidatedQuote.approvalAddress`), and the executor
141
+ * raises the allowance before it sends.
142
+ * - `'transfer'` — the reader sends the token to an address the provider
143
+ * gives. A plain transfer needs no approval, and the parser leaves
144
+ * `approvalAddress` null.
145
+ *
146
+ * A DISPLAY FACT, NEVER A SIGNING ONE. The press reads the live quote's own
147
+ * `approvalAddress` and the live allowance. This field lets a route say, BEFORE
148
+ * any live quote exists, that its wallet may ask for an approval first.
149
+ * The native coin never needs one, whatever this says.
150
+ */
151
+ originTokenSpend: 'allowance' | 'transfer';
135
152
  /**
136
153
  * Phase one: the plain forward-solving origin-to-anchor swap pre-quote.
137
154
  *
package/dist/providers.js CHANGED
@@ -11,6 +11,8 @@ const lifiDescriptor = {
11
11
  executesDestinationCalls: true,
12
12
  destinationCallOriginChainIds: [Number(Chains.ETH)],
13
13
  destinationCallCarriesNativeValue: false,
14
+ // Its quote names the Diamond as `estimate.approvalAddress`.
15
+ originTokenSpend: 'allowance',
14
16
  preQuote: {
15
17
  method: 'GET',
16
18
  build: buildLifiPreQuoteRequest,
@@ -33,6 +35,8 @@ const relayDescriptor = {
33
35
  executesDestinationCalls: true,
34
36
  destinationCallOriginChainIds: 'any',
35
37
  destinationCallCarriesNativeValue: true,
38
+ // Its quote carries an approve step; the parser lifts its spender.
39
+ originTokenSpend: 'allowance',
36
40
  preQuote: {
37
41
  method: 'POST',
38
42
  build: buildRelayPreQuoteRequest,
@@ -61,6 +65,8 @@ const nearIntentsDescriptor = {
61
65
  executesDestinationCalls: false,
62
66
  destinationCallOriginChainIds: [],
63
67
  destinationCallCarriesNativeValue: false,
68
+ // The reader sends the token to a deposit address.
69
+ originTokenSpend: 'transfer',
64
70
  preQuote: {
65
71
  method: 'POST',
66
72
  build: buildNearIntentsPreQuoteRequest,
@@ -34,7 +34,11 @@ export declare const referralIntegrator = "gibs.finance";
34
34
  */
35
35
  export declare const resolveAttribution: (override: string | null | undefined, packageDefault: string | undefined) => string | undefined;
36
36
  /**
37
- * Payout address for Relay application fees.
37
+ * Gibs Finance's payout address for Relay application fees.
38
+ *
39
+ * NO BUILDER APPLIES IT. A Relay request carries an `appFees` entry only when
40
+ * the caller passes `relayAppFee`. The Gibs interface passes this address
41
+ * explicitly; any other caller passes its own, or nothing.
38
42
  *
39
43
  * Set to the owner's receiving wallet. Setting it back to `null` is a working
40
44
  * state rather than a broken one, and is the way to stop collecting: Relay
@@ -53,9 +57,13 @@ export declare const resolveAttribution: (override: string | null | undefined, p
53
57
  */
54
58
  export declare const relayReferralRecipient: Hex | null;
55
59
  /**
56
- * Default application fee in basis points. Ten basis points is one tenth of one
57
- * percent. The interface can override it per provider in stored settings; this
58
- * is the value a reader who has never touched the setting gets.
60
+ * The application fee the Gibs interface charges, in basis points. Ten basis
61
+ * points is one tenth of one percent. The interface can override it per
62
+ * provider in stored settings; this is the value a reader who has never touched
63
+ * the setting gets.
64
+ *
65
+ * NO BUILDER APPLIES IT. It reaches a request only when a caller passes it as
66
+ * `relayAppFee.basisPoints` (Relay) or converts it to `lifiFee` (LI.FI).
59
67
  */
60
68
  export declare const defaultReferralFeeBasisPoints = 10n;
61
69
  /**
package/dist/referral.js CHANGED
@@ -33,7 +33,11 @@ export const referralIntegrator = 'gibs.finance';
33
33
  */
34
34
  export const resolveAttribution = (override, packageDefault) => (override === null ? undefined : (override ?? packageDefault));
35
35
  /**
36
- * Payout address for Relay application fees.
36
+ * Gibs Finance's payout address for Relay application fees.
37
+ *
38
+ * NO BUILDER APPLIES IT. A Relay request carries an `appFees` entry only when
39
+ * the caller passes `relayAppFee`. The Gibs interface passes this address
40
+ * explicitly; any other caller passes its own, or nothing.
37
41
  *
38
42
  * Set to the owner's receiving wallet. Setting it back to `null` is a working
39
43
  * state rather than a broken one, and is the way to stop collecting: Relay
@@ -52,9 +56,13 @@ export const resolveAttribution = (override, packageDefault) => (override === nu
52
56
  */
53
57
  export const relayReferralRecipient = '0xAF2ce0189f46f5663715b0b9ED2a10eA924AB9B0';
54
58
  /**
55
- * Default application fee in basis points. Ten basis points is one tenth of one
56
- * percent. The interface can override it per provider in stored settings; this
57
- * is the value a reader who has never touched the setting gets.
59
+ * The application fee the Gibs interface charges, in basis points. Ten basis
60
+ * points is one tenth of one percent. The interface can override it per
61
+ * provider in stored settings; this is the value a reader who has never touched
62
+ * the setting gets.
63
+ *
64
+ * NO BUILDER APPLIES IT. It reaches a request only when a caller passes it as
65
+ * `relayAppFee.basisPoints` (Relay) or converts it to `lifiFee` (LI.FI).
58
66
  */
59
67
  export const defaultReferralFeeBasisPoints = 10n;
60
68
  /**
@@ -0,0 +1,2 @@
1
+ import { type Hex } from 'viem';
2
+ export declare const relayDepositoryByChainId: ReadonlyMap<number, Hex>;
@@ -0,0 +1,82 @@
1
+ import { getAddress } from 'viem';
2
+ /**
3
+ * Relay's deposit contract (its "depository") on each Ethereum-style chain:
4
+ * the ONLY address a Relay origin deposit may be sent to, and the only
5
+ * address it may ask the reader to approve.
6
+ *
7
+ * WHY A FIXED TABLE. A quote that names its own spender cannot be trusted to
8
+ * name an honest one: a compromised quote host could name any contract. This
9
+ * table ships with the code and changes only through a release.
10
+ *
11
+ * SOURCE. Relay's Chains API, `GET https://api.relay.link/chains`, field
12
+ * `chains[].protocol.v2.depository`, read 2026-10-07 for every chain whose
13
+ * `vmType` is `evm`. Relay's documentation says its depository addresses are
14
+ * published only through that API
15
+ * (https://docs.relay.link/references/protocol/addresses). A live quote the
16
+ * same day (Ethereum USDC to Base, `POST /quote/v2`) approved and deposited to
17
+ * exactly the Ethereum entry below.
18
+ *
19
+ * NOT THE `contracts` BLOCK. The same API also lists `relayReceiver`,
20
+ * `approvalProxy` and `erc20Router` under `chains[].contracts`. The live
21
+ * quote used none of them, so they are left out: an address the quote does
22
+ * not use is not one this table should accept.
23
+ *
24
+ * A CHAIN NOT LISTED IS REFUSED. If Relay adds a chain or moves a depository,
25
+ * deposits there are refused until this table is updated.
26
+ */
27
+ const common = getAddress('0x4cd00e387622c35bddb9b4c962c136462338bc31');
28
+ const alternate = getAddress('0x59916da825d2d2ec1bf878d71c88826f6633ecca');
29
+ export const relayDepositoryByChainId = new Map([
30
+ [1, common], // ethereum
31
+ [10, common], // optimism
32
+ [25, alternate], // cronos
33
+ [56, common], // bsc
34
+ [100, common], // gnosis
35
+ [130, common], // unichain
36
+ [137, common], // polygon
37
+ [143, common], // monad
38
+ [146, common], // sonic
39
+ [169, common], // manta-pacific
40
+ [196, common], // xlayer
41
+ [288, common], // boba
42
+ [324, common], // zksync
43
+ [360, common], // shape
44
+ [480, common], // world-chain
45
+ [747, common], // flow-evm
46
+ [988, common], // stable
47
+ [999, common], // hyperevm
48
+ [1088, alternate], // metis
49
+ [1135, common], // lisk
50
+ [1868, common], // soneium
51
+ [2020, common], // ronin
52
+ [2741, common], // abstract
53
+ [2818, common], // morph
54
+ [4217, common], // tempo
55
+ [4326, common], // megaeth
56
+ [4663, common], // robinhood
57
+ [5000, alternate], // mantle
58
+ [5031, common], // somnia
59
+ [5042, common], // arc
60
+ [5330, common], // superseed
61
+ [8453, common], // base
62
+ [9745, common], // plasma
63
+ [33139, common], // apechain
64
+ [34443, common], // mode
65
+ [42018, common], // mythos
66
+ [42161, common], // arbitrum
67
+ [42220, common], // celo
68
+ [43114, common], // avalanche
69
+ [48900, common], // zircuit
70
+ [57073, common], // ink
71
+ [59144, alternate], // linea
72
+ [60808, common], // bob
73
+ [80094, common], // berachain
74
+ [81457, common], // blast
75
+ [97477, common], // doma
76
+ [98866, common], // plume
77
+ [534352, common], // scroll
78
+ [685689, common], // gensyn
79
+ [747474, common], // katana
80
+ [5064014, common], // ethereal
81
+ [7777777, common], // zora
82
+ ]);
@@ -1,3 +1,4 @@
1
+ import { buildUrl } from '@gibs/common/url';
1
2
  import { decodeFunctionData, encodeFunctionData, erc20Abi, maxUint256, zeroAddress, } from 'viem';
2
3
  import { Chains } from './config.js';
3
4
  import { isNonEthereumVirtualMachineOrigin, } from './consolidated-quote.js';
@@ -861,9 +862,7 @@ export const parseRelayQuote = (payload) => {
861
862
  * than trust it — see `checkConsolidatedRecord`.
862
863
  */
863
864
  export const relayStatusUrl = ({ quoteId }) => {
864
- const url = new URL(relayRequestsUrl);
865
- url.searchParams.set('id', quoteId);
866
- return url.toString();
865
+ return buildUrl(relayRequestsUrl, { id: quoteId });
867
866
  };
868
867
  /**
869
868
  * Relay's request lifecycle states. Terminal states are `success` (the
@@ -208,9 +208,9 @@ export type DirectCrossingCandidateInputs = {
208
208
  * to quote — the user bridges directly from that chain to PulseChain. The
209
209
  * same-chain skip in {@link useAggregatorRouteCandidate} is correct (nothing to
210
210
  * quote), but it left the conversion path empty. This helper fills that gap by
211
- * synthesizing a zero-step candidate that `usePulseXConversionRoutes` (the user
212
- * interface's plural composer) can consume unchanged, producing the correct
213
- * same-chain conversion route so the ranker can pick the cheapest option.
211
+ * synthesizing a zero-step candidate. The user interface no longer calls this:
212
+ * the route engine serves a conversion now. The export stays as published
213
+ * interface until a separate decision removes it.
214
214
  *
215
215
  * The synthetic quote carries zero steps (no aggregator hops), a zero execution
216
216
  * duration (the direct crossing needs no quote wait), and a null transaction
@@ -8,9 +8,9 @@ import { omnibridgeProviderForEntryChain } from './providers.js';
8
8
  * to quote — the user bridges directly from that chain to PulseChain. The
9
9
  * same-chain skip in {@link useAggregatorRouteCandidate} is correct (nothing to
10
10
  * quote), but it left the conversion path empty. This helper fills that gap by
11
- * synthesizing a zero-step candidate that `usePulseXConversionRoutes` (the user
12
- * interface's plural composer) can consume unchanged, producing the correct
13
- * same-chain conversion route so the ranker can pick the cheapest option.
11
+ * synthesizing a zero-step candidate. The user interface no longer calls this:
12
+ * the route engine serves a conversion now. The export stays as published
13
+ * interface until a separate decision removes it.
14
14
  *
15
15
  * The synthetic quote carries zero steps (no aggregator hops), a zero execution
16
16
  * duration (the direct crossing needs no quote wait), and a null transaction
@@ -0,0 +1,31 @@
1
+ import { type ProviderKey } from '../providers.js';
2
+ /**
3
+ * The carrier id of the native-coin wrap and unwrap step
4
+ * (`routing/carriers/wrap.ts`).
5
+ */
6
+ export declare const WRAP_CARRIER_ID = "wrap";
7
+ /**
8
+ * The steps a route can take that no third-party provider performs. A step of
9
+ * this kind is drawn from its own mark, never from a provider's.
10
+ */
11
+ export type CarrierStepKind = 'wrap';
12
+ /**
13
+ * The one place an engine carrier id becomes a {@link ProviderKey}.
14
+ *
15
+ * AN ENGINE CARRIER ID IS NOT A PROVIDER KEY. The engine is a plug-in host: a
16
+ * carrier id is any string, and one of them, `'wrap'`, names a step that no
17
+ * provider performs. Handing a raw id to the logo, glyph, or name tables drew a
18
+ * blank mark and logged an error. Route data resolves the key here, once, and
19
+ * every display site reads the resolved key.
20
+ *
21
+ * @param carrierId - the `carrier` of a planned hop's edge
22
+ * @returns the provider key, or null when no registered provider carries that id
23
+ */
24
+ export declare const carrierProviderKey: (carrierId: string) => ProviderKey | null;
25
+ /**
26
+ * The kind of provider-less step a carrier id performs.
27
+ *
28
+ * @param carrierId - the `carrier` of a planned hop's edge
29
+ * @returns the step kind, or null when the carrier is a provider or unknown
30
+ */
31
+ export declare const carrierStepKind: (carrierId: string) => CarrierStepKind | null;
@@ -0,0 +1,37 @@
1
+ import { Providers } from '../config.js';
2
+ import { providerRegistry } from '../providers.js';
3
+ /**
4
+ * The carrier id of the native-coin wrap and unwrap step
5
+ * (`routing/carriers/wrap.ts`).
6
+ */
7
+ export const WRAP_CARRIER_ID = 'wrap';
8
+ /**
9
+ * Every engine carrier id that IS a provider, mapped to its {@link ProviderKey}.
10
+ * The omnibridge crossings carry the provider value itself; the aggregators
11
+ * carry their registry id; the PulseChain exchange carries its own literal.
12
+ */
13
+ const providerKeyByCarrierId = new Map([
14
+ ...Object.values(Providers).map((provider) => [provider, provider]),
15
+ ...providerRegistry.map((descriptor) => [descriptor.id, descriptor.id]),
16
+ ['PulseX', 'PulseX'],
17
+ ]);
18
+ /**
19
+ * The one place an engine carrier id becomes a {@link ProviderKey}.
20
+ *
21
+ * AN ENGINE CARRIER ID IS NOT A PROVIDER KEY. The engine is a plug-in host: a
22
+ * carrier id is any string, and one of them, `'wrap'`, names a step that no
23
+ * provider performs. Handing a raw id to the logo, glyph, or name tables drew a
24
+ * blank mark and logged an error. Route data resolves the key here, once, and
25
+ * every display site reads the resolved key.
26
+ *
27
+ * @param carrierId - the `carrier` of a planned hop's edge
28
+ * @returns the provider key, or null when no registered provider carries that id
29
+ */
30
+ export const carrierProviderKey = (carrierId) => providerKeyByCarrierId.get(carrierId) ?? null;
31
+ /**
32
+ * The kind of provider-less step a carrier id performs.
33
+ *
34
+ * @param carrierId - the `carrier` of a planned hop's edge
35
+ * @returns the step kind, or null when the carrier is a provider or unknown
36
+ */
37
+ export const carrierStepKind = (carrierId) => carrierId === WRAP_CARRIER_ID ? 'wrap' : null;
@@ -29,6 +29,11 @@ export type CarrierAskAnswer = {
29
29
  readonly reason: string | null;
30
30
  /** A minimum amount this refusal names, in `edge.from`'s base units, or null. */
31
31
  readonly minimumAmount: bigint | null;
32
+ /**
33
+ * What the carrier can still take today, in `edge.from`'s base units,
34
+ * when the refusal is `'above-available'`. Absent otherwise.
35
+ */
36
+ readonly availableAmount?: bigint;
32
37
  };
33
38
  };
34
39
  /**
@@ -48,6 +53,13 @@ export type CarrierExactResult = {
48
53
  readonly deliveredAmount: bigint;
49
54
  /** How far this edge's executed rate falls below its marginal rate, in percent; absent when the carrier cannot measure it. */
50
55
  readonly priceImpactPercent?: number;
56
+ /**
57
+ * The token addresses the carrier's own execution passes through, in
58
+ * order, when it chose among several (a PulseX swap path). The figure
59
+ * above was priced along exactly this path, so it is the path that must
60
+ * be signed. Absent when the carrier makes no such choice.
61
+ */
62
+ readonly swapPath?: readonly string[];
51
63
  } | {
52
64
  readonly outcome: 'refused';
53
65
  readonly refusal: {
@@ -56,5 +68,10 @@ export type CarrierExactResult = {
56
68
  readonly reason: string | null;
57
69
  /** A minimum amount this refusal names, in `edge.from`'s base units, or null. */
58
70
  readonly minimumAmount: bigint | null;
71
+ /**
72
+ * What the carrier can still take today, in `edge.from`'s base units,
73
+ * when the refusal is `'above-available'`. Absent otherwise.
74
+ */
75
+ readonly availableAmount?: bigint;
59
76
  };
60
77
  };
@@ -277,9 +277,27 @@ const prepareOmnibridgeReads = async ({ nodes, edgesFrom, isProductionNetwork, c
277
277
  }
278
278
  return preparedByEdgeKey;
279
279
  };
280
- const entryAmountVerdictReason = (verdict) => verdict.reason === 'below-minimum'
281
- ? `below the entry mediator's minimum of ${verdict.minimumPerTransaction} base units`
282
- : `above what the entry mediator has left today (${verdict.availablePerTransaction} base units)`;
280
+ /**
281
+ * The refusal an entry mediator's own bound produces, carrying the bound
282
+ * itself as a figure: `minimumAmount` below the floor, `availableAmount`
283
+ * above what is left today. A reader is told the number to type, so the
284
+ * number must survive here rather than live only inside the reason string.
285
+ *
286
+ * @param verdict - the crossed bound, in the entry token's base units
287
+ * @returns the refusal, in `edge.from`'s base units
288
+ */
289
+ const entryAmountRefusal = (verdict) => verdict.reason === 'below-minimum'
290
+ ? {
291
+ kind: 'below-minimum',
292
+ reason: `below the entry mediator's minimum of ${verdict.minimumPerTransaction} base units`,
293
+ minimumAmount: verdict.minimumPerTransaction,
294
+ }
295
+ : {
296
+ kind: 'above-available',
297
+ reason: `above what the entry mediator has left today (${verdict.availablePerTransaction} base units)`,
298
+ minimumAmount: null,
299
+ availableAmount: verdict.availablePerTransaction,
300
+ };
283
301
  /**
284
302
  * Builds the omnibridge crossing as a plug-in carrier.
285
303
  *
@@ -316,15 +334,7 @@ export const createOmnibridgeCarrier = (options) => {
316
334
  return null;
317
335
  const verdict = checkEntryAmount({ amount: amountIn, limits: prepared.limits });
318
336
  if (!verdict.fits) {
319
- const minimumAmount = verdict.reason === 'below-minimum' ? verdict.minimumPerTransaction : null;
320
- return {
321
- outcome: 'refused',
322
- refusal: {
323
- kind: verdict.reason === 'below-minimum' ? 'below-minimum' : 'above-available',
324
- reason: entryAmountVerdictReason(verdict),
325
- minimumAmount,
326
- },
327
- };
337
+ return { outcome: 'refused', refusal: entryAmountRefusal(verdict) };
328
338
  }
329
339
  return { outcome: 'delivered', deliveredAmount: netDeliveredAmount(amountIn, prepared.feePerEther) };
330
340
  };
@@ -332,15 +342,7 @@ export const createOmnibridgeCarrier = (options) => {
332
342
  const limits = await readEntryLimits(edge);
333
343
  const verdict = checkEntryAmount({ amount: inputAmount, limits });
334
344
  if (!verdict.fits) {
335
- const minimumAmount = verdict.reason === 'below-minimum' ? verdict.minimumPerTransaction : null;
336
- return {
337
- ok: false,
338
- refusal: {
339
- kind: verdict.reason === 'below-minimum' ? 'below-minimum' : 'above-available',
340
- reason: entryAmountVerdictReason(verdict),
341
- minimumAmount,
342
- },
343
- };
345
+ return { ok: false, refusal: entryAmountRefusal(verdict) };
344
346
  }
345
347
  const feePerEther = await readFeePerEther(edge);
346
348
  const deliveredAmount = netDeliveredAmount(inputAmount, feePerEther);
@@ -0,0 +1,80 @@
1
+ import { type Hex } from 'viem';
2
+ /**
3
+ * Which swap path a PulseX hop takes. The hop no longer always goes through
4
+ * Wrapped PLS: the carrier prices every candidate path from the pool reserves
5
+ * it already read, and keeps the one that delivers the most. These are the
6
+ * pure parts of that choice — the hub list, the candidate paths, and the
7
+ * pick — kept apart from the carrier so each can be tested on its own.
8
+ */
9
+ /** One token a PulseX swap may pass through on its way from the input token to the output token. */
10
+ export type PulseXHubToken = {
11
+ /** The token's on-chain `symbol()`, for logs and tests only. Never used to match a token. */
12
+ readonly symbol: string;
13
+ /** The token's checksummed PulseChain address. Tokens are always matched by this. */
14
+ readonly address: Hex;
15
+ };
16
+ /**
17
+ * The tokens a PulseX swap may route through, Wrapped PLS first. Each address
18
+ * comes from a constant this package already holds; none is written here:
19
+ *
20
+ * - WPLS — `nativeAssetOut[Chains.PLS]` (`config.ts`).
21
+ * - PLSX, HEX, USDC, USDT — `pulsechainTokenAddresses` (`config.ts`).
22
+ * - DAI (from Ethereum) — `PULSECHAIN_USD_ANCHOR_ADDRESS` (`prices.ts`).
23
+ * - WETH (from Ethereum) — the Ethereum pathway's `bridgedNativeAssetOut` (`config.ts`).
24
+ *
25
+ * KEPT SMALL ON PURPOSE. Each hub adds pools to the one batched reserve read
26
+ * the carrier makes per search, and adds paths to every price it computes.
27
+ * Seven hubs give at most 20 candidate paths per hop (see
28
+ * {@link candidatePulseXPaths}).
29
+ */
30
+ export declare const pulseXHubTokens: readonly PulseXHubToken[];
31
+ /**
32
+ * Every swap path a PulseX hop from `from` to `to` may take:
33
+ *
34
+ * - direct: `[from, to]`;
35
+ * - one hub: `[from, hub, to]` for each hub, Wrapped PLS among them;
36
+ * - two hubs: `[from, wrappedNative, hub, to]` and `[from, hub, wrappedNative, to]`
37
+ * for each other hub — Wrapped PLS holds the deepest pools on PulseX, so it
38
+ * is the one middle token worth pairing with every other hub.
39
+ *
40
+ * A path that names a token twice is dropped. That removes every path whose
41
+ * hub IS `from` or `to`, and the same-token hop when `from` equals `to`.
42
+ *
43
+ * The fixed path this carrier used before — `[from, WPLS, to]`, or
44
+ * `[from, to]` when one side is WPLS — is always in the result, so the pick
45
+ * can only match or beat it.
46
+ *
47
+ * @param options.from - the pool token the swap spends
48
+ * @param options.to - the pool token the swap buys
49
+ * @param options.wrappedNative - Wrapped PLS
50
+ * @param options.hubs - the hub token addresses, Wrapped PLS included
51
+ * @returns the candidate paths, direct first, each at least two tokens long
52
+ */
53
+ export declare const candidatePulseXPaths: (options: {
54
+ readonly from: Hex;
55
+ readonly to: Hex;
56
+ readonly wrappedNative: Hex;
57
+ readonly hubs: readonly Hex[];
58
+ }) => readonly (readonly Hex[])[];
59
+ /** One candidate path, priced. */
60
+ export type PulseXPathQuote<Step> = {
61
+ /** The tokens the swap passes through, in order. */
62
+ readonly path: readonly Hex[];
63
+ /** What the path delivers for the trade, in the last token's base units. */
64
+ readonly deliveredAmount: bigint;
65
+ /** The pools the path crosses, in order — kept so the caller can measure price impact on the same pools. */
66
+ readonly steps: readonly Step[];
67
+ };
68
+ /**
69
+ * The priced path that delivers the most. A path that delivers nothing is
70
+ * never chosen. Among equal outputs the path with fewer pools wins, and among
71
+ * equal lengths the earlier candidate wins, so the pick is deterministic.
72
+ *
73
+ * Gas is not weighed beyond that tie-break: the search charges one flat gas
74
+ * figure per swap signature, whatever its path length (`route-search-gas.ts`
75
+ * in the interface), so there is no per-pool gas figure to subtract here.
76
+ *
77
+ * @param quotes - the priced candidates; paths with a missing pool are already left out
78
+ * @returns the best candidate, or null when none delivers anything
79
+ */
80
+ export declare const bestPulseXPathQuote: <Step>(quotes: readonly PulseXPathQuote<Step>[]) => PulseXPathQuote<Step> | null;
@@ -0,0 +1,92 @@
1
+ import { getAddress } from 'viem';
2
+ import { Chains, Providers, nativeAssetOut, pathways, pulsechainTokenAddresses } from '../../config.js';
3
+ import { PULSECHAIN_USD_ANCHOR_ADDRESS } from '../../prices.js';
4
+ /** Wrapped PLS: the first hub, and the middle token of every two-hub path. */
5
+ const wrappedPulseAddress = getAddress(nativeAssetOut[Chains.PLS]);
6
+ /** The Ethereum-bridged WETH on PulseChain, read off the Ethereum pathway's own config. Undefined only if that pathway is removed. */
7
+ const bridgedWrappedEtherAddress = pathways[Providers.PULSECHAIN][Chains.PLS]?.[Chains.ETH]?.bridgedNativeAssetOut;
8
+ /**
9
+ * The tokens a PulseX swap may route through, Wrapped PLS first. Each address
10
+ * comes from a constant this package already holds; none is written here:
11
+ *
12
+ * - WPLS — `nativeAssetOut[Chains.PLS]` (`config.ts`).
13
+ * - PLSX, HEX, USDC, USDT — `pulsechainTokenAddresses` (`config.ts`).
14
+ * - DAI (from Ethereum) — `PULSECHAIN_USD_ANCHOR_ADDRESS` (`prices.ts`).
15
+ * - WETH (from Ethereum) — the Ethereum pathway's `bridgedNativeAssetOut` (`config.ts`).
16
+ *
17
+ * KEPT SMALL ON PURPOSE. Each hub adds pools to the one batched reserve read
18
+ * the carrier makes per search, and adds paths to every price it computes.
19
+ * Seven hubs give at most 20 candidate paths per hop (see
20
+ * {@link candidatePulseXPaths}).
21
+ */
22
+ export const pulseXHubTokens = Object.freeze([
23
+ { symbol: 'WPLS', address: wrappedPulseAddress },
24
+ { symbol: 'PLSX', address: pulsechainTokenAddresses.PLSX },
25
+ { symbol: 'HEX', address: pulsechainTokenAddresses.HEX },
26
+ { symbol: 'DAI', address: getAddress(PULSECHAIN_USD_ANCHOR_ADDRESS) },
27
+ { symbol: 'USDC', address: pulsechainTokenAddresses.USDC },
28
+ { symbol: 'USDT', address: pulsechainTokenAddresses.USDT },
29
+ ...(bridgedWrappedEtherAddress === undefined ? [] : [{ symbol: 'WETH', address: getAddress(bridgedWrappedEtherAddress) }]),
30
+ ].map((hub) => Object.freeze(hub)));
31
+ /** Whether a path names any token twice, compared without regard to letter case. */
32
+ const repeatsAToken = (path) => new Set(path.map((address) => address.toLowerCase())).size !== path.length;
33
+ /** Whether two addresses are the same token, compared without regard to letter case. */
34
+ const sameToken = (left, right) => left.toLowerCase() === right.toLowerCase();
35
+ /**
36
+ * Every swap path a PulseX hop from `from` to `to` may take:
37
+ *
38
+ * - direct: `[from, to]`;
39
+ * - one hub: `[from, hub, to]` for each hub, Wrapped PLS among them;
40
+ * - two hubs: `[from, wrappedNative, hub, to]` and `[from, hub, wrappedNative, to]`
41
+ * for each other hub — Wrapped PLS holds the deepest pools on PulseX, so it
42
+ * is the one middle token worth pairing with every other hub.
43
+ *
44
+ * A path that names a token twice is dropped. That removes every path whose
45
+ * hub IS `from` or `to`, and the same-token hop when `from` equals `to`.
46
+ *
47
+ * The fixed path this carrier used before — `[from, WPLS, to]`, or
48
+ * `[from, to]` when one side is WPLS — is always in the result, so the pick
49
+ * can only match or beat it.
50
+ *
51
+ * @param options.from - the pool token the swap spends
52
+ * @param options.to - the pool token the swap buys
53
+ * @param options.wrappedNative - Wrapped PLS
54
+ * @param options.hubs - the hub token addresses, Wrapped PLS included
55
+ * @returns the candidate paths, direct first, each at least two tokens long
56
+ */
57
+ export const candidatePulseXPaths = (options) => {
58
+ const { from, to, wrappedNative, hubs } = options;
59
+ const oneHubPaths = hubs.map((hub) => [from, hub, to]);
60
+ const twoHubPaths = hubs
61
+ .filter((hub) => !sameToken(hub, wrappedNative))
62
+ .flatMap((hub) => [
63
+ [from, wrappedNative, hub, to],
64
+ [from, hub, wrappedNative, to],
65
+ ]);
66
+ return [[from, to], ...oneHubPaths, ...twoHubPaths].filter((path) => !repeatsAToken(path));
67
+ };
68
+ /**
69
+ * Whether `candidate` should replace `best`: it delivers more, or it delivers
70
+ * the same and crosses fewer pools. A tie goes to the shorter path because
71
+ * each extra pool costs gas, and nothing else separates the two.
72
+ */
73
+ const beats = (candidate, best) => {
74
+ if (candidate.deliveredAmount !== best.deliveredAmount)
75
+ return candidate.deliveredAmount > best.deliveredAmount;
76
+ return candidate.path.length < best.path.length;
77
+ };
78
+ /**
79
+ * The priced path that delivers the most. A path that delivers nothing is
80
+ * never chosen. Among equal outputs the path with fewer pools wins, and among
81
+ * equal lengths the earlier candidate wins, so the pick is deterministic.
82
+ *
83
+ * Gas is not weighed beyond that tie-break: the search charges one flat gas
84
+ * figure per swap signature, whatever its path length (`route-search-gas.ts`
85
+ * in the interface), so there is no per-pool gas figure to subtract here.
86
+ *
87
+ * @param quotes - the priced candidates; paths with a missing pool are already left out
88
+ * @returns the best candidate, or null when none delivers anything
89
+ */
90
+ export const bestPulseXPathQuote = (quotes) => quotes
91
+ .filter((quote) => quote.deliveredAmount > 0n)
92
+ .reduce((best, quote) => (best === null || beats(quote, best) ? quote : best), null);