@xochi/client 0.0.0-stage → 0.1.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -2
  4. package/dist/cjs/claim.d.ts +33 -0
  5. package/dist/cjs/claim.js +44 -0
  6. package/dist/cjs/fee.d.ts +247 -0
  7. package/dist/cjs/fee.js +295 -0
  8. package/dist/cjs/index.d.ts +18 -0
  9. package/dist/cjs/index.js +33 -0
  10. package/dist/cjs/machine/claimInfo.d.ts +29 -0
  11. package/dist/cjs/machine/claimInfo.js +26 -0
  12. package/dist/cjs/machine/decisions.d.ts +452 -0
  13. package/dist/cjs/machine/decisions.js +665 -0
  14. package/dist/cjs/machine/deposit.d.ts +113 -0
  15. package/dist/cjs/machine/deposit.js +265 -0
  16. package/dist/cjs/machine/index.d.ts +23 -0
  17. package/dist/cjs/machine/index.js +53 -0
  18. package/dist/cjs/machine/machine.d.ts +409 -0
  19. package/dist/cjs/machine/machine.js +1615 -0
  20. package/dist/cjs/machine/phase.d.ts +24 -0
  21. package/dist/cjs/machine/phase.js +45 -0
  22. package/dist/cjs/machine/quoteChecks.d.ts +41 -0
  23. package/dist/cjs/machine/quoteChecks.js +82 -0
  24. package/dist/cjs/machine/receipt.d.ts +151 -0
  25. package/dist/cjs/machine/receipt.js +69 -0
  26. package/dist/cjs/machine/recipientEcho.d.ts +16 -0
  27. package/dist/cjs/machine/recipientEcho.js +27 -0
  28. package/dist/cjs/machine/solverPin.d.ts +110 -0
  29. package/dist/cjs/machine/solverPin.js +214 -0
  30. package/dist/cjs/machine/state.d.ts +167 -0
  31. package/dist/cjs/machine/state.js +31 -0
  32. package/dist/cjs/machine/transition.d.ts +15 -0
  33. package/dist/cjs/machine/transition.js +20 -0
  34. package/dist/cjs/machine/validateRequest.d.ts +15 -0
  35. package/dist/cjs/machine/validateRequest.js +127 -0
  36. package/dist/cjs/machine/view.d.ts +3 -0
  37. package/dist/cjs/machine/view.js +74 -0
  38. package/dist/cjs/machine/wire.d.ts +527 -0
  39. package/dist/cjs/machine/wire.js +280 -0
  40. package/dist/cjs/package.json +1 -0
  41. package/dist/cjs/shared/api.d.ts +9 -0
  42. package/dist/cjs/shared/api.js +5 -0
  43. package/dist/cjs/shared/chains.d.ts +243 -0
  44. package/dist/cjs/shared/chains.js +250 -0
  45. package/dist/cjs/shared/eip712.d.ts +44 -0
  46. package/dist/cjs/shared/eip712.js +64 -0
  47. package/dist/cjs/shared/index.d.ts +15 -0
  48. package/dist/cjs/shared/index.js +31 -0
  49. package/dist/cjs/shared/intentStream.d.ts +74 -0
  50. package/dist/cjs/shared/intentStream.js +59 -0
  51. package/dist/cjs/shared/intents.d.ts +88 -0
  52. package/dist/cjs/shared/intents.js +227 -0
  53. package/dist/cjs/shared/schemas.d.ts +928 -0
  54. package/dist/cjs/shared/schemas.js +524 -0
  55. package/dist/cjs/shared/stealth.d.ts +53 -0
  56. package/dist/cjs/shared/stealth.js +68 -0
  57. package/dist/cjs/shared/trading.d.ts +153 -0
  58. package/dist/cjs/shared/trading.js +185 -0
  59. package/dist/cjs/shared/types.d.ts +37 -0
  60. package/dist/cjs/shared/types.js +7 -0
  61. package/dist/cjs/shared/validators.d.ts +30 -0
  62. package/dist/cjs/shared/validators.js +79 -0
  63. package/dist/cjs/transport/client.d.ts +234 -0
  64. package/dist/cjs/transport/client.js +1456 -0
  65. package/dist/esm/claim.d.ts +33 -0
  66. package/dist/esm/claim.js +41 -0
  67. package/dist/esm/fee.d.ts +247 -0
  68. package/dist/esm/fee.js +285 -0
  69. package/dist/esm/index.d.ts +18 -0
  70. package/dist/esm/index.js +17 -0
  71. package/dist/esm/machine/claimInfo.d.ts +29 -0
  72. package/dist/esm/machine/claimInfo.js +23 -0
  73. package/dist/esm/machine/decisions.d.ts +452 -0
  74. package/dist/esm/machine/decisions.js +640 -0
  75. package/dist/esm/machine/deposit.d.ts +113 -0
  76. package/dist/esm/machine/deposit.js +260 -0
  77. package/dist/esm/machine/index.d.ts +23 -0
  78. package/dist/esm/machine/index.js +23 -0
  79. package/dist/esm/machine/machine.d.ts +409 -0
  80. package/dist/esm/machine/machine.js +1609 -0
  81. package/dist/esm/machine/phase.d.ts +24 -0
  82. package/dist/esm/machine/phase.js +42 -0
  83. package/dist/esm/machine/quoteChecks.d.ts +41 -0
  84. package/dist/esm/machine/quoteChecks.js +78 -0
  85. package/dist/esm/machine/receipt.d.ts +151 -0
  86. package/dist/esm/machine/receipt.js +64 -0
  87. package/dist/esm/machine/recipientEcho.d.ts +16 -0
  88. package/dist/esm/machine/recipientEcho.js +24 -0
  89. package/dist/esm/machine/solverPin.d.ts +110 -0
  90. package/dist/esm/machine/solverPin.js +210 -0
  91. package/dist/esm/machine/state.d.ts +167 -0
  92. package/dist/esm/machine/state.js +28 -0
  93. package/dist/esm/machine/transition.d.ts +15 -0
  94. package/dist/esm/machine/transition.js +17 -0
  95. package/dist/esm/machine/validateRequest.d.ts +15 -0
  96. package/dist/esm/machine/validateRequest.js +123 -0
  97. package/dist/esm/machine/view.d.ts +3 -0
  98. package/dist/esm/machine/view.js +71 -0
  99. package/dist/esm/machine/wire.d.ts +527 -0
  100. package/dist/esm/machine/wire.js +266 -0
  101. package/dist/esm/shared/api.d.ts +9 -0
  102. package/dist/esm/shared/api.js +4 -0
  103. package/dist/esm/shared/chains.d.ts +243 -0
  104. package/dist/esm/shared/chains.js +236 -0
  105. package/dist/esm/shared/eip712.d.ts +44 -0
  106. package/dist/esm/shared/eip712.js +61 -0
  107. package/dist/esm/shared/index.d.ts +15 -0
  108. package/dist/esm/shared/index.js +15 -0
  109. package/dist/esm/shared/intentStream.d.ts +74 -0
  110. package/dist/esm/shared/intentStream.js +55 -0
  111. package/dist/esm/shared/intents.d.ts +88 -0
  112. package/dist/esm/shared/intents.js +213 -0
  113. package/dist/esm/shared/schemas.d.ts +928 -0
  114. package/dist/esm/shared/schemas.js +521 -0
  115. package/dist/esm/shared/stealth.d.ts +53 -0
  116. package/dist/esm/shared/stealth.js +64 -0
  117. package/dist/esm/shared/trading.d.ts +153 -0
  118. package/dist/esm/shared/trading.js +182 -0
  119. package/dist/esm/shared/types.d.ts +37 -0
  120. package/dist/esm/shared/types.js +6 -0
  121. package/dist/esm/shared/validators.d.ts +30 -0
  122. package/dist/esm/shared/validators.js +74 -0
  123. package/dist/esm/transport/client.d.ts +234 -0
  124. package/dist/esm/transport/client.js +1447 -0
  125. package/package.json +61 -4
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Deposit-route (Tron and other non-EVM origin) binding: the shapes the machine
3
+ * stores, and the checks it runs at quote ingest and on resume.
4
+ *
5
+ * The attestation itself -- the v3 message, its EIP-191 recovery against the
6
+ * pinned signer, the request binding, the deposit window and the
7
+ * destination-token guard -- is `@xochi/signer`'s. This module adapts it to
8
+ * the machine: it assembles the request side, carries the caller's pinned
9
+ * signer, keeps the per-session address-reuse tripwire as plain data on the
10
+ * model, and maps the signer's error classes to `DepositAttestationResult`.
11
+ *
12
+ * There is no clock parameter anywhere here, on purpose: the signer bounds the
13
+ * deposit window against the wall clock, and an injectable `now` on a security
14
+ * assertion is a bypass wearing a test affordance's name.
15
+ */
16
+ import { recipientIsDestinationToken, type DepositAttestationFields } from "@xochi/signer";
17
+ import type { QuoteRequest, QuoteResponse } from "./wire.js";
18
+ export { recipientIsDestinationToken, type DepositAttestationFields };
19
+ /**
20
+ * `kind` names the rejections a caller must treat differently, and only those.
21
+ *
22
+ * `addressReuse` is the one the user can act on: it is the per-session tripwire
23
+ * on `Model.seenDepositAddresses`, which cannot tell a genuine collision from
24
+ * Riddler re-issuing an address to a re-quote, and which a reload clears.
25
+ * Everything else is `unavailable`, where retrying is the right advice and the
26
+ * detail belongs in the log rather than on screen.
27
+ */
28
+ export type DepositAttestationResult = {
29
+ ok: true;
30
+ } | {
31
+ ok: false;
32
+ kind: "unavailable" | "addressReuse";
33
+ reason: string;
34
+ };
35
+ /**
36
+ * A deposit binding as persisted alongside a swap record, and the signature over
37
+ * it. `attested` is the signer's own field set, exactly as it was verified.
38
+ *
39
+ * The FIELDS are stored verbatim rather than re-derived on read. Re-deriving
40
+ * would mean the values displayed and the values verified are built by two
41
+ * different code paths, which is precisely how a screen comes to show one
42
+ * address while a check passes over another.
43
+ */
44
+ export interface StoredDepositAttestation {
45
+ attested: DepositAttestationFields;
46
+ attestation: string;
47
+ }
48
+ /**
49
+ * `verifyStoredDeposit`'s answer. On success it carries the binding it PROVED,
50
+ * in the signer's field set, and that object is the one to render and persist:
51
+ * a record written by an older build arrives in a different shape (see
52
+ * `STORED_FIELDS`), so the caller's own copy is not what was verified.
53
+ */
54
+ export type StoredDepositResult = {
55
+ ok: true;
56
+ stored: StoredDepositAttestation;
57
+ } | Extract<DepositAttestationResult, {
58
+ ok: false;
59
+ }>;
60
+ export type DepositQuoteCheck = {
61
+ ok: true;
62
+ stored: StoredDepositAttestation;
63
+ seen: Readonly<Record<string, string>>;
64
+ } | Extract<DepositAttestationResult, {
65
+ ok: false;
66
+ }>;
67
+ /**
68
+ * Verify a served deposit route against the request that produced it.
69
+ *
70
+ * Runs, in order: the signature-shape gate above; `depositAttestationFields`
71
+ * (the request half of the message is rebuilt from `request`, the server half
72
+ * from the quote, with the requested `from_amount` because the Worker envelope
73
+ * carries none on an exact-input route); `verifyDepositAttestation` against the
74
+ * caller-pinned `signer`; `assertDepositQuoteMatchesRequest` (amount, displayed
75
+ * recipient, destination-token payout, attested deposit window); and finally
76
+ * `recordDepositAddress` against `seen`, only once the signature has verified so
77
+ * a forged quote cannot poison the record.
78
+ *
79
+ * Returns the next `seen` map rather than mutating the one passed in, so the
80
+ * model stays plain data.
81
+ */
82
+ export declare function checkDepositQuote(request: QuoteRequest, quote: QuoteResponse, signer: string | null, seen: Readonly<Record<string, string>>): DepositQuoteCheck;
83
+ /**
84
+ * Prove a deposit binding restored from storage, before any of it is shown.
85
+ *
86
+ * A resumed swap reads its deposit address out of host storage, which is not a
87
+ * trust boundary. Rendering an address from there, having never checked it,
88
+ * hands away the invariant at the one point the user is about to move funds by
89
+ * hand. TWO independent things are checked, and both are necessary:
90
+ *
91
+ * 1. The BINDING to the record it was stored under. An attacker can obtain a
92
+ * genuine attestation for their own deposit quote and transplant it into a
93
+ * victim's record; every field is internally consistent and the signature
94
+ * verifies. What it cannot fake is whose deal it is: v3 binds `wallet` and
95
+ * the record is filed under its owner, and `intent_id` must match the record.
96
+ * 2. The SIGNATURE, by `verifyDepositAttestation` against the pinned signer,
97
+ * plus the destination-token guard the live path runs.
98
+ *
99
+ * Takes the record as `unknown` because that is what storage hands back. A
100
+ * record persisted before the cutover to `@xochi/signer` (camelCase fields,
101
+ * `resolvedSettlementType`) is renamed first and then checked exactly like a
102
+ * current one; any other shape refuses. On success the result carries the
103
+ * proven binding in the current shape: render THAT, never the record passed
104
+ * in, and write it back if the record is re-persisted.
105
+ *
106
+ * NOT checked here: the deposit window. A resumed swap legitimately comes back
107
+ * after it closed, and the honest view then names no address to send to; the
108
+ * caller reads `attested.deposit_deadline` and renders accordingly.
109
+ */
110
+ export declare function verifyStoredDeposit(raw: unknown, signer: string | null, expected: {
111
+ intentId: string;
112
+ owner: string;
113
+ }): StoredDepositResult;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Deposit-route (Tron and other non-EVM origin) binding: the shapes the machine
3
+ * stores, and the checks it runs at quote ingest and on resume.
4
+ *
5
+ * The attestation itself -- the v3 message, its EIP-191 recovery against the
6
+ * pinned signer, the request binding, the deposit window and the
7
+ * destination-token guard -- is `@xochi/signer`'s. This module adapts it to
8
+ * the machine: it assembles the request side, carries the caller's pinned
9
+ * signer, keeps the per-session address-reuse tripwire as plain data on the
10
+ * model, and maps the signer's error classes to `DepositAttestationResult`.
11
+ *
12
+ * There is no clock parameter anywhere here, on purpose: the signer bounds the
13
+ * deposit window against the wall clock, and an injectable `now` on a security
14
+ * assertion is a bypass wearing a test affordance's name.
15
+ */
16
+ import { DepositAttestationError, DepositQuoteMismatchError, assertDepositQuoteMatchesRequest, depositAttestationFields, recipientIsDestinationToken, recordDepositAddress, verifyDepositAttestation, } from "@xochi/signer";
17
+ import { quoteRequestParams } from "./quoteChecks.js";
18
+ export { recipientIsDestinationToken };
19
+ /**
20
+ * Every signed field: its spelling in records persisted by builds before the
21
+ * cutover to `@xochi/signer` (xochi `src/lib/depositAttestation.ts`, the
22
+ * widget's `packages/machine`), and the JSON type it must have.
23
+ *
24
+ * Those builds stored camelCase `DepositAttestationFields` and named the
25
+ * settlement field `resolvedSettlementType`; the signer names it
26
+ * `settlement_preference`. Same eighteen-line v3 message, same values, so a
27
+ * renamed record signs the same bytes. Keyed by the signer's type so a field
28
+ * added there without a row here fails to compile.
29
+ *
30
+ * `optional` fields were `undefined` in the old type, which `JSON.stringify`
31
+ * drops, so an absent key is how an old record says "empty line". Absent and
32
+ * `null` both become `null`, which signs the same empty line the signer writes
33
+ * for either.
34
+ */
35
+ const STORED_FIELDS = {
36
+ intent_id: { legacy: "intentId", type: "string" },
37
+ quote_id: { legacy: "quoteId", type: "string" },
38
+ wallet: { legacy: "wallet", type: "string" },
39
+ from_chain_id: { legacy: "fromChainId", type: "integer" },
40
+ from_token: { legacy: "fromToken", type: "string" },
41
+ from_amount: { legacy: "fromAmount", type: "string" },
42
+ deposit_address: { legacy: "depositAddress", type: "string" },
43
+ deposit_deadline: { legacy: "depositDeadline", type: "optionalInteger" },
44
+ refund_address: { legacy: "refundAddress", type: "optionalString" },
45
+ to_chain_id: { legacy: "toChainId", type: "integer" },
46
+ to_token: { legacy: "toToken", type: "string" },
47
+ to_amount: { legacy: "toAmount", type: "string" },
48
+ min_to_amount: { legacy: "minToAmount", type: "string" },
49
+ recipient: { legacy: "recipient", type: "string" },
50
+ stealth_spending_pub_key: { legacy: "stealthSpendingPubKey", type: "optionalString" },
51
+ stealth_viewing_pub_key: { legacy: "stealthViewingPubKey", type: "optionalString" },
52
+ settlement_preference: { legacy: "resolvedSettlementType", type: "string" },
53
+ };
54
+ /**
55
+ * Read a persisted binding into the signer's field set, renaming a pre-cutover
56
+ * camelCase record on the way. Pure, and decides SHAPE only: whatever comes out
57
+ * still goes through every check `verifyStoredDeposit` runs, so a migrated
58
+ * record is trusted exactly as far as a current one, which is not at all until
59
+ * its signature recovers.
60
+ *
61
+ * A record in neither shape, one missing a required field, or one with a field
62
+ * of the wrong type is refused rather than repaired. Only the signed fields are
63
+ * copied, so nothing unsigned rides along into what the caller renders.
64
+ */
65
+ function readStoredDeposit(raw) {
66
+ if (typeof raw !== "object" ||
67
+ raw === null ||
68
+ !("attestation" in raw) ||
69
+ typeof raw.attestation !== "string" ||
70
+ !raw.attestation) {
71
+ return { ok: false, reason: "stored deposit carries no attestation" };
72
+ }
73
+ const { attestation } = raw;
74
+ if (!("attested" in raw) || typeof raw.attested !== "object" || raw.attested === null) {
75
+ return { ok: false, reason: "stored deposit carries no attested fields" };
76
+ }
77
+ const source = raw.attested;
78
+ const legacy = !("intent_id" in source) && "intentId" in source;
79
+ if (!legacy && !("intent_id" in source)) {
80
+ return { ok: false, reason: "stored deposit is in no recognised shape" };
81
+ }
82
+ const fields = {};
83
+ for (const name of Object.keys(STORED_FIELDS)) {
84
+ const { legacy: legacyName, type } = STORED_FIELDS[name];
85
+ const key = legacy ? legacyName : name;
86
+ const value = key in source ? Reflect.get(source, key) : undefined;
87
+ const optional = type === "optionalString" || type === "optionalInteger";
88
+ if (optional && (value === undefined || value === null)) {
89
+ fields[name] = null;
90
+ }
91
+ else if ((type === "string" || type === "optionalString") && typeof value === "string") {
92
+ fields[name] = value;
93
+ }
94
+ else if ((type === "integer" || type === "optionalInteger") &&
95
+ typeof value === "number" &&
96
+ Number.isSafeInteger(value)) {
97
+ fields[name] = value;
98
+ }
99
+ else {
100
+ return {
101
+ ok: false,
102
+ reason: `stored deposit field ${key} is ${value === undefined ? "missing" : "malformed"}`,
103
+ };
104
+ }
105
+ }
106
+ // Every key of STORED_FIELDS (which is keyed by the signer's type) was
107
+ // assigned above with the JSON type its row names; the loop cannot tell
108
+ // the compiler which row is which, so the per-field types are asserted here.
109
+ const attested = fields;
110
+ return { ok: true, stored: { attested, attestation } };
111
+ }
112
+ /**
113
+ * 0x + 65-byte secp256k1 signature. Stricter than the signer, which also
114
+ * recovers a 64-byte EIP-2098 compact signature; the solver only ever serves
115
+ * the 65-byte form, so anything else is refused before recovery.
116
+ */
117
+ const SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;
118
+ /**
119
+ * Verify a served deposit route against the request that produced it.
120
+ *
121
+ * Runs, in order: the signature-shape gate above; `depositAttestationFields`
122
+ * (the request half of the message is rebuilt from `request`, the server half
123
+ * from the quote, with the requested `from_amount` because the Worker envelope
124
+ * carries none on an exact-input route); `verifyDepositAttestation` against the
125
+ * caller-pinned `signer`; `assertDepositQuoteMatchesRequest` (amount, displayed
126
+ * recipient, destination-token payout, attested deposit window); and finally
127
+ * `recordDepositAddress` against `seen`, only once the signature has verified so
128
+ * a forged quote cannot poison the record.
129
+ *
130
+ * Returns the next `seen` map rather than mutating the one passed in, so the
131
+ * model stays plain data.
132
+ */
133
+ export function checkDepositQuote(request, quote, signer, seen) {
134
+ const attestation = quote.depositAttestation;
135
+ if (attestation && !SIGNATURE_RE.test(attestation)) {
136
+ return { ok: false, kind: "unavailable", reason: "malformed deposit attestation signature" };
137
+ }
138
+ const params = quoteRequestParams(request);
139
+ let fields;
140
+ try {
141
+ fields = depositAttestationFields({
142
+ intent_id: quote.intentId,
143
+ quote_id: quote.quoteId,
144
+ from_amount: request.fromAmount,
145
+ deposit_address: quote.depositAddress,
146
+ deposit_deadline: quote.depositDeadline,
147
+ to_amount: quote.toAmount,
148
+ min_to_amount: quote.minToAmount,
149
+ }, params);
150
+ verifyDepositAttestation(fields, attestation, signer);
151
+ // `escrow` is the raw served envelope: the signer refuses any deposit quote
152
+ // that carries one, since this client runs no escrow binding.
153
+ assertDepositQuoteMatchesRequest(params, fields, {
154
+ recipient_address: quote.recipientAddress,
155
+ escrow: quote.escrow,
156
+ });
157
+ }
158
+ catch (err) {
159
+ return depositRefusal(err);
160
+ }
161
+ // Only AFTER the signature verifies: recording an unverified address would let
162
+ // a forged quote poison the record and lock the user out of a legitimate one.
163
+ const next = { ...seen };
164
+ try {
165
+ recordDepositAddress({ get: (key) => next[key], set: (key, intentId) => void (next[key] = intentId) }, fields.deposit_address, fields.intent_id);
166
+ }
167
+ catch (err) {
168
+ if (err instanceof DepositQuoteMismatchError) {
169
+ return { ok: false, kind: "addressReuse", reason: err.message };
170
+ }
171
+ return depositRefusal(err);
172
+ }
173
+ // `verifyDepositAttestation` threw on an absent attestation, so it is a string here.
174
+ return { ok: true, stored: { attested: fields, attestation: attestation }, seen: next };
175
+ }
176
+ /**
177
+ * Map the signer's error classes onto the machine's refusal shape: every reason
178
+ * not to send funds is `unavailable`. Anything that is not one of the signer's
179
+ * classes still refuses (fail closed) and says so.
180
+ */
181
+ function depositRefusal(err) {
182
+ const reason = err instanceof Error ? err.message : String(err);
183
+ if (err instanceof DepositAttestationError)
184
+ return { ok: false, kind: "unavailable", reason };
185
+ return {
186
+ ok: false,
187
+ kind: "unavailable",
188
+ reason: `deposit verification failed unexpectedly: ${reason}`,
189
+ };
190
+ }
191
+ /**
192
+ * Prove a deposit binding restored from storage, before any of it is shown.
193
+ *
194
+ * A resumed swap reads its deposit address out of host storage, which is not a
195
+ * trust boundary. Rendering an address from there, having never checked it,
196
+ * hands away the invariant at the one point the user is about to move funds by
197
+ * hand. TWO independent things are checked, and both are necessary:
198
+ *
199
+ * 1. The BINDING to the record it was stored under. An attacker can obtain a
200
+ * genuine attestation for their own deposit quote and transplant it into a
201
+ * victim's record; every field is internally consistent and the signature
202
+ * verifies. What it cannot fake is whose deal it is: v3 binds `wallet` and
203
+ * the record is filed under its owner, and `intent_id` must match the record.
204
+ * 2. The SIGNATURE, by `verifyDepositAttestation` against the pinned signer,
205
+ * plus the destination-token guard the live path runs.
206
+ *
207
+ * Takes the record as `unknown` because that is what storage hands back. A
208
+ * record persisted before the cutover to `@xochi/signer` (camelCase fields,
209
+ * `resolvedSettlementType`) is renamed first and then checked exactly like a
210
+ * current one; any other shape refuses. On success the result carries the
211
+ * proven binding in the current shape: render THAT, never the record passed
212
+ * in, and write it back if the record is re-persisted.
213
+ *
214
+ * NOT checked here: the deposit window. A resumed swap legitimately comes back
215
+ * after it closed, and the honest view then names no address to send to; the
216
+ * caller reads `attested.deposit_deadline` and renders accordingly.
217
+ */
218
+ export function verifyStoredDeposit(raw, signer, expected) {
219
+ const read = readStoredDeposit(raw);
220
+ if (!read.ok)
221
+ return { ok: false, kind: "unavailable", reason: read.reason };
222
+ const { stored } = read;
223
+ const { attested } = stored;
224
+ if (attested.intent_id !== expected.intentId) {
225
+ return {
226
+ ok: false,
227
+ kind: "unavailable",
228
+ reason: `stored deposit attests intent ${attested.intent_id}, record is ${expected.intentId}`,
229
+ };
230
+ }
231
+ if (!expected.owner || canonical(attested.wallet) !== canonical(expected.owner)) {
232
+ return {
233
+ ok: false,
234
+ kind: "unavailable",
235
+ reason: `stored deposit attests wallet ${attested.wallet}, record is filed under ` +
236
+ `${expected.owner || "(none)"}; refusing a binding for a different account`,
237
+ };
238
+ }
239
+ if (!SIGNATURE_RE.test(stored.attestation)) {
240
+ return { ok: false, kind: "unavailable", reason: "malformed deposit attestation signature" };
241
+ }
242
+ if (recipientIsDestinationToken(attested.recipient, attested.to_token)) {
243
+ return {
244
+ ok: false,
245
+ kind: "unavailable",
246
+ reason: `payout recipient is the destination token contract (${attested.to_token}); funds sent there are unspendable`,
247
+ };
248
+ }
249
+ try {
250
+ verifyDepositAttestation(attested, stored.attestation, signer);
251
+ return { ok: true, stored };
252
+ }
253
+ catch (err) {
254
+ return depositRefusal(err);
255
+ }
256
+ }
257
+ /** The message's own canonicalisation: a 0x address lowercased, base58 verbatim. */
258
+ function canonical(value) {
259
+ return value.startsWith("0x") ? value.toLowerCase() : value;
260
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The trade lifecycle as a pure state machine.
3
+ *
4
+ * update(model, msg) -> [model, effects] (machine.ts)
5
+ * view(model) -> Receipt (view.ts)
6
+ *
7
+ * Plus the wire decoders and the fund-safety gates the machine runs, exported
8
+ * so the widget and headless integrators share one implementation of each.
9
+ */
10
+ export { init, update, QUOTE_DEBOUNCE_MS, QUOTE_REFRESH_BUFFER_MS, RECONCILE_AFTER_ATTEMPTS, SIGN_PROMPT_TIMEOUT_MS, type Model, type Msg, type MsgBody, type Effect, type ApiCall, type WalletCall, type TimerCall, type TimerName, type MachineEvent, type ErrorReport, type Context, type WalletContext, type Outcome, type StealthMetaAddress, type MachineConfig, type QuoteFlight, type SignFlow, type PollState, type ExecuteAttempt, } from "./machine.js";
11
+ export { view } from "./view.js";
12
+ export { payoutOf, costOf, settlementDisclosure, type Receipt, type Served, type PayoutSlot, type CostSlot, type QuoteSlot, type SigningSlot, type SettlementDisclosure, } from "./receipt.js";
13
+ export { phaseFromStatus, type SwapPhase } from "./phase.js";
14
+ export * from "./decisions.js";
15
+ export * from "./wire.js";
16
+ export * from "./state.js";
17
+ export * from "./deposit.js";
18
+ export * from "./quoteChecks.js";
19
+ export * from "./claimInfo.js";
20
+ export * from "./recipientEcho.js";
21
+ export * from "./solverPin.js";
22
+ export { applyStatusTransition } from "./transition.js";
23
+ export { validateSwapParams, type SwapValidationResult } from "./validateRequest.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The trade lifecycle as a pure state machine.
3
+ *
4
+ * update(model, msg) -> [model, effects] (machine.ts)
5
+ * view(model) -> Receipt (view.ts)
6
+ *
7
+ * Plus the wire decoders and the fund-safety gates the machine runs, exported
8
+ * so the widget and headless integrators share one implementation of each.
9
+ */
10
+ export { init, update, QUOTE_DEBOUNCE_MS, QUOTE_REFRESH_BUFFER_MS, RECONCILE_AFTER_ATTEMPTS, SIGN_PROMPT_TIMEOUT_MS, } from "./machine.js";
11
+ export { view } from "./view.js";
12
+ export { payoutOf, costOf, settlementDisclosure, } from "./receipt.js";
13
+ export { phaseFromStatus } from "./phase.js";
14
+ export * from "./decisions.js";
15
+ export * from "./wire.js";
16
+ export * from "./state.js";
17
+ export * from "./deposit.js";
18
+ export * from "./quoteChecks.js";
19
+ export * from "./claimInfo.js";
20
+ export * from "./recipientEcho.js";
21
+ export * from "./solverPin.js";
22
+ export { applyStatusTransition } from "./transition.js";
23
+ export { validateSwapParams } from "./validateRequest.js";