@ninsei-labs/sdk 0.39.0 → 0.39.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/src/engine.mjs CHANGED
@@ -1,53 +1,53 @@
1
- // THE ENGINE SEAM, NOW SELF-CONTAINED.
2
- //
3
- // The engine modules the core depends on are mirrored under sdk/engine/ - byte-for-byte copies kept in
4
- // sync by tools/build-sdk-engine.mjs and guarded by tools/check-sdk-engine.mjs. This file is the ONLY
5
- // place that knows where the engine lives; no other SDK module does.
6
- //
7
- // WHY A VERIFIED MIRROR, NOT A MOVE. The very same modules are loaded by the demo page in the browser
8
- // (www/js), and the production web root is www/ alone - the sdk/ directory is not served there (see
9
- // tools/serve.mjs). Lifting the files out of www/js would 404 every engine import on the live site, and
10
- // dozens of repository checks read those sources by their www/js path. So the single source of truth
11
- // stays www/js and the SDK carries a copy: any drift reddens check-sdk-engine, so it cannot diverge
12
- // silently (the same rule build-sweep uses for the sweep/rfq copies).
13
- //
14
- // The mirror is generated - never edit sdk/engine/ by hand. Change www/js, then run:
15
- // node tools/build-sdk-engine.mjs
16
- export * as config from "../engine/core/config.js";
17
- export * as guards from "../engine/core/preSignGuards.js";
18
- export * as restore from "../engine/recovery/restore.js";
19
- // STORES - THROUGH THIS SAME SEAM. Both modules take an adapter from outside (get/set/remove), and by default
20
- // use web storage if it is present: without an adapter they run in memory rather than fail on a missing DOM.
21
- export * as store from "../engine/core/store.js";
22
- export * as auth from "../engine/evm/auth.js";
23
- export * as swap from "../engine/core/swap.js";
24
- export * as recoveryFile from "../engine/recovery/recoveryFile.js";
25
- export * as halves from "../engine/atomic/halves.js";
26
- export * as orderContext from "../engine/atomic/orderContext.js";
27
- export * as order from "../engine/atomic/order.js";
28
- // HALF TRANSFER: the byte format and opening are the very same ones the page worker uses
29
- // (www/js/atomic/order-worker.js). The core keeps no copy of the format: the commitment is computed over those
30
- // exact bytes, and a second copy would silently drift from the calldata.
31
- export * as halfenc from "../engine/atomic/halfenc.js";
32
- // The engine's EVM layer: talking to the chain (order terms, sending, reading) - through this seam.
33
- export * as evm from "../engine/evm/index.js";
34
- // THE DEAL ENGINE NO LONGER LIVES IN www/js (#32, wave 3): the order flow moved into the package
35
- // (sdk/src/swap-flow.mjs) and is declared in sdk/index.d.ts. The page reaches it through the bridge, so the
36
- // mirror no longer carries core/swap-flow.js - what stays in the seam is the ORDER CLIENT (the half worker),
37
- // the ORDER QUOTE source and the shared CLAIM GAS LIMIT that the moved flow reads through this same seam.
38
- export * as orderClient from "../engine/atomic/order-client.js";
39
- export * as rfqSource from "../engine/core/rfqSource.js";
40
- export * as claimGas from "../engine/evm/claimGas.js";
41
- export * as escrow from "../engine/evm/escrow.js";
42
- export * as halvesSweep from "../engine/monero/halvesSweep.js";
43
- // MONERO ADDRESSES - THROUGH THE SAME MODULE THE DEMO USES. The format, the network prefixes and the address
44
- // check live in www/js/monero/address.js (checkAddress: base58 + keccak256 + network), and the page uses it
45
- // too. The wallet adapter needs exactly networkFromShape: its own address must belong to the network named by
46
- // the core, and that is checked by the same code, not by a private prefix table.
47
- export * as xmrAddress from "../engine/monero/address.js";
48
- // THE unlock_time RULE AND THE BLOCK READER - FROM THE SAME MIRROR, NOT A COPY. A second copy of "the output
49
- // is barred until a boundary" would be a second truth about money, so the rule lives under www/js
50
- // (monero/unlock.js) together with the Monero block parser (monero/scan.js), and the SDK reads them through
51
- // the engine mirror. check-sdk-engine reddens on any drift, so the two cannot diverge silently.
52
- export * as xmrUnlock from "../engine/monero/unlock.js";
53
- export * as xmrScan from "../engine/monero/scan.js";
1
+ // THE ENGINE SEAM, NOW SELF-CONTAINED.
2
+ //
3
+ // The engine modules the core depends on are mirrored under sdk/engine/ - byte-for-byte copies kept in
4
+ // sync by tools/build-sdk-engine.mjs and guarded by tools/check-sdk-engine.mjs. This file is the ONLY
5
+ // place that knows where the engine lives; no other SDK module does.
6
+ //
7
+ // WHY A VERIFIED MIRROR, NOT A MOVE. The very same modules are loaded by the demo page in the browser
8
+ // (www/js), and the production web root is www/ alone - the sdk/ directory is not served there (see
9
+ // tools/serve.mjs). Lifting the files out of www/js would 404 every engine import on the live site, and
10
+ // dozens of repository checks read those sources by their www/js path. So the single source of truth
11
+ // stays www/js and the SDK carries a copy: any drift reddens check-sdk-engine, so it cannot diverge
12
+ // silently (the same rule build-sweep uses for the sweep/rfq copies).
13
+ //
14
+ // The mirror is generated - never edit sdk/engine/ by hand. Change www/js, then run:
15
+ // node tools/build-sdk-engine.mjs
16
+ export * as config from "../engine/core/config.js";
17
+ export * as guards from "../engine/core/preSignGuards.js";
18
+ export * as restore from "../engine/recovery/restore.js";
19
+ // STORES - THROUGH THIS SAME SEAM. Both modules take an adapter from outside (get/set/remove), and by default
20
+ // use web storage if it is present: without an adapter they run in memory rather than fail on a missing DOM.
21
+ export * as store from "../engine/core/store.js";
22
+ export * as auth from "../engine/evm/auth.js";
23
+ export * as swap from "../engine/core/swap.js";
24
+ export * as recoveryFile from "../engine/recovery/recoveryFile.js";
25
+ export * as halves from "../engine/atomic/halves.js";
26
+ export * as orderContext from "../engine/atomic/orderContext.js";
27
+ export * as order from "../engine/atomic/order.js";
28
+ // HALF TRANSFER: the byte format and opening are the very same ones the page worker uses
29
+ // (www/js/atomic/order-worker.js). The core keeps no copy of the format: the commitment is computed over those
30
+ // exact bytes, and a second copy would silently drift from the calldata.
31
+ export * as halfenc from "../engine/atomic/halfenc.js";
32
+ // The engine's EVM layer: talking to the chain (order terms, sending, reading) - through this seam.
33
+ export * as evm from "../engine/evm/index.js";
34
+ // THE DEAL ENGINE NO LONGER LIVES IN www/js (#32, wave 3): the order flow moved into the package
35
+ // (sdk/src/swap-flow.mjs) and is declared in sdk/index.d.ts. The page reaches it through the bridge, so the
36
+ // mirror no longer carries core/swap-flow.js - what stays in the seam is the ORDER CLIENT (the half worker),
37
+ // the ORDER QUOTE source and the shared CLAIM GAS LIMIT that the moved flow reads through this same seam.
38
+ export * as orderClient from "../engine/atomic/order-client.js";
39
+ export * as rfqSource from "../engine/core/rfqSource.js";
40
+ export * as claimGas from "../engine/evm/claimGas.js";
41
+ export * as escrow from "../engine/evm/escrow.js";
42
+ export * as halvesSweep from "../engine/monero/halvesSweep.js";
43
+ // MONERO ADDRESSES - THROUGH THE SAME MODULE THE DEMO USES. The format, the network prefixes and the address
44
+ // check live in www/js/monero/address.js (checkAddress: base58 + keccak256 + network), and the page uses it
45
+ // too. The wallet adapter needs exactly networkFromShape: its own address must belong to the network named by
46
+ // the core, and that is checked by the same code, not by a private prefix table.
47
+ export * as xmrAddress from "../engine/monero/address.js";
48
+ // THE unlock_time RULE AND THE BLOCK READER - FROM THE SAME MIRROR, NOT A COPY. A second copy of "the output
49
+ // is barred until a boundary" would be a second truth about money, so the rule lives under www/js
50
+ // (monero/unlock.js) together with the Monero block parser (monero/scan.js), and the SDK reads them through
51
+ // the engine mirror. check-sdk-engine reddens on any drift, so the two cannot diverge silently.
52
+ export * as xmrUnlock from "../engine/monero/unlock.js";
53
+ export * as xmrScan from "../engine/monero/scan.js";
package/src/lock.mjs CHANGED
@@ -1,140 +1,140 @@
1
- // LOCKING THE FUNDS: TERMS FIRST, THEN SENDING.
2
- //
3
- // The first half of the step is here - assembling and CHECKING the order terms BEFORE anything goes on-chain. The
4
- // rules are not ours: the contract sets them, and the engine already checks them (`www/js/evm/escrow.js`,
5
- // `orderTerms`). The core will not hold a copy of the rules: a copy would drift from the contract right where the
6
- // error shows up only as a rejected transaction.
7
- //
8
- // WHAT IS HERE: both the assembly of the terms and the sending itself - it goes through the engine's EVM layer
9
- // (`fundOrder`), and the EVM wallet (the `send`/`receipt` role) comes from outside. What reads the chain is also a
10
- // seam: by default the engine's read, but the caller can substitute its own (as in actions.mjs).
11
- import { SdkError } from "./errors.mjs";
12
- import * as engine from "./engine.mjs";
13
- // THE DEAL FLOW IS A PACKAGE MODULE NOW (#32, wave 3): the registry address is read through it, exactly as
14
- // the actions read the order flow.
15
- import * as flow from "./swap-flow.mjs";
16
-
17
- // THE ORDER'S WORD-ADDRESSES ARE BROUGHT TO 0x HERE, AT THE BOUNDARY, NOT AT EVERY CALLER.
18
- //
19
- // The engine returns the points and commitments WITHOUT the 0x prefix (that is how www/js/atomic/halves.js stores
20
- // them), while the calldata encoder (www/js/evm/factory.js, word) REQUIRES the prefix and refuses "not a number
21
- // and not hex" on 64 digits without one. The page normalises them on its side (sdk/src/swap-flow.mjs, with0x)
22
- // for exactly this reason. The core must do the same ITSELF: otherwise every caller would repeat the
23
- // normalisation, and one forgetful one would break the signature with the encoder's raw error. The list is
24
- // exactly the fields encoded as bytes32 that do NOT enter the order context string (otherwise the normalisation
25
- // would drift from the proof, which is bound to the context): two half commitments and three ed25519 points.
26
- const HEX_POLICY_FIELDS = ["commitHalfLocker", "commitHalfClaimer", "edPointLocker", "edPointClaimer", "edViewPointLocker"];
27
- const with0x = (v) => (typeof v === "string" && v && !/^0x/i.test(v) ? "0x" + v : v);
28
-
29
- /**
30
- * The order terms in canonical form: points and commitments - with the 0x prefix. One form for the check and for
31
- * sending - otherwise one thing would be checked and another encoded.
32
- */
33
- export function canonicalTerms(request) {
34
- if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
35
- const out = { ...request };
36
- for (const key of HEX_POLICY_FIELDS) out[key] = with0x(out[key]);
37
- return out;
38
- }
39
-
40
- /**
41
- * The order terms for the contract. They are assembled by the engine, and its refusal (the deadlines the wrong
42
- * way, readyBy in the past) is turned into a CODE: texts are the interface's business, the core speaks in codes.
43
- */
44
- export function terms(request) {
45
- if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
46
- const need = ["locker", "claimer", "commitHalfLocker", "commitHalfClaimer", "edPointLocker", "edPointClaimer", "amount", "readyBy", "t1"];
47
- const missing = need.filter((k) => request[k] === undefined || request[k] === null || request[k] === "");
48
- if (missing.length) throw new SdkError("bad-input", { field: "terms", missing });
49
- try {
50
- return engine.escrow.orderTerms(canonicalTerms(request));
51
- } catch (error) {
52
- // We will not parse ANOTHER's text: we name what the contract's rules check, and only if the deadlines are
53
- // actually violated. Otherwise - a general refusal on the terms.
54
- const seconds = Math.floor(Date.now() / 1000);
55
- if (Number(request.readyBy) <= seconds) throw new SdkError("bad-input", { field: "readyBy", why: "not-in-future" });
56
- if (Number(request.t1) <= Number(request.readyBy)) throw new SdkError("bad-input", { field: "t1", why: "not-after-readyBy" });
57
- throw new SdkError("bad-input", { field: "terms" });
58
- }
59
- }
60
-
61
- // SANITISING THE REASON. The engine's messages carry no keys, but sanitising costs one line and covers the case
62
- // where the engine starts printing a value: 0x + 64 hex is a private key, and it never goes out.
63
- const sanitizeWhy = (value) =>
64
- String(value === undefined || value === null ? "" : value).replace(/0x[0-9a-fA-F]{64}/g, "0x<redacted>").slice(0, 200);
65
- // A NETWORK REFUSAL IS CALLED A NETWORK ONE, rather than being fitted to a data error: transport and order
66
- // validation are different things, and the caller needs an answer to "retry or fix".
67
- const TRANSPORT_WHY = /fetch failed|network|ECONN|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket|timeout|HTTP \d|status \d|non-ok/i;
68
- const transportish = (why) => TRANSPORT_WHY.test(why);
69
-
70
- /**
71
- * SEND THE LOCK. The deadlines and the composition are checked BEFORE sending (see `terms`), the chain work is
72
- * done by the engine (`fundOrder`), and the wallet arrives as an adapter: signing is possible only in it, and the
73
- * core keeps no path into the chain of its own.
74
- *
75
- * TWO THINGS ARE DELIBERATELY NOT OURS: reading from the chain is taken from the engine, and the escrow address -
76
- * FROM THE RECEIPT, not from a prediction. A prediction could diverge from what was actually created, and it
77
- * would diverge silently.
78
- */
79
- export async function send(request, wallet, deps = {}) {
80
- if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
81
- if (!wallet || typeof wallet.send !== "function" || typeof wallet.receipt !== "function") {
82
- throw new SdkError("bad-input", { field: "wallet", missing: ["send", "receipt"] });
83
- }
84
- // THE TERMS ARE BROUGHT TO CANONICAL FORM BEFORE EVERYTHING, and go in one form both into the check and into
85
- // sending.
86
- // THE DEADLINES - HERE, BEFORE ANY SENDING, and on the form funding expects (`amountWei`, not `amount`: the
87
- // order terms and the sending have different names for the same field, and they must not be confused).
88
- const canonical = canonicalTerms(request);
89
- terms({ ...canonical, amount: canonical.amountWei });
90
- const evm = engine.evm;
91
- // WHAT READS THE CHAIN IS ALSO A SEAM (as in actions.mjs): by default it is the engine's read, but the caller
92
- // can substitute its own. Without this capability the lock step could not be checked beyond predicting the
93
- // address - and then "the address from the receipt" would remain an argument on trust, not a verified property.
94
- const reader = typeof deps.call === "function" ? deps.call : evm.readContract;
95
- try {
96
- return await evm.fundOrder({
97
- ...canonical,
98
- call: reader,
99
- send: (tx) => wallet.send(tx),
100
- // NORMALISING THE RECEIPT ARGUMENT'S SHAPE - HERE, AT THE CORE'S BOUNDARY, NOT AT EVERY CALLER.
101
- //
102
- // www/js/evm/funding.js calls the receipt with an OBJECT { hash } (waitReceipt), while the adapter contract
103
- // declares receipt(txHash: string). A strict adapter (sdk/src/adapters/evm-wallet.mjs) answers bad-input to
104
- // an object, the refusal is SWALLOWED by the receipt wait, and the escrow address is silently replaced with
105
- // the PREDICTED one - that is, the "address from the receipt" check stops working without saying so. We
106
- // normalise the argument to a string here: funding.js is foreign code and must not be edited, and every
107
- // adapter should not repeat the normalisation.
108
- receipt: (arg) => wallet.receipt(typeof arg === "string" ? arg : (arg && typeof arg.hash === "string" ? arg.hash : arg)),
109
- });
110
- } catch (error) {
111
- // AN ALREADY-NAMED REASON PASSES AS IS: the adapter's and the reader's codes (wrong-chain, server-unavailable,
112
- // contract-reverted, bad-input) ARE the answer, and it must not be overridden.
113
- if (error && typeof error.code === "string") throw error;
114
- // THE REST USED TO HIDE BEHIND server-unavailable { step: "fund-order" }, and the real reason (the first live
115
- // run: "locker and claimer cannot coincide") never reached the caller. Now the reason is NAMED as a string
116
- // (briefly, without keys), and the code is chosen by it: transport - server-unavailable, order/data
117
- // validation - bad-input.
118
- const why = sanitizeWhy((error && error.message) || error);
119
- throw new SdkError(transportish(why) ? "server-unavailable" : "bad-input", { step: "fund-order", why });
120
- }
121
- }
122
-
123
- /**
124
- * The escrow factory address for a network: FROM THE NETWORK REGISTRY, by the same path as the rest of the core.
125
- * The registry is read through the deal-flow module (sdk/src/swap-flow.mjs, escrowFactoryAddress) - the core
126
- * keeps no SECOND table of addresses.
127
- *
128
- * PREVIOUSLY THE FUNCTION LOOKED IN THE WRONG MODULE (www/js/evm/escrow.js, which has no such function) and
129
- * ALWAYS returned null - that is, the seam stayed silent about the factory's absence, and the caller took the
130
- * address elsewhere. Now the absence of a factory for a network is a REFUSAL WITH A CODE naming the network, not
131
- * null.
132
- */
133
- export function factoryOf(chainSlug) {
134
- let address = null;
135
- try { address = flow.escrowFactoryAddress(chainSlug); } catch { address = null; }
136
- if (typeof address !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(address)) {
137
- throw new SdkError("bad-input", { field: "factory", chain: typeof chainSlug === "string" ? chainSlug : null });
138
- }
139
- return address;
140
- }
1
+ // LOCKING THE FUNDS: TERMS FIRST, THEN SENDING.
2
+ //
3
+ // The first half of the step is here - assembling and CHECKING the order terms BEFORE anything goes on-chain. The
4
+ // rules are not ours: the contract sets them, and the engine already checks them (`www/js/evm/escrow.js`,
5
+ // `orderTerms`). The core will not hold a copy of the rules: a copy would drift from the contract right where the
6
+ // error shows up only as a rejected transaction.
7
+ //
8
+ // WHAT IS HERE: both the assembly of the terms and the sending itself - it goes through the engine's EVM layer
9
+ // (`fundOrder`), and the EVM wallet (the `send`/`receipt` role) comes from outside. What reads the chain is also a
10
+ // seam: by default the engine's read, but the caller can substitute its own (as in actions.mjs).
11
+ import { SdkError } from "./errors.mjs";
12
+ import * as engine from "./engine.mjs";
13
+ // THE DEAL FLOW IS A PACKAGE MODULE NOW (#32, wave 3): the registry address is read through it, exactly as
14
+ // the actions read the order flow.
15
+ import * as flow from "./swap-flow.mjs";
16
+
17
+ // THE ORDER'S WORD-ADDRESSES ARE BROUGHT TO 0x HERE, AT THE BOUNDARY, NOT AT EVERY CALLER.
18
+ //
19
+ // The engine returns the points and commitments WITHOUT the 0x prefix (that is how www/js/atomic/halves.js stores
20
+ // them), while the calldata encoder (www/js/evm/factory.js, word) REQUIRES the prefix and refuses "not a number
21
+ // and not hex" on 64 digits without one. The page normalises them on its side (sdk/src/swap-flow.mjs, with0x)
22
+ // for exactly this reason. The core must do the same ITSELF: otherwise every caller would repeat the
23
+ // normalisation, and one forgetful one would break the signature with the encoder's raw error. The list is
24
+ // exactly the fields encoded as bytes32 that do NOT enter the order context string (otherwise the normalisation
25
+ // would drift from the proof, which is bound to the context): two half commitments and three ed25519 points.
26
+ const HEX_POLICY_FIELDS = ["commitHalfLocker", "commitHalfClaimer", "edPointLocker", "edPointClaimer", "edViewPointLocker"];
27
+ const with0x = (v) => (typeof v === "string" && v && !/^0x/i.test(v) ? "0x" + v : v);
28
+
29
+ /**
30
+ * The order terms in canonical form: points and commitments - with the 0x prefix. One form for the check and for
31
+ * sending - otherwise one thing would be checked and another encoded.
32
+ */
33
+ export function canonicalTerms(request) {
34
+ if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
35
+ const out = { ...request };
36
+ for (const key of HEX_POLICY_FIELDS) out[key] = with0x(out[key]);
37
+ return out;
38
+ }
39
+
40
+ /**
41
+ * The order terms for the contract. They are assembled by the engine, and its refusal (the deadlines the wrong
42
+ * way, readyBy in the past) is turned into a CODE: texts are the interface's business, the core speaks in codes.
43
+ */
44
+ export function terms(request) {
45
+ if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
46
+ const need = ["locker", "claimer", "commitHalfLocker", "commitHalfClaimer", "edPointLocker", "edPointClaimer", "amount", "readyBy", "t1"];
47
+ const missing = need.filter((k) => request[k] === undefined || request[k] === null || request[k] === "");
48
+ if (missing.length) throw new SdkError("bad-input", { field: "terms", missing });
49
+ try {
50
+ return engine.escrow.orderTerms(canonicalTerms(request));
51
+ } catch (error) {
52
+ // We will not parse ANOTHER's text: we name what the contract's rules check, and only if the deadlines are
53
+ // actually violated. Otherwise - a general refusal on the terms.
54
+ const seconds = Math.floor(Date.now() / 1000);
55
+ if (Number(request.readyBy) <= seconds) throw new SdkError("bad-input", { field: "readyBy", why: "not-in-future" });
56
+ if (Number(request.t1) <= Number(request.readyBy)) throw new SdkError("bad-input", { field: "t1", why: "not-after-readyBy" });
57
+ throw new SdkError("bad-input", { field: "terms" });
58
+ }
59
+ }
60
+
61
+ // SANITISING THE REASON. The engine's messages carry no keys, but sanitising costs one line and covers the case
62
+ // where the engine starts printing a value: 0x + 64 hex is a private key, and it never goes out.
63
+ const sanitizeWhy = (value) =>
64
+ String(value === undefined || value === null ? "" : value).replace(/0x[0-9a-fA-F]{64}/g, "0x<redacted>").slice(0, 200);
65
+ // A NETWORK REFUSAL IS CALLED A NETWORK ONE, rather than being fitted to a data error: transport and order
66
+ // validation are different things, and the caller needs an answer to "retry or fix".
67
+ const TRANSPORT_WHY = /fetch failed|network|ECONN|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket|timeout|HTTP \d|status \d|non-ok/i;
68
+ const transportish = (why) => TRANSPORT_WHY.test(why);
69
+
70
+ /**
71
+ * SEND THE LOCK. The deadlines and the composition are checked BEFORE sending (see `terms`), the chain work is
72
+ * done by the engine (`fundOrder`), and the wallet arrives as an adapter: signing is possible only in it, and the
73
+ * core keeps no path into the chain of its own.
74
+ *
75
+ * TWO THINGS ARE DELIBERATELY NOT OURS: reading from the chain is taken from the engine, and the escrow address -
76
+ * FROM THE RECEIPT, not from a prediction. A prediction could diverge from what was actually created, and it
77
+ * would diverge silently.
78
+ */
79
+ export async function send(request, wallet, deps = {}) {
80
+ if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
81
+ if (!wallet || typeof wallet.send !== "function" || typeof wallet.receipt !== "function") {
82
+ throw new SdkError("bad-input", { field: "wallet", missing: ["send", "receipt"] });
83
+ }
84
+ // THE TERMS ARE BROUGHT TO CANONICAL FORM BEFORE EVERYTHING, and go in one form both into the check and into
85
+ // sending.
86
+ // THE DEADLINES - HERE, BEFORE ANY SENDING, and on the form funding expects (`amountWei`, not `amount`: the
87
+ // order terms and the sending have different names for the same field, and they must not be confused).
88
+ const canonical = canonicalTerms(request);
89
+ terms({ ...canonical, amount: canonical.amountWei });
90
+ const evm = engine.evm;
91
+ // WHAT READS THE CHAIN IS ALSO A SEAM (as in actions.mjs): by default it is the engine's read, but the caller
92
+ // can substitute its own. Without this capability the lock step could not be checked beyond predicting the
93
+ // address - and then "the address from the receipt" would remain an argument on trust, not a verified property.
94
+ const reader = typeof deps.call === "function" ? deps.call : evm.readContract;
95
+ try {
96
+ return await evm.fundOrder({
97
+ ...canonical,
98
+ call: reader,
99
+ send: (tx) => wallet.send(tx),
100
+ // NORMALISING THE RECEIPT ARGUMENT'S SHAPE - HERE, AT THE CORE'S BOUNDARY, NOT AT EVERY CALLER.
101
+ //
102
+ // www/js/evm/funding.js calls the receipt with an OBJECT { hash } (waitReceipt), while the adapter contract
103
+ // declares receipt(txHash: string). A strict adapter (sdk/src/adapters/evm-wallet.mjs) answers bad-input to
104
+ // an object, the refusal is SWALLOWED by the receipt wait, and the escrow address is silently replaced with
105
+ // the PREDICTED one - that is, the "address from the receipt" check stops working without saying so. We
106
+ // normalise the argument to a string here: funding.js is foreign code and must not be edited, and every
107
+ // adapter should not repeat the normalisation.
108
+ receipt: (arg) => wallet.receipt(typeof arg === "string" ? arg : (arg && typeof arg.hash === "string" ? arg.hash : arg)),
109
+ });
110
+ } catch (error) {
111
+ // AN ALREADY-NAMED REASON PASSES AS IS: the adapter's and the reader's codes (wrong-chain, server-unavailable,
112
+ // contract-reverted, bad-input) ARE the answer, and it must not be overridden.
113
+ if (error && typeof error.code === "string") throw error;
114
+ // THE REST USED TO HIDE BEHIND server-unavailable { step: "fund-order" }, and the real reason (the first live
115
+ // run: "locker and claimer cannot coincide") never reached the caller. Now the reason is NAMED as a string
116
+ // (briefly, without keys), and the code is chosen by it: transport - server-unavailable, order/data
117
+ // validation - bad-input.
118
+ const why = sanitizeWhy((error && error.message) || error);
119
+ throw new SdkError(transportish(why) ? "server-unavailable" : "bad-input", { step: "fund-order", why });
120
+ }
121
+ }
122
+
123
+ /**
124
+ * The escrow factory address for a network: FROM THE NETWORK REGISTRY, by the same path as the rest of the core.
125
+ * The registry is read through the deal-flow module (sdk/src/swap-flow.mjs, escrowFactoryAddress) - the core
126
+ * keeps no SECOND table of addresses.
127
+ *
128
+ * PREVIOUSLY THE FUNCTION LOOKED IN THE WRONG MODULE (www/js/evm/escrow.js, which has no such function) and
129
+ * ALWAYS returned null - that is, the seam stayed silent about the factory's absence, and the caller took the
130
+ * address elsewhere. Now the absence of a factory for a network is a REFUSAL WITH A CODE naming the network, not
131
+ * null.
132
+ */
133
+ export function factoryOf(chainSlug) {
134
+ let address = null;
135
+ try { address = flow.escrowFactoryAddress(chainSlug); } catch { address = null; }
136
+ if (typeof address !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(address)) {
137
+ throw new SdkError("bad-input", { field: "factory", chain: typeof chainSlug === "string" ? chainSlug : null });
138
+ }
139
+ return address;
140
+ }