@ninsei-labs/sdk 0.39.3 → 0.40.0-develop.1
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/CHANGELOG.md +35 -0
- package/engine/atomic/dleq2.js +86 -111
- package/engine/atomic/halfenc.js +58 -72
- package/engine/atomic/halves.js +31 -53
- package/engine/atomic/order-client.js +15 -16
- package/engine/atomic/order.js +44 -62
- package/engine/atomic/orderContext.js +26 -50
- package/engine/atomic/orderHalfCommit.js +17 -18
- package/engine/core/chainSource.js +54 -69
- package/engine/core/clock.js +11 -12
- package/engine/core/config.js +554 -449
- package/engine/core/finality.js +34 -53
- package/engine/core/format.js +35 -36
- package/engine/core/preSignGuards.js +109 -117
- package/engine/core/rfqSource.js +57 -75
- package/engine/core/sellWindow.js +23 -24
- package/engine/core/store.js +20 -21
- package/engine/core/swap.js +242 -243
- package/engine/evm/amounts.js +50 -51
- package/engine/evm/asyncExec.js +577 -0
- package/engine/evm/asyncLegs.js +308 -0
- package/engine/evm/asyncRoute.js +179 -0
- package/engine/evm/auth.js +41 -48
- package/engine/evm/claimGas.js +28 -47
- package/engine/evm/depositor.js +39 -43
- package/engine/evm/dex.js +266 -267
- package/engine/evm/escrow.js +113 -129
- package/engine/evm/factory.js +71 -77
- package/engine/evm/fees.js +41 -42
- package/engine/evm/funding.js +160 -162
- package/engine/evm/gasReserve.js +67 -36
- package/engine/evm/index.js +46 -53
- package/engine/evm/injected.js +34 -42
- package/engine/evm/permit.js +129 -56
- package/engine/evm/prices.js +30 -45
- package/engine/evm/session.js +188 -189
- package/engine/mock/chain.js +17 -18
- package/engine/monero/address.js +33 -48
- package/engine/monero/halvesSweep.js +25 -43
- package/engine/monero/node.js +14 -15
- package/engine/monero/scan.js +82 -95
- package/engine/monero/swapKeys.js +30 -33
- package/engine/monero/unlock.js +17 -20
- package/engine/monero/wallet.js +32 -42
- package/engine/recovery/recoveryFile.js +154 -161
- package/engine/recovery/restore.js +50 -61
- package/engine/sdk/bridge.js +244 -234
- package/engine/ui/dom.js +4 -5
- package/engine/ui/walletModal.js +20 -31
- package/index.d.ts +8 -0
- package/package.json +1 -1
- package/src/adapters/evm-wallet.mjs +16 -0
- package/src/adapters/monero-wallet.mjs +1 -1
- package/src/adapters/monero.mjs +1 -1
- package/src/adapters/node-test-evm-wallet.mjs +5 -5
- package/src/config.mjs +1 -1
- package/src/engine.mjs +4 -0
- package/src/legs/cow-spec.mjs +254 -0
- package/src/legs/cow.mjs +315 -0
- package/src/legs/evm.mjs +86 -2
- package/src/legs/kyber-spec.mjs +265 -0
- package/src/legs/kyber.mjs +425 -0
- package/src/legs/shape.mjs +35 -0
- package/src/legs/uniswaprelay-spec.mjs +541 -0
- package/src/legs/uniswaprelay.mjs +422 -0
- package/src/legs/uniswapx-spec.mjs +329 -0
- package/src/legs/uniswapx.mjs +362 -0
- package/src/quotes.mjs +1 -1
- package/src/swaps.mjs +1 -1
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
|
|
2
|
+
// UNISWAPX - THE THIRD CONCRETE ASYNCHRONOUS ROUTE PROVIDER, A SIBLING OF cow.mjs AND kyber.mjs.
|
|
3
|
+
//
|
|
4
|
+
// WHAT IT IS. UniswapX is an intent auction: the swapper signs an EIP-712 order (gasless) and fillers compete to
|
|
5
|
+
// settle it on chain LATER as a separate transaction. So its shape is "async" - there is no single transaction that
|
|
6
|
+
// swaps and funds the escrow - and its execution ends when the order service reports the order FILLED, or finally
|
|
7
|
+
// dead (expired / error / cancelled / insufficient-funds). The `settled(request, deps)` below is that "what ends its
|
|
8
|
+
// execution", and the seam refuses an async provider without it.
|
|
9
|
+
//
|
|
10
|
+
// WHY THIS ONE CAN CARRY A ROUTE ALL THE WAY TO NATIVE. Its output token may be address(0), the protocol's NATIVE
|
|
11
|
+
// sentinel (uniswapx-spec.mjs, UNISWAPX_NATIVE, from Uniswap/UniswapX CurrencyLibrary). So a route to a native need
|
|
12
|
+
// can end HERE, unlike the KyberSwap leg (which settles the wrapped native only). `settles: "native"`.
|
|
13
|
+
//
|
|
14
|
+
// THE FLOW, AND WHERE THE HOSTED SERVICE IS.
|
|
15
|
+
// 1) BUILD the swapper's own part of the order LOCALLY (uniswapxPlan below): every field of a V2 Dutch order that
|
|
16
|
+
// the swapper signs is known here, and the digest is computed from this repository's own EIP-712
|
|
17
|
+
// (uniswapx-spec.mjs). This part needs no service.
|
|
18
|
+
// 2) SIGN it through the wallet adapter (eth_signTypedData_v4 over Permit2's domain, the order as the witness).
|
|
19
|
+
// 3) CO-SIGN AND PUBLISH. A V2 Dutch order is COSIGNED: `cosignerData` (the decay window and any overrides) and a
|
|
20
|
+
// `cosignature` are produced by the cosigner AFTER the swapper signs. Uniswap's own documentation states that
|
|
21
|
+
// the Uniswap Interface and the Uniswap API set the cosigner to Uniswap Labs ("Auction Types"), and orders
|
|
22
|
+
// reach fillers through the Uniswap API: "Order submission is handled by the Uniswap Trading API" (the UniswapX
|
|
23
|
+
// orders OpenAPI). That endpoint needs an API key, so PUBLISHING IS A HOSTED, KEYED STEP - and THIS PACKAGE
|
|
24
|
+
// HOLDS NO KEY. It posts the signed order to OUR SUBMITTER, a URL taken from configuration (the caller's
|
|
25
|
+
// `submitter`), and the submitter adds the key SERVER-SIDE. With no submitter it refuses by name
|
|
26
|
+
// ("uniswapx-no-submitter") instead of inventing an endpoint and instead of asking the browser for a key.
|
|
27
|
+
// 4) READ THE OUTCOME from the UniswapX orders service (GET /orders on https://api.uniswap.org/v2 - the endpoint
|
|
28
|
+
// Uniswap's own filler documentation tells fillers to poll), by the order hash, and read `orderStatus`.
|
|
29
|
+
//
|
|
30
|
+
// EVERY EXTERNAL FACT IS PINNED TO ITS SOURCE:
|
|
31
|
+
// * the order type, the field names/order, the Permit2 domain and the native sentinel - uniswapx-spec.mjs, with
|
|
32
|
+
// its own citations (Uniswap/UniswapX src/lib/V2DutchOrderLib.sol, DutchOrderLib.sol, OrderInfoLib.sol,
|
|
33
|
+
// CurrencyLibrary.sol; Uniswap/permit2 src/EIP712.sol; Uniswap/uniswapx-sdk src/order/V2DutchOrder.ts);
|
|
34
|
+
// * the reactor addresses per chain - developers.uniswap.org/deployments.json (the feed the UniswapX "Deployment"
|
|
35
|
+
// page points to): Ethereum mainnet V2DutchOrderReactor 0x00000011F84B9aa48e5f8aA8B9897600006289Be. Other chains
|
|
36
|
+
// run the Dutch V3 reactor, whose order struct is a different type and is NOT built here;
|
|
37
|
+
// * the publishing endpoint and its API key requirement - developers.uniswap.org, "Swapping via the Uniswap API"
|
|
38
|
+
// (POST /order for a UniswapX order) and the Trading API OpenAPI (trade-api.gateway.uniswap.org/v1/api.json:
|
|
39
|
+
// path /order, operation "Create a gasless order", security apiKey in header `x-api-key`);
|
|
40
|
+
// * the READ endpoint and status vocabulary - api.uniswap.org/v2/uniswapx/docs.json (GET /orders; OrderStatus
|
|
41
|
+
// enum open | expired | error | cancelled | filled | insufficient-funds) and developers.uniswap.org,
|
|
42
|
+
// "Fillers Overview" (poll the orders endpoint, at most 4 RPS).
|
|
43
|
+
//
|
|
44
|
+
// THE SHARED PRE-SIGNATURE GUARDS STAY ON FOR THIS PROVIDER TOO (uniswapxGate): the price is not the market, the
|
|
45
|
+
// amount is covered, the token allowance - the SAME guard functions the synchronous DEX leg and the other two
|
|
46
|
+
// providers use. An external, asynchronous route must not become a hole in the guards.
|
|
47
|
+
//
|
|
48
|
+
// NO NEW DEPENDENCY: the typed data is built by uniswapx-spec.mjs (this repository's own EIP-712), signing goes
|
|
49
|
+
// through the wallet adapter / the repository's signer, and the transport is the repository's own createHttp.
|
|
50
|
+
import { ASYNC } from "./shape.mjs";
|
|
51
|
+
import {
|
|
52
|
+
UNISWAPX_PERMIT2, UNISWAPX_NATIVE, UNISWAPX_ORDER_FIELDS,
|
|
53
|
+
uniswapxOrderMissingFields, uniswapxOrderHashHex, uniswapxOrderTypedData,
|
|
54
|
+
} from "./uniswapx-spec.mjs";
|
|
55
|
+
import { createHttp } from "../http.mjs";
|
|
56
|
+
import * as engine from "../engine.mjs";
|
|
57
|
+
|
|
58
|
+
// THE ORDER SERVICE (read side): the UniswapX orders API, one base URL for every chain (api.uniswap.org/v2).
|
|
59
|
+
export const UNISWAPX_API = "https://api.uniswap.org/v2";
|
|
60
|
+
export const UNISWAPX_ORDER_PATH = "/orders";
|
|
61
|
+
// The venue name, in one word (no assembly needed: it is a single token).
|
|
62
|
+
export const UNISWAPX_VENUE = "UniswapX";
|
|
63
|
+
|
|
64
|
+
// THE COSIGNER. A V2 Dutch order is cosigned, and Uniswap's documentation states the Uniswap Interface and the
|
|
65
|
+
// Uniswap API set the cosigner to Uniswap Labs ("Auction Types", concepts). The address below is the one every real
|
|
66
|
+
// mainnet Dutch_V2 order we read carries; it is a DEFAULT for building a well-formed order, and the cosignature
|
|
67
|
+
// itself is the cosigner's - this provider cannot produce it.
|
|
68
|
+
export const UNISWAPX_COSIGNER = "0x4449Cd34d1eb1FEDCF02A1Be3834FfDe8E6A6180";
|
|
69
|
+
|
|
70
|
+
// ZERO - used for the absent additional-validation contract.
|
|
71
|
+
export const UNISWAPX_ZERO = "0x0000000000000000000000000000000000000000";
|
|
72
|
+
|
|
73
|
+
// THE NETWORKS WHERE THIS PROVIDER CAN BUILD AN ORDER. Keyed by EVM chainId. Only the chains whose reactor accepts
|
|
74
|
+
// the V2 Dutch order this spec implements: Ethereum mainnet (V2DutchOrderReactor). A network absent here is a NAMED
|
|
75
|
+
// refusal ("uniswapx-unknown-network"), never a guess at an endpoint or a reactor.
|
|
76
|
+
export const UNISWAPX_NETWORKS = Object.freeze({
|
|
77
|
+
1: { orderbook: UNISWAPX_API, reactor: "0x00000011F84B9aa48e5f8aA8B9897600006289Be" },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// THE STATUS VOCABULARY OF THE ORDER SERVICE (api.uniswap.org/v2/uniswapx/docs.json: OrderStatus). A status outside
|
|
81
|
+
// this set is NOT "probably fine": the answer is not the shape we know and it is refused with "uniswapx-bad-response".
|
|
82
|
+
export const UNISWAPX_STATUSES = ["open", "expired", "error", "cancelled", "filled", "insufficient-funds"];
|
|
83
|
+
export const UNISWAPX_FINAL_STATUSES = ["expired", "error", "cancelled", "filled", "insufficient-funds"];
|
|
84
|
+
export const UNISWAPX_SETTLED_STATUS = "filled";
|
|
85
|
+
export const UNISWAPX_OPEN_STATUSES = ["open"];
|
|
86
|
+
|
|
87
|
+
// THE NAMED REFUSALS OF THIS PROVIDER. Every one is a VALUE ({ ok: false, code }), never silence and never null: a
|
|
88
|
+
// caller must always be able to tell "unreachable" from "refused" from "not settled yet", and a missing submitter
|
|
89
|
+
// ("uniswapx-no-submitter") from every other refusal - publishing is blocked one way here, and it is named.
|
|
90
|
+
export const UNISWAPX_REFUSAL_CODES = [
|
|
91
|
+
"uniswapx-unknown-network", // the chain has no V2 Dutch reactor here
|
|
92
|
+
"uniswapx-bad-order", // the order is incomplete or malformed - nothing to sign
|
|
93
|
+
"uniswapx-sign-failed", // the wallet did not produce a signature
|
|
94
|
+
"uniswapx-no-submitter", // no submitter URL was configured - publishing is refused, never guessed
|
|
95
|
+
"uniswapx-unreachable", // no answer from the order service (connection or timeout)
|
|
96
|
+
"uniswapx-order-unknown", // the service does not know this order hash
|
|
97
|
+
"uniswapx-refused", // the service answered and did not accept (it carries the status and its reason)
|
|
98
|
+
"uniswapx-bad-response", // the answer is not the shape we know
|
|
99
|
+
"uniswapx-not-settled", // the order stayed open until the timeout - "not yet", not "broken"
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const refusal = (code, params = {}) => ({ ok: false, code, params });
|
|
103
|
+
|
|
104
|
+
/** The reactor for a chain, or null. */
|
|
105
|
+
export const uniswapxReactorFor = (chainId) => {
|
|
106
|
+
const n = UNISWAPX_NETWORKS[Number(chainId)];
|
|
107
|
+
return n ? n.reactor : null;
|
|
108
|
+
};
|
|
109
|
+
/** The order service base URL for a chain, or null. */
|
|
110
|
+
export const uniswapxOrderbook = (chainId) => {
|
|
111
|
+
const n = UNISWAPX_NETWORKS[Number(chainId)];
|
|
112
|
+
return n ? n.orderbook : null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isAddress = (v) => /^0x[0-9a-fA-F]{40}$/.test(String(v || ""));
|
|
116
|
+
const isPositive = (v) => { try { return BigInt(String(v)) > 0n; } catch { return false; } };
|
|
117
|
+
const isHash = (v) => /^0x[0-9a-fA-F]{64}$/.test(String(v || ""));
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* THE INTENT: from what the person gives, build the swapper's own V2 Dutch order, its EIP-712 typed data and its
|
|
121
|
+
* order hash. Returns { ok: true, order, typedData, orderHash, chainId, owner, reactor } or a named refusal.
|
|
122
|
+
* Pure - no network, no wallet. The cosignerData / cosignature are NOT here: the cosigner adds them after the
|
|
123
|
+
* swapper signs (see the header) and this provider cannot produce them.
|
|
124
|
+
*/
|
|
125
|
+
export function buildUniswapxIntent(request = {}) {
|
|
126
|
+
const chainId = Number(request.chainId);
|
|
127
|
+
const net = UNISWAPX_NETWORKS[chainId];
|
|
128
|
+
if (!net) return { ...refusal("uniswapx-unknown-network", { chainId: Number.isFinite(chainId) ? chainId : null }), known: Object.keys(UNISWAPX_NETWORKS).map(Number) };
|
|
129
|
+
const swapper = request.swapper;
|
|
130
|
+
const recipient = request.recipient === undefined || request.recipient === null ? swapper : request.recipient;
|
|
131
|
+
const order = {
|
|
132
|
+
reactor: request.reactor === undefined || request.reactor === null ? net.reactor : request.reactor,
|
|
133
|
+
swapper,
|
|
134
|
+
nonce: request.nonce,
|
|
135
|
+
deadline: request.deadline,
|
|
136
|
+
additionalValidationContract: request.additionalValidationContract === undefined || request.additionalValidationContract === null ? UNISWAPX_ZERO : request.additionalValidationContract,
|
|
137
|
+
additionalValidationData: request.additionalValidationData === undefined || request.additionalValidationData === null ? "0x" : request.additionalValidationData,
|
|
138
|
+
cosigner: request.cosigner === undefined || request.cosigner === null ? UNISWAPX_COSIGNER : request.cosigner,
|
|
139
|
+
baseInputToken: request.sellToken,
|
|
140
|
+
baseInputStartAmount: request.sellAmountWei,
|
|
141
|
+
baseInputEndAmount: request.sellAmountEndWei === undefined || request.sellAmountEndWei === null ? request.sellAmountWei : request.sellAmountEndWei,
|
|
142
|
+
baseOutputs: [{
|
|
143
|
+
token: request.buyToken,
|
|
144
|
+
startAmount: request.buyAmountWei,
|
|
145
|
+
endAmount: request.buyAmountEndWei === undefined || request.buyAmountEndWei === null ? request.buyAmountWei : request.buyAmountEndWei,
|
|
146
|
+
recipient,
|
|
147
|
+
}],
|
|
148
|
+
};
|
|
149
|
+
const missing = uniswapxOrderMissingFields(order);
|
|
150
|
+
if (!isAddress(swapper)) missing.push("swapper");
|
|
151
|
+
if (!isAddress(order.reactor)) missing.push("reactor");
|
|
152
|
+
if (!isAddress(order.cosigner)) missing.push("cosigner");
|
|
153
|
+
if (!isAddress(order.baseInputToken)) missing.push("sellToken");
|
|
154
|
+
if (!isPositive(order.baseInputStartAmount) || !isPositive(order.baseInputEndAmount)) missing.push("sellAmountWei");
|
|
155
|
+
if (!isPositive(order.baseOutputs[0].startAmount) || !isPositive(order.baseOutputs[0].endAmount)) missing.push("buyAmountWei");
|
|
156
|
+
if (!isAddress(order.baseOutputs[0].token) || !isAddress(order.baseOutputs[0].recipient)) missing.push("buyToken");
|
|
157
|
+
if (!Number.isInteger(Number(order.deadline))) missing.push("deadline");
|
|
158
|
+
if (missing.length) return refusal("uniswapx-bad-order", { missing: [...new Set(missing)] });
|
|
159
|
+
try {
|
|
160
|
+
const typedData = uniswapxOrderTypedData({ order, chainId });
|
|
161
|
+
const orderHash = uniswapxOrderHashHex(order);
|
|
162
|
+
return { ok: true, chainId, owner: swapper, reactor: order.reactor, order, orderHash, typedData };
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// A malformed number/address inside the encoder: a NAMED refusal, not an exception escaping to the caller.
|
|
165
|
+
return refusal("uniswapx-bad-order", { why: String(error && error.message ? error.message : error) });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The provider's plan, in the shape the registry and the paths expect. Returns a VALUE with a named refusal. */
|
|
170
|
+
export function uniswapxPlan(request = {}) {
|
|
171
|
+
const intent = buildUniswapxIntent(request);
|
|
172
|
+
if (!intent.ok) return intent;
|
|
173
|
+
const output = intent.order.baseOutputs[0];
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
provider: "uniswapx",
|
|
177
|
+
venue: UNISWAPX_VENUE,
|
|
178
|
+
shape: ASYNC,
|
|
179
|
+
route: "uniswapx",
|
|
180
|
+
expectedOutWei: String(output.endAmount),
|
|
181
|
+
order: intent.order,
|
|
182
|
+
orderHash: intent.orderHash,
|
|
183
|
+
typedData: intent.typedData,
|
|
184
|
+
owner: intent.owner,
|
|
185
|
+
reactor: intent.reactor,
|
|
186
|
+
nativeOutput: String(output.token).toLowerCase() === UNISWAPX_NATIVE,
|
|
187
|
+
endsWith: { kind: "order-status", settledWhen: UNISWAPX_SETTLED_STATUS, final: [...UNISWAPX_FINAL_STATUSES], endpoint: UNISWAPX_ORDER_PATH },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* SIGN THE ORDER THROUGH THE WALLET ADAPTER. The adapter's own `signTypedData` is used when present (it wraps
|
|
193
|
+
* eth_signTypedData_v4); otherwise the same call is made through the adapter's EIP-1193 driver. A wallet that will
|
|
194
|
+
* not sign is a NAMED refusal, not a crash.
|
|
195
|
+
*/
|
|
196
|
+
export async function uniswapxSignOrder({ typedData, wallet, address = null } = {}) {
|
|
197
|
+
if (!typedData || typeof typedData !== "object") return refusal("uniswapx-bad-order", { field: "typedData" });
|
|
198
|
+
try {
|
|
199
|
+
if (wallet && typeof wallet.signTypedData === "function") {
|
|
200
|
+
const signature = await wallet.signTypedData(typedData);
|
|
201
|
+
if (typeof signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(signature)) return refusal("uniswapx-sign-failed", { why: "bad-signature-shape" });
|
|
202
|
+
return { ok: true, signature };
|
|
203
|
+
}
|
|
204
|
+
const driver = wallet && typeof wallet.driver === "function" ? wallet.driver() : null;
|
|
205
|
+
if (driver && typeof driver.request === "function") {
|
|
206
|
+
const from = address || (typeof wallet.address === "function" ? await wallet.address() : null);
|
|
207
|
+
const signature = await driver.request({ method: "eth_signTypedData_v4", params: [from, JSON.stringify(typedData)] });
|
|
208
|
+
if (typeof signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(signature)) return refusal("uniswapx-sign-failed", { why: "bad-signature-shape" });
|
|
209
|
+
return { ok: true, signature };
|
|
210
|
+
}
|
|
211
|
+
return refusal("uniswapx-sign-failed", { why: "no-wallet" });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return refusal("uniswapx-sign-failed", { why: String((error && error.code) || (error && error.message) || error) });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* PUBLISH THE SIGNED ORDER TO OUR SUBMITTER. The submitter is OUR proxy - a URL from configuration (the caller's
|
|
219
|
+
* `submitter`), NEVER a host baked into this package. It holds the Uniswap Trading API key SERVER-SIDE and forwards
|
|
220
|
+
* the order on, so the browser never sees a key and this function NEVER sends one: it posts `{signature, quote,
|
|
221
|
+
* routing}` with a JSON content-type and nothing else. The `quote` is the object the host returned from its own
|
|
222
|
+
* quote call - this provider does not fabricate it, and the cosigner is Uniswap Labs' (see the header). With no
|
|
223
|
+
* submitter URL it refuses by name ("uniswapx-no-submitter"). The endpoint is NEVER guessed.
|
|
224
|
+
*/
|
|
225
|
+
export async function submitUniswapxOrder({ quote = null, signature = null, routing = "DUTCH_V2", submitter = null, fetchImpl = null, timeoutMs = 20_000 } = {}) {
|
|
226
|
+
if (typeof signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(signature)) return refusal("uniswapx-bad-order", { field: "signature" });
|
|
227
|
+
if (!quote || typeof quote !== "object") return refusal("uniswapx-bad-order", { field: "quote" });
|
|
228
|
+
const target = submitter === undefined || submitter === null || submitter === "" ? null : String(submitter);
|
|
229
|
+
if (!target) return refusal("uniswapx-no-submitter", { field: "submitter" });
|
|
230
|
+
const http = createHttp({ apiBase: target, fetchImpl, timeoutMs });
|
|
231
|
+
// NO `x-api-key` HERE, EVER: the submitter adds it server-side, so the package and the browser stay key-free.
|
|
232
|
+
const body = JSON.stringify({ signature, quote, routing });
|
|
233
|
+
let res;
|
|
234
|
+
try {
|
|
235
|
+
res = await http.tryJson("", { method: "POST", headers: { "content-type": "application/json" }, body });
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return refusal("uniswapx-unreachable", { why: String((error && error.params && error.params.why) || (error && error.code) || error) });
|
|
238
|
+
}
|
|
239
|
+
if (!res || typeof res.ok !== "boolean") return refusal("uniswapx-bad-response", { where: "submit" });
|
|
240
|
+
if (!res.ok) return refusal("uniswapx-refused", { status: res.status, stage: "submit" });
|
|
241
|
+
const data = res.body && typeof res.body === "object" ? res.body : null;
|
|
242
|
+
if (!data || typeof data.orderId !== "string" || data.orderId === "") return refusal("uniswapx-bad-response", { where: "submit", field: "orderId" });
|
|
243
|
+
const status = data.orderStatus;
|
|
244
|
+
if (typeof status !== "string" || !UNISWAPX_STATUSES.includes(status)) return refusal("uniswapx-bad-response", { where: "submit", field: "orderStatus" });
|
|
245
|
+
return { ok: true, orderId: data.orderId, orderStatus: status, requestId: typeof data.requestId === "string" ? data.requestId : null };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* READ ONE ORDER by its hash. GET /orders?orderHash=<hash> -> { orders: [ ... ] }. The entry's `orderStatus` must be
|
|
250
|
+
* a known status word (else "uniswapx-bad-response"); an empty list means the service does not know the order
|
|
251
|
+
* ("uniswapx-order-unknown") - never silence, never null.
|
|
252
|
+
*/
|
|
253
|
+
export async function uniswapxOrderStatus({ orderHash, chainId = null, fetchImpl = null, timeoutMs = 20_000, api = null } = {}) {
|
|
254
|
+
const chain = Number(chainId);
|
|
255
|
+
const base = api ? String(api) : (Number.isFinite(chain) ? uniswapxOrderbook(chain) : null);
|
|
256
|
+
if (!base) return refusal("uniswapx-unknown-network", { chainId: Number.isFinite(chain) ? chain : null });
|
|
257
|
+
if (!isHash(orderHash)) return refusal("uniswapx-bad-order", { field: "orderHash" });
|
|
258
|
+
const http = createHttp({ apiBase: base, fetchImpl, timeoutMs });
|
|
259
|
+
let res;
|
|
260
|
+
try {
|
|
261
|
+
res = await http.tryJson(UNISWAPX_ORDER_PATH + "?orderHash=" + orderHash);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return refusal("uniswapx-unreachable", { why: String((error && error.params && error.params.why) || (error && error.code) || error) });
|
|
264
|
+
}
|
|
265
|
+
if (!res || typeof res.ok !== "boolean") return refusal("uniswapx-bad-response", { where: "orders" });
|
|
266
|
+
if (!res.ok) return refusal("uniswapx-refused", { status: res.status, stage: "orders" });
|
|
267
|
+
const orders = res.body && res.body.orders;
|
|
268
|
+
if (!Array.isArray(orders)) return refusal("uniswapx-bad-response", { where: "orders" });
|
|
269
|
+
const found = orders.find((o) => o && String(o.orderHash || "").toLowerCase() === String(orderHash).toLowerCase()) || orders[0] || null;
|
|
270
|
+
if (!found) return refusal("uniswapx-order-unknown", { orderHash });
|
|
271
|
+
const status = found.orderStatus;
|
|
272
|
+
if (typeof status !== "string" || !UNISWAPX_STATUSES.includes(status)) return refusal("uniswapx-bad-response", { where: "orderStatus", got: typeof status === "string" ? status : null });
|
|
273
|
+
return { ok: true, status, order: found };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* WHAT ENDS AN ASYNCHRONOUS PROVIDER'S EXECUTION - poll the order service until the order is settled or finally
|
|
278
|
+
* dead, with a timeout. Returns:
|
|
279
|
+
* { ok: true, done: true, settled: true, status: "filled" } - the swap settled;
|
|
280
|
+
* { ok: true, done: true, settled: false, status: "expired" | "error" | "cancelled" | ... } - finally dead;
|
|
281
|
+
* { ok: false, code: "uniswapx-not-settled" | "uniswapx-unreachable" | ... } - a NAMED refusal.
|
|
282
|
+
* "Not settled in time" is its OWN code: "we did not wait long enough" is a state of the order, not a breakdown.
|
|
283
|
+
* The clock and the sleep are injectable so the wait is testable without real time.
|
|
284
|
+
*/
|
|
285
|
+
export async function uniswapxSettled(request = {}, deps = {}) {
|
|
286
|
+
const { orderHash, chainId = null } = request;
|
|
287
|
+
if (!isHash(orderHash)) return refusal("uniswapx-bad-order", { field: "orderHash" });
|
|
288
|
+
const now = typeof deps.now === "function" ? deps.now : () => Date.now();
|
|
289
|
+
const sleep = typeof deps.sleep === "function" ? deps.sleep : (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
290
|
+
const pollMs = Number.isFinite(deps.pollMs) && deps.pollMs > 0 ? deps.pollMs : 5_000;
|
|
291
|
+
const timeoutMs = Number.isFinite(deps.timeoutMs) && deps.timeoutMs > 0 ? deps.timeoutMs : 300_000;
|
|
292
|
+
const startedAt = now();
|
|
293
|
+
let lastStatus = null;
|
|
294
|
+
for (;;) {
|
|
295
|
+
const read = await uniswapxOrderStatus({ orderHash, chainId, fetchImpl: deps.fetchImpl, timeoutMs: deps.requestTimeoutMs, api: deps.api });
|
|
296
|
+
if (!read.ok) return read; // unreachable / bad-response / unknown: a named refusal, not silence
|
|
297
|
+
lastStatus = read.status;
|
|
298
|
+
if (UNISWAPX_FINAL_STATUSES.includes(lastStatus)) {
|
|
299
|
+
return { ok: true, done: true, settled: lastStatus === UNISWAPX_SETTLED_STATUS, status: lastStatus, orderHash };
|
|
300
|
+
}
|
|
301
|
+
if (now() - startedAt >= timeoutMs) return refusal("uniswapx-not-settled", { orderHash, status: lastStatus, waitedMs: now() - startedAt });
|
|
302
|
+
await sleep(pollMs);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* THE SHARED PRE-SIGNATURE GUARDS, APPLIED TO THIS PROVIDER. The same guard functions the synchronous DEX leg and the
|
|
308
|
+
* other two providers use (engine.dex.priceGateVerdict, engine.dex.dexLegVerdict, engine.guards.allowanceVerdict),
|
|
309
|
+
* composed by the same summary guard (engine.guards.signingGateVerdict). Pure - no network, no wallet.
|
|
310
|
+
*/
|
|
311
|
+
export function uniswapxGate({
|
|
312
|
+
order = null, requiredOutWei = null, tokenUsd = null, referenceNativeUsd = null, tolerated = false, gates = null,
|
|
313
|
+
sellDecimals = null, buyDecimals = null, buySymbol = null, allowanceWei = null, allowanceConsent = false,
|
|
314
|
+
tokenIsNative = false, symbol = null, decimals = null,
|
|
315
|
+
} = {}) {
|
|
316
|
+
if (!order) return { ok: false, blocked: true, reason: "uniswapx-no-order", checks: [] };
|
|
317
|
+
const output = Array.isArray(order.baseOutputs) && order.baseOutputs.length ? order.baseOutputs[0] : null;
|
|
318
|
+
const amountOut = output ? output.endAmount : null;
|
|
319
|
+
const price = engine.dex.priceGateVerdict({
|
|
320
|
+
amountInWei: order.baseInputEndAmount, amountOutWei: amountOut,
|
|
321
|
+
inDecimals: sellDecimals, outDecimals: buyDecimals,
|
|
322
|
+
tokenUsd: Number(tokenUsd) > 0 ? Number(tokenUsd) : null,
|
|
323
|
+
referenceUsd: Number(referenceNativeUsd) > 0 ? Number(referenceNativeUsd) : null,
|
|
324
|
+
stableSteps: [], tolerated: Boolean(tolerated), gates,
|
|
325
|
+
});
|
|
326
|
+
const leg = { ok: true, amountOutWei: amountOut === null ? null : String(amountOut), price, wrapped: { symbol: buySymbol, decimals: buyDecimals } };
|
|
327
|
+
const coverage = engine.dex.dexLegVerdict(leg, requiredOutWei);
|
|
328
|
+
const coverageCheck = {
|
|
329
|
+
ok: !coverage.blocked, checked: coverage.kind !== "unchecked" && coverage.kind !== "unquoted",
|
|
330
|
+
blocks: coverage.blocked === true, kind: coverage.kind, reason: coverage.reason,
|
|
331
|
+
};
|
|
332
|
+
const allowance = engine.guards.allowanceVerdict({
|
|
333
|
+
tokenIsNative, allowanceWei, amountWei: order.baseInputEndAmount,
|
|
334
|
+
consent: allowanceConsent === true, symbol, decimals: Number.isFinite(Number(decimals)) ? Number(decimals) : 18,
|
|
335
|
+
});
|
|
336
|
+
const gate = engine.guards.signingGateVerdict([coverageCheck, allowance]);
|
|
337
|
+
return { ...gate, price, coverage, allowance };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// THE PROVIDER, AS THE REGISTRY EXPECTS IT: id, kind, shape and, for "async", what ends execution (settled).
|
|
341
|
+
export const uniswapxProvider = Object.freeze({
|
|
342
|
+
id: "uniswapx",
|
|
343
|
+
kind: "uniswapx",
|
|
344
|
+
venue: UNISWAPX_VENUE,
|
|
345
|
+
shape: ASYNC,
|
|
346
|
+
// WHAT IT SETTLES: native coin. An UniswapX output token may be the NATIVE sentinel address(0)
|
|
347
|
+
// (uniswapx-spec.mjs), so this provider can carry a native need ALL THE WAY - it is a native leg of a composed
|
|
348
|
+
// route, or a single route on its own.
|
|
349
|
+
settles: "native",
|
|
350
|
+
networks: UNISWAPX_NETWORKS,
|
|
351
|
+
settlement: UNISWAPX_NETWORKS[1].reactor,
|
|
352
|
+
permit2: UNISWAPX_PERMIT2,
|
|
353
|
+
nativeToken: UNISWAPX_NATIVE,
|
|
354
|
+
orderFields: UNISWAPX_ORDER_FIELDS,
|
|
355
|
+
plan: uniswapxPlan,
|
|
356
|
+
settled: uniswapxSettled,
|
|
357
|
+
buildIntent: buildUniswapxIntent,
|
|
358
|
+
sign: uniswapxSignOrder,
|
|
359
|
+
submit: submitUniswapxOrder,
|
|
360
|
+
status: uniswapxOrderStatus,
|
|
361
|
+
gate: uniswapxGate,
|
|
362
|
+
});
|
package/src/quotes.mjs
CHANGED
|
@@ -478,7 +478,7 @@ export function createQuotes({ config, http, now = () => Date.now(), evmCall = n
|
|
|
478
478
|
}
|
|
479
479
|
// BINDING THE QUOTE TO OUR TERMS. The signature confirms the provider signed THIS quote, but not that it is
|
|
480
480
|
// about our swap: we are responsible for the binding. The context string is computed by OUR engine module
|
|
481
|
-
// (format
|
|
481
|
+
// (format ninsei-order-v3; the field order is part of the protocol and must not be rearranged). There is
|
|
482
482
|
// deliberately no copy of the format here: a copy would silently drift from the protocol.
|
|
483
483
|
//
|
|
484
484
|
// THE ORDER OF THE GATES: signature first, then binding. So a refusal names the nearest reason, not the first
|
package/src/swaps.mjs
CHANGED
|
@@ -247,7 +247,7 @@ const recordInputOf = (request) => {
|
|
|
247
247
|
// WHAT THE PERSON SAW: the maker, its spread, the fee, the rate and the quote signature. Screen data, but it must
|
|
248
248
|
// live in the record - otherwise the progress screen shows an empty maker instead of whoever gave the price.
|
|
249
249
|
makerId: pass(quote.makerId), makerName: pass(quote.makerName), makerSpread: pass(quote.makerSpread),
|
|
250
|
-
quoteSignature: pass(quote.signature), fee: pass(quote.
|
|
250
|
+
quoteSignature: pass(quote.signature), fee: pass(quote.ninseiFee),
|
|
251
251
|
dexSpread: pass(quote.dexSpread), gasUsd: pass(quote.gasUsd), rate: pass(quote.rate),
|
|
252
252
|
};
|
|
253
253
|
};
|