@ninsei-labs/sdk 0.40.0-develop.1 → 0.41.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/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## 0.41.0 — 2026-10-11
2
+
3
+ THE DEPOSITOR PATH IS GONE. The factory no longer offers `createOrderAndFundByDepositor`, so the package must
4
+ stop building a call to a selector that does not exist in the deployed factory.
5
+
6
+ - `src/swap-flow.mjs` no longer carries or relays `depositorSignature`: the funding path is the one where the
7
+ payer and the refund recipient are always the same address.
8
+ - The engine mirror loses `evm/depositor.js` (47 modules instead of 48) and the EIP-712 spec comment now names
9
+ the order-quote form.
10
+ - A consumer that meant to fund someone else's quote funds through `createOrderAndFund`.
11
+
12
+ ## 0.40.0 — 2026-10-11
13
+
14
+ THE ASYNCHRONOUS ROUTE PROVIDERS REACH `latest`. This release moves the tag from 0.39.3; the demo pins this exact
15
+ version rather than the tag.
16
+
17
+ - Four asynchronous route providers, each declaring what it settles: CoWSwap (native), KyberSwap Limit Order
18
+ (wrapped: a liquidity leg, never the sole provider for a native need), UniswapX (native), and the relayed
19
+ Uniswap route through the UniversalRouter (native).
20
+ - `submitUniswapxOrder` publishes an order through a submitter you name; the package never carries an API key.
21
+ - A route can be composed of two legs - liquidity to the wrapped token, then a native leg - and a provider that
22
+ cannot carry the route to what the person needs can never be chosen alone.
23
+ - The engine mirror is regenerated from the source of truth and the vendored browser bundle is rebuilt.
24
+ - The config names the demo's current escrow (factory `0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2`, cashier
25
+ `0x7E84035B4f42219F3fCE22855c6Dd7D36942B019`, registry `0x416d548B497d3AE808807172a5Bc3549512a64E5`): the
26
+ demo redeployed on 2026-10-11 and the retired router is gone from the configuration.
27
+ - The test wallet adapter signs on the demo network: `arbitrum-sepolia` is on its allowlist, and it no longer
28
+ takes a gas price from a tick behind, which came out below the next block's base fee and was rejected.
29
+
30
+ The synchronous swap path is untouched: nothing that already existed changed behaviour.
31
+
1
32
  ## 0.40.0-develop.1 — 2026-10-10
2
33
 
3
34
  A build of the asynchronous route-provider work, published for the demo and for our own testing. This build does NOT
@@ -46,7 +46,7 @@ export const ENDPOINTS = Object.freeze({
46
46
  // with the page's actual origin, so a hard-coded host breaks the session on any other host.
47
47
  const PAGE_ORIGIN = typeof location !== "undefined" ? location.origin : "";
48
48
 
49
- // THE EIP-712 SIGNING DOMAIN (name/version) for the depositor signature. It is NOT a brand literal in the signer: it
49
+ // THE EIP-712 SIGNING DOMAIN (name/version) for a signed order quote. It is NOT a brand literal in the signer: it
50
50
  // belongs to the registry contract (it returns DOMAIN_SEPARATOR()), so it is taken from the config here. The defaults
51
51
  // reproduce today's result byte for byte.
52
52
  export const SIGNING_DOMAIN = Object.freeze({ name: "NinseiEscrowFactory", version: "1" });
@@ -363,14 +363,19 @@ export const CHAINS = [
363
363
  explorerName: "arbiscan (sepolia)",
364
364
  gasUsd: 0.01,
365
365
  routeType: "atomic",
366
- // THE DEMO'S MAIN NETWORK: the escrow went here, and the FEE ROUTER IS DEPLOYED here - also the factory (it
367
- // spawns escrows, keeps the rate, holds the fee cashier). The address is a FACT, not an intention:
368
- // 0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8 (height 314323871, transaction
369
- // 0x7a88c0249867b76b11c6cb678e6092fdc19404b51540a64c5fd03f5a32f74826, code 8223 bytes, keccak256
370
- // 0x90f26f641b48a2a630463195d4429b0818d29187563e09711a40811fcf08a0ba); it added EARLY REFUND in the dead
371
- // zone - with no mark, refund is open right after readyBy and it publishes the depositor's half. The code
372
- // matches the current repo build byte for byte.
373
- // `npm run check:deployed -- --address=0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8`).
366
+ // THE DEMO'S MAIN NETWORK: the Ninsei escrow factory, the interface's fee cashier and the maker registry live
367
+ // here; the ArrakisFeeRouter is gone. The factory is a FACT, not an intention:
368
+ // 0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2 (height 318033203, transaction
369
+ // 0x0f82c556f056f2dcaba5c12da75a749c257dfb1ccbf0eb6962a4e7f0119d166d, code 4226 bytes, keccak256
370
+ // 0x4b515f600c641bffde054b3a98f4e26bd43d2dd2b2d8e60e8cfe36ee03686dd9); every order is an EIP-1167 clone and
371
+ // the factory carries the pair (implementation(), implementationCodeHash()). The fee cashier is the second CREATE
372
+ // of the same pass - 0x7E84035B4f42219F3fCE22855c6Dd7D36942B019 (height 318033206, code 573 bytes, keccak256
373
+ // 0xcca1d8bc53d1d1c98901570800e093d0933f84637c6cdc727153b3ef8d8c3c36); the maker registry is the third -
374
+ // 0x416d548B497d3AE808807172a5Bc3549512a64E5 (height 318033209, code 4557 bytes). The code matches the current
375
+ // repo build byte for byte.
376
+ // `npm run check:deployed -- --address=0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2`).
377
+ // PREVIOUS (the ArrakisFeeRouter of the previous rules, now removed): 0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8
378
+ // (height 314323871, code 8223 bytes, keccak256 0x90f26f641b48a2a630463195d4429b0818d29187563e09711a40811fcf08a0ba).
374
379
  // PREVIOUS (ceiling 3%, no early refund): 0xEa619bDC78989627093B7830990367B2ECE52CE5 (height 314309038,
375
380
  // code 8143 bytes). PREVIOUS live (router with a 10% ceiling): 0x9f856cd9c14fd9493961473b804039b09a4012ef
376
381
  // (height 313597131, code 8723 bytes). PREVIOUS before it (factory without a fee):
@@ -379,9 +384,9 @@ export const CHAINS = [
379
384
  // (createOrderAndFund; the escrow has no separate funding function - an order is born funded, see FACTORY_METHODS
380
385
  // in www/js/evm/factory.js and ESCROW_METHODS in escrow.js). The old lock(sealedHalf) and hashlock are gone: the
381
386
  // half is not sent but revealed by presentation. Switching back to "simulated" is one edit, to show a demo with
382
- // no money movement. cashier: the INTERFACE's fee cashier, named in quotes. The contracts on chain are still the
383
- // previous ones, so there is no cashier there: the field is filled in the same pass as the factory address.
384
- escrow: { address: "0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8", mode: "live", cashier: null },
387
+ // no money movement. cashier: the INTERFACE's fee cashier, named in quotes and now deployed (the second CREATE of
388
+ // the same pass). implementationCodeHash: the escrow implementation the factory carries, checked against the chain.
389
+ escrow: { address: "0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2", mode: "live", cashier: "0x7E84035B4f42219F3fCE22855c6Dd7D36942B019", implementationCodeHash: "0xfd1ef12c1d96d3ee7060bd5901e06108723783403fad00c9630dcdeb6193674b" },
385
390
  tokens: [
386
391
  // Test USDC (Circle): decimals 6 and symbol "USDC" verified on chain by the same node.
387
392
  { symbol: "USDC", address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", decimals: 6, usd: 1, cls: "ar-token-usdc" },
@@ -19,9 +19,6 @@ export const FACTORY_METHODS = {
19
19
  predict: "0x0824d205",
20
20
  // create the escrow AND deposit funds in ONE transaction (a value transfer with the call).
21
21
  createOrderAndFund: "0x4703a9d3",
22
- // create the escrow and deposit when the DEPOSITOR is recovered FROM THE SIGNATURE over the quote digest:
23
- // anyone may send the transaction, and `locker` is set to the signer.
24
- createOrderAndFundByDepositor: "0x2acd187b",
25
22
  };
26
23
 
27
24
  // The OrderCreated event topic. It grew the registry address, the provider and the signing key:
@@ -129,15 +126,6 @@ export function encodeCreateOrderAndFund(quote) {
129
126
  return FACTORY_METHODS.createOrderAndFund + quoteCallArgs(quote);
130
127
  }
131
128
 
132
- // CREATE AND DEPOSIT when the depositor is recovered FROM ITS SIGNATURE over the quote digest. Anyone may send
133
- // the transaction, so there are TWO signers: quote.signature (the provider, checked by the escrow) and
134
- // depositorSignature (the depositor, checked by the factory, which requires the recovered address to equal
135
- // quote.locker). Both over ONE digest.
136
- export function encodeCreateOrderAndFundByDepositor(quote, depositorSignature) {
137
- return FACTORY_METHODS.createOrderAndFundByDepositor + quoteWords(quote) +
138
- signatureWords(quote.signature) + signatureWords(depositorSignature) + lockerHalfWords(quote);
139
- }
140
-
141
129
  // predict() answer - one address in a 32-byte word.
142
130
  export function decodeAddress(returnedHex) {
143
131
  const hex = String(returnedHex || "").replace(/^0x/, "");
@@ -14,10 +14,7 @@
14
14
  // keccak256 is passed in as a dependency, as in the address module: the caller loads the key library, not this
15
15
  // module. Monero and Ethereum share Keccak-256, so the hash computed by the bundle matches what Solidity checks.
16
16
 
17
- import { encodeCreateOrderAndFund, encodeCreateOrderAndFundByDepositor, encodePredict, decodeAddress, ORDER_CREATED_TOPIC } from "./factory.js";
18
- // THE DEPOSITOR BY SIGNATURE: the signature shape comes from one place (evm/depositor.js), next to the protocol
19
- // shape. Checking the shape here would be a second record of the same rule.
20
- import { depositorSignatureShape } from "./depositor.js";
17
+ import { encodeCreateOrderAndFund, encodePredict, decodeAddress, ORDER_CREATED_TOPIC } from "./factory.js";
21
18
  import { encodeStatus, decodeStatus } from "./escrow.js";
22
19
  // THE FEE: rate, amount and recipient are PART OF THE SIGNED QUOTE. The factory no longer has them (the router was
23
20
  // removed), so the amount comes from the quote and is cross-checked by the formula mirror. Without a quote there
@@ -152,7 +149,7 @@ async function waitReceipt(receiptReader, hash, { tries = 12, delayMs = 1500 } =
152
149
 
153
150
  // The full funding path. Steps are returned outward so the interface can show each one: predict is a free call,
154
151
  // createOrder and lock are transactions in the user's wallet.
155
- export async function fundOrder({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt: providedSalt, amountWei, amountLabel, claimWindowSeconds, readyWindowSeconds, readyBy, t1, quote, depositorSignature = null, call, send, receipt, onStep }) {
152
+ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt: providedSalt, amountWei, amountLabel, claimWindowSeconds, readyWindowSeconds, readyBy, t1, quote, call, send, receipt, onStep }) {
156
153
  if (!factory) throw new Error("no escrow factory address set for this network");
157
154
  if (!locker) throw new Error("no EVM wallet connected: there is nobody to deposit");
158
155
  if (!claimer) throw new Error("the order recipient (claimer) is not set");
@@ -243,20 +240,10 @@ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, co
243
240
  ? " (order " + amountWei + " + fee " + fee.feeWei + ")"
244
241
  : "";
245
242
  onStep?.("locking " + (amountLabel || "funds") + feeWords + " in one transaction - confirm in wallet");
246
- // WHICH FACTORY DOOR THE DEPOSIT GOES THROUGH, AND IT IS NOT DECORATION. If a depositor signature is present, we
247
- // deposit via part A: createOrderAndFundByDepositor recovers the depositor FROM THE SIGNATURE over the quote
248
- // digest, so anyone may send the transaction (a relayer, our service) while the `locker` role (right to refund,
249
- // right to mark readiness) stays with the signer. Without a signature, the old createOrderAndFund: one wallet
250
- // deposits and sends, and the direct swap works as before. On neither path may the sender change the order terms
251
- // - both signatures cover THE SAME quote digest.
252
- if (depositorSignature !== null && depositorSignature !== undefined && depositorSignature !== "") {
253
- const shape = depositorSignatureShape(depositorSignature);
254
- if (!shape.ok) throw new Error("the depositor signature is unusable: " + shape.reason);
255
- }
256
- const relayedByDepositor = Boolean(depositorSignature);
257
- const createData = relayedByDepositor
258
- ? encodeCreateOrderAndFundByDepositor(quoteEnc, depositorSignature)
259
- : encodeCreateOrderAndFund(quoteEnc);
243
+ // WHICH FACTORY DOOR THE DEPOSIT GOES THROUGH. There is ONE path: createOrderAndFund, where the wallet that
244
+ // sends the transaction becomes the locker. The sender may not change the order terms - the quote signature
245
+ // covers THE SAME digest the escrow checks.
246
+ const createData = encodeCreateOrderAndFund(quoteEnc);
260
247
  const createHash = await send({
261
248
  to: factory,
262
249
  data: createData,
@@ -300,8 +287,5 @@ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, co
300
287
  // THE FEE GOES OUTWARD: it went into the transaction and the deal record must keep it - otherwise the record
301
288
  // cannot tell how much the person deposited, and it would diverge from what the chain shows.
302
289
  return { feeWei: fee.feeWei, feeTotalWei: totalValueWei, feeBps: fee.bps, feeWasLegacyFactory: Boolean(fee.legacy),
303
- // WHICH FACTORY PATH RAN: visible outside, not only in the wallet's calldata. By it the checks and the
304
- // deal record know whether the ETH returns to the signer and not to the sender.
305
- depositorPath: relayedByDepositor,
306
290
  escrow: escrowToUse, predictedEscrow: escrow, escrowMismatch: Boolean(actualEscrow && actualEscrow !== String(escrow).toLowerCase()), salt, birthBlock, terms, createHash, lockHash };
307
291
  }
@@ -32,9 +32,8 @@ import { fundOrder } from "./funding.js";
32
32
  export * from "./factory.js";
33
33
  export * from "./escrow.js";
34
34
  export * from "./funding.js";
35
- // The depositor signature form (evm/depositor.js) and the permit verdict (evm/permit.js) go through the SAME
36
- // engine seam: the page and the SDK share one protocol code, not two lookalikes.
37
- export * from "./depositor.js";
35
+ // The permit verdict (evm/permit.js) goes through the SAME engine seam: the page and the SDK share one
36
+ // protocol code, not two lookalikes.
38
37
  export * from "./permit.js";
39
38
  // THE ASYNCHRONOUS ROUTE, EXECUTED - through the same engine seam as the decision that chooses it. The page reaches
40
39
  // it through the bridge; the SDK carries the same module in its engine mirror (tools/build-sdk-engine.mjs).
@@ -87,13 +86,10 @@ export async function simulateEscrowFunding(tx) {
87
86
  // could not pass even the first step. Neither syntax nor checks saw this, because the live path is never run.
88
87
  // FOUR FIELDS AND TWO DEADLINES EXPLICITLY. The deadlines come ready (readyBy, t1), not computed from now:
89
88
  // they go into the context the proof binds to, so they must be fixed before it.
90
- export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote, depositorSignature, onStep }) {
89
+ export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote, onStep }) {
91
90
  return fundOrder({
92
91
  factory,
93
92
  quote, // signed quote: carries the fee, registry address and provider
94
- // THE DEPOSITOR SIGNATURE OVER THE QUOTE DIGEST. Present: funding goes via
95
- // createOrderAndFundByDepositor and anyone may send the transaction. Absent: the old path.
96
- depositorSignature,
97
93
  locker,
98
94
  claimer,
99
95
  commitHalfLocker,
@@ -22,8 +22,8 @@
22
22
  // cannot cover the gas - the swap settles first and the escrow is funded after it. See usdcWithoutEthVerdict below
23
23
  // and www/js/evm/asyncRoute.js - and it is a NAMED refusal only when no provider serves the network.
24
24
  //
25
- // THE DEPOSITOR SIGNATURE IS A DIFFERENT ALLOWANCE. It lets a relayer SEND the order-creation transaction, but
26
- // not pull the token. Pulling USDC needs a separate TOKEN allowance (approve or permit) addressed to the puller.
25
+ // A RELAYED ORDER NEEDS A SEPARATE TOKEN ALLOWANCE. Letting a relayer SEND the order-creation transaction does
26
+ // not let it pull the token. Pulling USDC needs a separate TOKEN allowance (approve or permit) addressed to the puller.
27
27
 
28
28
  // THE BALANCE-DRIVEN PATH CHOICE. Whether a provider serves a network ASYNCHRONOUSLY (an intent auction whose
29
29
  // settlement arrives later - see www/js/evm/asyncRoute.js) is what decides the path for a person with no own
@@ -179,7 +179,7 @@ export function usdcWithoutEthVerdict({
179
179
  if (!isAddr(orchestrator)) {
180
180
  return {
181
181
  ok: false, blocked: true, kind: "no-orchestrator", path: null,
182
- reason: "no executor contract: only a contract could pull a token by the user signature and deposit native into the escrow in one transaction, and this build has none. The depositor signature without it lets a relayer SEND the order creation, but not pull the token",
182
+ reason: "no executor contract: only a contract could pull a token by the user signature and deposit native into the escrow in one transaction, and this build has none",
183
183
  };
184
184
  }
185
185
  if (permitSupported === false) {
@@ -551,12 +551,10 @@ export async function signMessage(message) {
551
551
  }
552
552
 
553
553
  // SIGNING TYPED DATA with the wallet (EIP-712, eth_signTypedData_v4). This is also NOT a transaction: the
554
- // signature moves no money and costs no gas, so a person WITHOUT their own ETH can give it. With it the
555
- // depositor lets the factory record his address in the order, and anyone may send the transaction for him
556
- // (the createOrderAndFundByDepositor path).
554
+ // signature moves no money and costs no gas, so a wallet without its own ETH can give it.
557
555
  //
558
- // WHAT EXACTLY IS SIGNED IS DECIDED BY THE CALLER: ready typed data arrives here (www/js/evm/depositor.js),
559
- // not a string. Assembling the shape here would be a second record of the protocol next to the real one.
556
+ // WHAT EXACTLY IS SIGNED IS DECIDED BY THE CALLER: ready typed data arrives here as an object, not a string.
557
+ // Assembling the shape here would be a second record of the protocol next to the real one.
560
558
  export async function signTypedData(typedData) {
561
559
  if (!provider || state.status !== "connected") {
562
560
  throw new Error("wallet not connected: typed-data signing is impossible");
@@ -68,7 +68,7 @@ import { height as nodeHeightValue } from "../monero/node.js";
68
68
 
69
69
  // THE CORE ADDRESS AS A STRING - THE BUILT BUNDLE UNDER www/: the page gets the core as ONE module, not a pile
70
70
  // of sdk/ sources; ?v=<hash> keeps the cache fresh.
71
- const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=3ce64c53";
71
+ const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=69d79c13";
72
72
 
73
73
  // A NAMED SEAM FOR CHECKS - THE SAME TECHNIQUE AS sdkBookUse BELOW. The asynchronous path is driven against a
74
74
  // stand-in order book (the demo network has no provider), and the ordinary escrow funding that follows a settled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninsei-labs/sdk",
3
- "version": "0.40.0-develop.1",
3
+ "version": "0.41.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -43,8 +43,11 @@ export const CLAIMER_ENV = "NINSEI_TEST_EVM_CLAIMER";
43
43
  // the registry's production networks the escrow is marked "simulated", that is, by the letter of the rule they are
44
44
  // not production. So the test key would also sign on mainnet - the engine would not notice, and the engine's rule
45
45
  // must not be changed (it is shared by the page and all legs). So the "signing is ALLOWED here" marker is set
46
- // separately and EXPLICITLY: only what is listed, and nothing more. First - the local anvil by chainId 31337.
47
- export const TEST_CHAIN_IDS = Object.freeze([31337]);
46
+ // separately and EXPLICITLY: only what is listed, and nothing more. The local anvil by chainId 31337, and the
47
+ // demo testnet arbitrum-sepolia by chainId 421614 - the network the demo escrow lives on (the registry entry
48
+ // marks it testnet: true with a live escrow, so the second line below already passes; without it in THIS list
49
+ // the first line refuses the whole demo contour, and no headless run can sign).
50
+ export const TEST_CHAIN_IDS = Object.freeze([31337, 421614]);
48
51
 
49
52
  const envOf = (name) => {
50
53
  const box = typeof process !== "undefined" && process ? process.env : null;
@@ -302,7 +305,16 @@ export function nodeTestEvmWallet({ privateKey, rpcUrl, chainId = null, id = "no
302
305
  const remote = await assertTestContour();
303
306
  const valueWei = BigInt(value === undefined || value === null ? 0 : value);
304
307
  const nonce = Number(BigInt(await provider.request({ method: "eth_getTransactionCount", params: [owner, "pending"] })));
305
- const gasPrice = BigInt(await provider.request({ method: "eth_gasPrice" }));
308
+ // NEVER SIGN BELOW THE BLOCK BASE FEE. On a live node eth_gasPrice answers a tick behind and comes out
309
+ // slightly BELOW the next block's base - the signature is then rejected with "max fee per gas less than
310
+ // block base fee". The node's own sender already carries this rule (rfq/evm.mjs: base + base/8); the test
311
+ // adapter must not diverge from it, or a headless run cannot send anything on such a network.
312
+ let gasPrice = BigInt(await provider.request({ method: "eth_gasPrice" }));
313
+ try {
314
+ const block = await provider.request({ method: "eth_getBlockByNumber", params: ["latest", false] });
315
+ const baseFee = block && block.baseFeePerGas ? BigInt(block.baseFeePerGas) : 0n;
316
+ if (baseFee > 0n && gasPrice <= baseFee) gasPrice = baseFee + baseFee / 8n;
317
+ } catch { /* the base fee was not read - the node's own price stays */ }
306
318
  let gasLimit = gas === null || gas === undefined ? null : BigInt(gas);
307
319
  if (gasLimit === null) {
308
320
  const est = BigInt(await provider.request({
@@ -22,7 +22,7 @@
22
22
  // signature covers exactly the XMR the chain verifies at order birth. Earlier this field
23
23
  // rode in the quote object OUTSIDE the digest on purpose - a field the chain could not
24
24
  // see must not be signed; once the contract learned it, it moved in here together with
25
- // the typehash, the depositor form and the independent vector.
25
+ // the typehash, the order-quote form and the independent vector.
26
26
  // commitHalfClaimer, - the provider's own commitment and ed25519 spend point: both are inputs to the
27
27
  // edPointClaimer escrow's termsHash, so they are bound and cannot be swapped for another order's.
28
28
  // chainId - bound in the domain AND in the message. A quote signed for one network does not
package/src/swap-flow.mjs CHANGED
@@ -66,10 +66,6 @@ export function escrowCashierAddress(chainSlug) {
66
66
  // The full flow. onStage(stage, detail) is called on every step - the interface must show what is going on.
67
67
  export async function runAtomicOrder({
68
68
  chainSlug, locker, claimer, amountWei, amountLabel, providerId = null,
69
- // THE DEPOSITOR'S SIGNATURE (issue #97, parts A/C): when given, the order is placed through
70
- // createOrderAndFundByDepositor - any wallet may send the transaction, while the signer is recorded as the
71
- // depositor.
72
- depositorSignature = null,
73
69
  claimWindowSeconds = 3600, readyWindowSeconds = 14400, salt: providedSalt, onStage,
74
70
  // THE SUBSTITUTION POINTS FOR THE SAKE OF THE CHECK, and they are not decoration. The live flow could be
75
71
  // neither run in a check nor covered otherwise: it requires a browser Worker and a signature in the wallet.
@@ -261,9 +257,6 @@ export async function runAtomicOrder({
261
257
  // (or from the caller in a simulation) and already participates in the context over which our half was
262
258
  // computed.
263
259
  factory, locker, claimer: order.claimer,
264
- // WE PASS THE DEPOSITOR'S SIGNATURE FURTHER instead of losing it halfway: without it the funding would go
265
- // the previous way, and the relayer would get a NotLocker refusal only after the person had signed.
266
- depositorSignature,
267
260
  // THE DEADLINES GO INTO THE CONTRACT EXACTLY THE ONES THAT ENTERED THE CONTEXT. This is not duplication but
268
261
  // a condition: diverge by even a second - and the proof by which the provider confirms the half stops
269
262
  // agreeing.
@@ -1,123 +0,0 @@
1
- // GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/depositor.js for the SDK package.
2
- // Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
3
- // Everything below this header is identical to the source.
4
-
5
- // THE DEPOSITOR BY SIGNATURE, CLIENT SIDE: EIP-712 typed data over the QUOTE DIGEST.
6
- //
7
- // WHY. The factory has a path createOrderAndFundByDepositor: ANYONE may send the transaction (a relayer, our
8
- // service), but the depositor is whoever signed the quote digest, so the depositor must sign first. A wallet
9
- // signature is typed data, not a transaction: it costs no gas and moves no money, so a person without ETH can give it.
10
- //
11
- // WHY THIS FORM, NOT AN INVENTED ONE. The field set and domain are PART OF THE PROTOCOL - the same OrderQuote
12
- // the escrow verifies and the node signs. Two swapped fields give a DIFFERENT digest and the factory refuses
13
- // BadDepositorSignature. So the form is ONE list below, and a check computes the digest with it and compares.
14
- //
15
- // DOMAIN. verifyingContract is the FACTORY ADDRESS FROM THE QUOTE (field `factory`), chainId is live: a signature
16
- // made for another factory or network will not recover here. There is no second source of the domain.
17
- //
18
- // NO CRYPTOGRAPHY HERE: the wallet computes the digest (eth_signTypedData_v4). Here is only the form and its check.
19
-
20
- import { SIGNING_DOMAIN } from "../core/config.js";
21
-
22
- // The domain name and version are quote protocol v8. They are NOT brand literals here: they belong to the registry
23
- // contract (it returns DOMAIN_SEPARATOR()), so they are read from the config. The defaults equal today's values.
24
- export const DEPOSITOR_DOMAIN_NAME = SIGNING_DOMAIN.name;
25
- export const DEPOSITOR_DOMAIN_VERSION = SIGNING_DOMAIN.version;
26
-
27
- // FIELD SET IN CONTRACT ORDER. The order is part of the ABI: a swap gives another digest. One list feeds both
28
- // the type string and the message, so they cannot diverge.
29
- export const ORDER_QUOTE_TYPED_FIELDS = [
30
- ["provider", "address"],
31
- ["quoteKey", "address"],
32
- ["registry", "address"],
33
- ["cashier", "address"],
34
- ["locker", "address"],
35
- ["claimer", "address"],
36
- ["amount", "uint256"],
37
- ["readyBy", "uint64"],
38
- ["t1", "uint64"],
39
- ["salt", "bytes32"],
40
- ["commitHalfClaimer", "bytes32"],
41
- ["edPointClaimer", "bytes32"],
42
- ["chainId", "uint256"],
43
- ["feeBps", "uint256"],
44
- ["fee", "uint256"],
45
- ["feeRecipient", "address"],
46
- ["validUntil", "uint64"],
47
- ["nonce", "uint256"],
48
- // THE XMR SIDE OF THE DEAL, in atomic units: it joined the signed set together with the contract
49
- // (OrderQuote.sol), the node/SDK spec and the vector, so the depositor's signature covers the same XMR the chain
50
- // verifies. A field the chain could not see used to stay OUT of this list on purpose - that is no longer the case.
51
- ["xmrAmount", "uint256"],
52
- ];
53
-
54
- // The message type. Built from the list: editing the list changes both the type and the signature, so it cannot
55
- // go unnoticed.
56
- export const ORDER_QUOTE_TYPED_TYPE = "OrderQuote(" + ORDER_QUOTE_TYPED_FIELDS.map(([n, t]) => t + " " + n).join(",") + ")";
57
-
58
- const isHex = (v, bytes) => new RegExp("^(0x)?[0-9a-fA-F]{" + bytes * 2 + "}$").test(String(v === undefined || v === null ? "" : v).trim());
59
- const isNum = (v) => /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(String(v === undefined || v === null ? "" : v).trim());
60
-
61
- // FORM IS NORMALISED, NOT DEMANDED. The quote carries ed25519 points WITHOUT the 0x prefix, while the wallet and
62
- // contract expect the same BYTES. So we normalise here and reject anything that is not address/bytes32/number.
63
- function normaliseField(name, type, value) {
64
- const raw = String(value === undefined || value === null ? "" : value).trim();
65
- if (raw === "") throw new Error("the quote carries no depositor-signature field: " + name);
66
- if (type === "address") {
67
- if (!isHex(raw, 20)) throw new Error("field " + name + " is not an address: " + raw);
68
- return "0x" + raw.replace(/^0x/, "").toLowerCase();
69
- }
70
- if (type === "bytes32") {
71
- if (!isHex(raw, 32)) throw new Error("field " + name + " is not bytes32: " + raw);
72
- return "0x" + raw.replace(/^0x/, "").toLowerCase();
73
- }
74
- // uint64 / uint256: the wallet accepts a string; decimal and hex are parsed the same.
75
- if (!isNum(raw)) throw new Error("field " + name + " is not a number: " + raw);
76
- return String(BigInt(raw));
77
- }
78
-
79
- /**
80
- * EIP-712 typed data that the depositor signs (eth_signTypedData_v4).
81
- * The domain is built FROM THE QUOTE: verifyingContract = quote.factory, chainId = quote.chainId.
82
- * @returns {{ domain: object, types: object, primaryType: string, message: object }}
83
- */
84
- export function orderQuoteTypedData(quote) {
85
- if (!quote || typeof quote !== "object") throw new Error("no signed quote: nothing to sign");
86
- if (!isHex(quote.factory, 20)) throw new Error("the quote has no factory address - cannot build the signing domain");
87
- if (!isNum(quote.chainId)) throw new Error("the quote has no chainId - cannot build the signing domain");
88
- const message = {};
89
- for (const [name, type] of ORDER_QUOTE_TYPED_FIELDS) {
90
- message[name] = normaliseField(name, type, quote[name]);
91
- }
92
- return {
93
- domain: {
94
- name: DEPOSITOR_DOMAIN_NAME,
95
- version: DEPOSITOR_DOMAIN_VERSION,
96
- chainId: Number(BigInt(quote.chainId)),
97
- verifyingContract: "0x" + String(quote.factory).replace(/^0x/, "").toLowerCase(),
98
- },
99
- types: {
100
- // EIP712Domain explicit: wallets show it to the person, and it must match what the chain computes.
101
- EIP712Domain: [
102
- { name: "name", type: "string" },
103
- { name: "version", type: "string" },
104
- { name: "chainId", type: "uint256" },
105
- { name: "verifyingContract", type: "address" },
106
- ],
107
- OrderQuote: ORDER_QUOTE_TYPED_FIELDS.map(([name, type]) => ({ name, type })),
108
- },
109
- primaryType: "OrderQuote",
110
- message,
111
- };
112
- }
113
-
114
- // SIGNATURE ON THE WIRE - 65 BYTES r || s || v. The form is checked HERE: the factory takes the signature as
115
- // three words, and a "signature-like" string must not reach the contract as consent.
116
- export function depositorSignatureShape(signature) {
117
- const hex = String(signature === undefined || signature === null ? "" : signature).replace(/^0x/, "");
118
- if (!/^[0-9a-fA-F]{130}$/.test(hex)) return { ok: false, reason: "the depositor signature must be 65 bytes (r || s || v), got " + hex.length / 2 + " bytes" };
119
- let v = parseInt(hex.slice(128, 130), 16);
120
- if (v < 27) v += 27;
121
- if (v !== 27 && v !== 28) return { ok: false, reason: "depositor signature v outside {27,28}: " + v };
122
- return { ok: true, r: "0x" + hex.slice(0, 64).toLowerCase(), s: "0x" + hex.slice(64, 128).toLowerCase(), v: 27 + (v - 27) };
123
- }