@xochi/client 0.0.0-stage → 0.1.2

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 (129) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +120 -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 +411 -0
  19. package/dist/cjs/machine/machine.js +1627 -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 +16 -0
  48. package/dist/cjs/shared/index.js +32 -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/shieldedPreview.d.ts +841 -0
  56. package/dist/cjs/shared/shieldedPreview.js +128 -0
  57. package/dist/cjs/shared/stealth.d.ts +53 -0
  58. package/dist/cjs/shared/stealth.js +68 -0
  59. package/dist/cjs/shared/trading.d.ts +153 -0
  60. package/dist/cjs/shared/trading.js +185 -0
  61. package/dist/cjs/shared/types.d.ts +37 -0
  62. package/dist/cjs/shared/types.js +7 -0
  63. package/dist/cjs/shared/validators.d.ts +30 -0
  64. package/dist/cjs/shared/validators.js +79 -0
  65. package/dist/cjs/transport/client.d.ts +234 -0
  66. package/dist/cjs/transport/client.js +1456 -0
  67. package/dist/esm/claim.d.ts +33 -0
  68. package/dist/esm/claim.js +41 -0
  69. package/dist/esm/fee.d.ts +247 -0
  70. package/dist/esm/fee.js +285 -0
  71. package/dist/esm/index.d.ts +18 -0
  72. package/dist/esm/index.js +17 -0
  73. package/dist/esm/machine/claimInfo.d.ts +29 -0
  74. package/dist/esm/machine/claimInfo.js +23 -0
  75. package/dist/esm/machine/decisions.d.ts +452 -0
  76. package/dist/esm/machine/decisions.js +640 -0
  77. package/dist/esm/machine/deposit.d.ts +113 -0
  78. package/dist/esm/machine/deposit.js +260 -0
  79. package/dist/esm/machine/index.d.ts +23 -0
  80. package/dist/esm/machine/index.js +23 -0
  81. package/dist/esm/machine/machine.d.ts +411 -0
  82. package/dist/esm/machine/machine.js +1621 -0
  83. package/dist/esm/machine/phase.d.ts +24 -0
  84. package/dist/esm/machine/phase.js +42 -0
  85. package/dist/esm/machine/quoteChecks.d.ts +41 -0
  86. package/dist/esm/machine/quoteChecks.js +78 -0
  87. package/dist/esm/machine/receipt.d.ts +151 -0
  88. package/dist/esm/machine/receipt.js +64 -0
  89. package/dist/esm/machine/recipientEcho.d.ts +16 -0
  90. package/dist/esm/machine/recipientEcho.js +24 -0
  91. package/dist/esm/machine/solverPin.d.ts +110 -0
  92. package/dist/esm/machine/solverPin.js +210 -0
  93. package/dist/esm/machine/state.d.ts +167 -0
  94. package/dist/esm/machine/state.js +28 -0
  95. package/dist/esm/machine/transition.d.ts +15 -0
  96. package/dist/esm/machine/transition.js +17 -0
  97. package/dist/esm/machine/validateRequest.d.ts +15 -0
  98. package/dist/esm/machine/validateRequest.js +123 -0
  99. package/dist/esm/machine/view.d.ts +3 -0
  100. package/dist/esm/machine/view.js +71 -0
  101. package/dist/esm/machine/wire.d.ts +527 -0
  102. package/dist/esm/machine/wire.js +266 -0
  103. package/dist/esm/shared/api.d.ts +9 -0
  104. package/dist/esm/shared/api.js +4 -0
  105. package/dist/esm/shared/chains.d.ts +243 -0
  106. package/dist/esm/shared/chains.js +236 -0
  107. package/dist/esm/shared/eip712.d.ts +44 -0
  108. package/dist/esm/shared/eip712.js +61 -0
  109. package/dist/esm/shared/index.d.ts +16 -0
  110. package/dist/esm/shared/index.js +16 -0
  111. package/dist/esm/shared/intentStream.d.ts +74 -0
  112. package/dist/esm/shared/intentStream.js +55 -0
  113. package/dist/esm/shared/intents.d.ts +88 -0
  114. package/dist/esm/shared/intents.js +213 -0
  115. package/dist/esm/shared/schemas.d.ts +928 -0
  116. package/dist/esm/shared/schemas.js +521 -0
  117. package/dist/esm/shared/shieldedPreview.d.ts +841 -0
  118. package/dist/esm/shared/shieldedPreview.js +124 -0
  119. package/dist/esm/shared/stealth.d.ts +53 -0
  120. package/dist/esm/shared/stealth.js +64 -0
  121. package/dist/esm/shared/trading.d.ts +153 -0
  122. package/dist/esm/shared/trading.js +182 -0
  123. package/dist/esm/shared/types.d.ts +37 -0
  124. package/dist/esm/shared/types.js +6 -0
  125. package/dist/esm/shared/validators.d.ts +30 -0
  126. package/dist/esm/shared/validators.js +74 -0
  127. package/dist/esm/transport/client.d.ts +234 -0
  128. package/dist/esm/transport/client.js +1447 -0
  129. package/package.json +61 -4
@@ -0,0 +1,411 @@
1
+ /**
2
+ * The quote -> sign -> execute -> status lifecycle as a pure state machine.
3
+ *
4
+ * update(model, msg) -> [model, effects]
5
+ *
6
+ * `update` is the only place lifecycle state changes. Everything it cannot do
7
+ * itself -- an HTTP call, a wallet prompt, a timer, a log line -- it asks for as
8
+ * an `Effect`, which is plain JSON. A host (the React interpreter in the widget,
9
+ * or a headless integrator) performs the effect and feeds the outcome back as a
10
+ * `Msg`. Every `Msg` is stamped with the clock and tab visibility it was
11
+ * observed under, so a recorded msg log replays to the same models, effects and
12
+ * receipts with no mocks.
13
+ *
14
+ * The one exception to that clock is the fund-safety checks from
15
+ * `@xochi/signer` that run at quote ingest: they bound the intent deadline and
16
+ * the deposit window against the WALL clock, and take no `now` argument by
17
+ * design, since an injectable clock on a security assertion is a bypass.
18
+ *
19
+ * No DOM, no fetch, no React, no wagmi, no console: the machine compiles
20
+ * against `lib: ["ES2020"]` alone (tsconfig.machine.json).
21
+ */
22
+ import type { ChainId, Eip712TypedData, IntentStatus, SelectableChainId, SettlementType } from "../shared/index.js";
23
+ import { type PinnedPullRecipients } from "@xochi/signer";
24
+ import { type SolverPinConfig } from "./solverPin.js";
25
+ import { type IntentState, type StructuredError } from "./state.js";
26
+ import type { ExecuteReply, ExecuteRequest, IntentStatusResult, IntentWebSocketMessage, QuoteRequest, QuoteResponse } from "./wire.js";
27
+ /** Amount edits settle for this long before a quote is requested. */
28
+ export declare const QUOTE_DEBOUNCE_MS = 500;
29
+ /**
30
+ * Refresh this long before expiry. Exported so the panel countdown pill can
31
+ * align "New price in Ns" with the actual refresh instant.
32
+ */
33
+ export declare const QUOTE_REFRESH_BUFFER_MS = 5000;
34
+ /**
35
+ * After this many polls in one phase without reaching a terminal state, flip the
36
+ * modal from an indefinite "Submitting" spinner to the reassuring "Still
37
+ * confirming" (safe-to-close) view. Reconciliation/webhooks still resolve it;
38
+ * this only fixes the feedback gap. ~90s at the shared cadence.
39
+ */
40
+ export declare const RECONCILE_AFTER_ATTEMPTS: number;
41
+ /**
42
+ * If a wallet signature prompt never resolves (a mobile deep-link that failed to
43
+ * re-open the wallet), stop waiting after this and surface a retry. Long enough
44
+ * for a real human to review + sign, short enough to recover from a dead prompt.
45
+ */
46
+ export declare const SIGN_PROMPT_TIMEOUT_MS = 90000;
47
+ export interface StealthMetaAddress {
48
+ spendingPubKey: string;
49
+ viewingPubKey: string;
50
+ chainId: ChainId;
51
+ }
52
+ export interface WalletContext {
53
+ /** Connected account, or null with no wallet. */
54
+ address: string | null;
55
+ /**
56
+ * The wallet's ACTUAL chain (it may be one the app has no config for,
57
+ * xochi#539), or null when none is reported.
58
+ */
59
+ chainId: number | null;
60
+ /**
61
+ * A deep-link wallet (mobile WalletConnect) that will not re-open for a
62
+ * second back-to-back prompt, so the pull signature waits for a user tap.
63
+ */
64
+ stepwise: boolean;
65
+ }
66
+ /** The trade the host's form describes, plus the wallet it is connected with. */
67
+ export interface Context {
68
+ /** Origin can be non-EVM (a Tron deposit route); destination too. */
69
+ fromChainId: SelectableChainId;
70
+ toChainId: SelectableChainId;
71
+ fromToken: string;
72
+ toToken: string;
73
+ settlementPreference: SettlementType;
74
+ trustScore: number;
75
+ slippageBps: number;
76
+ /** Recipient on the destination chain (non-EVM routes); null for EVM. */
77
+ recipientAddress: string | null;
78
+ /** Refund destination on a deposit-route ORIGIN; null for EVM origins. */
79
+ refundAddress: string | null;
80
+ stealthMetaAddress: StealthMetaAddress | null;
81
+ /** The confirm surface is open: pre-expiry refreshes run silently. */
82
+ paused: boolean;
83
+ wallet: WalletContext;
84
+ }
85
+ export interface MachineConfig {
86
+ /** The build's pinned origin-pull recipients (fail-closed, see solverPin.ts). */
87
+ solverPin: SolverPinConfig;
88
+ /**
89
+ * The caller's per-chain, per-method pull-recipient pin, handed to
90
+ * `@xochi/signer`'s `assertPullMatchesIntent` at quote ingest. A chain absent
91
+ * from it is left to `solverPin`; a chain present without the method in use
92
+ * refuses. Never built from `/api/capabilities`: an advertisement arrives over
93
+ * the same channel as the quote it would vouch for.
94
+ */
95
+ pullRecipients?: PinnedPullRecipients;
96
+ /**
97
+ * The pinned deposit-attestation signer (0x address). `null` refuses every
98
+ * deposit route. Caller-supplied for the same reason as `pullRecipients`.
99
+ */
100
+ depositAttestationSigner: string | null;
101
+ }
102
+ export type TimerName = "debounce" | "refresh" | "expiry" | "poll" | "signPrompt" | "execute";
103
+ export type ApiCall = {
104
+ op: "quote";
105
+ flight: string;
106
+ request: QuoteRequest;
107
+ forceRefresh: boolean;
108
+ } | {
109
+ op: "execute";
110
+ flow: string;
111
+ request: ExecuteRequest;
112
+ } | {
113
+ op: "status";
114
+ intentId: string;
115
+ gen: number;
116
+ attempt: number;
117
+ };
118
+ export type WalletCall =
119
+ /**
120
+ * Prove the wallet to the worker (SIWE) before allocating a deposit route.
121
+ * Non-interactive means "only if a session already exists": a background
122
+ * refresh never opens a surprise wallet prompt.
123
+ */
124
+ {
125
+ op: "ensureSession";
126
+ flight: string;
127
+ interactive: boolean;
128
+ } | {
129
+ op: "signTypedData";
130
+ flow: string;
131
+ purpose: "intent" | "pull";
132
+ typedData: Eip712TypedData;
133
+ /** The wallet's current chain, which the host binds into the domain. */
134
+ chainId: number | null;
135
+ }
136
+ /** One-time token approval for the origin pull (base units). */
137
+ | {
138
+ op: "ensureApproval";
139
+ flow: string;
140
+ amount: string;
141
+ } | {
142
+ op: "resetApproval";
143
+ };
144
+ export type TimerCall = {
145
+ op: "set";
146
+ timer: TimerName;
147
+ ms: number;
148
+ repeat: boolean;
149
+ } | {
150
+ op: "clear";
151
+ timer: TimerName;
152
+ };
153
+ export type MachineEvent = {
154
+ kind: "log";
155
+ level: "debug" | "warn" | "error";
156
+ message: string;
157
+ data: unknown[];
158
+ }
159
+ /** The outcome of one explicit deposit-route allocation (`AllocateRequested.ref`). */
160
+ | {
161
+ kind: "allocation";
162
+ ref: string;
163
+ ok: boolean;
164
+ };
165
+ export type Effect = {
166
+ type: "Api";
167
+ call: ApiCall;
168
+ } | {
169
+ type: "Wallet";
170
+ call: WalletCall;
171
+ } | {
172
+ type: "Timer";
173
+ call: TimerCall;
174
+ } | {
175
+ type: "Emit";
176
+ event: MachineEvent;
177
+ };
178
+ /** A failure the host observed, already classified (the machine sees no Error objects). */
179
+ export interface ErrorReport {
180
+ /** The wallet said the user declined (EIP-1193 4001 and kin). */
181
+ rejected: boolean;
182
+ /**
183
+ * What `intent.error` becomes: a StructuredError for an API failure (code +
184
+ * retryAfter), the message for an Error, null for a non-Error throw (the
185
+ * machine then supplies its own fallback copy).
186
+ */
187
+ value: string | StructuredError | null;
188
+ /** For the log line only. */
189
+ detail: string;
190
+ /**
191
+ * Execute only, and only from `ApiError.notAccepted`: the server proved it
192
+ * refused before accepting anything, so the machine may send the SAME
193
+ * request again at `value.retryAfter` (`decideExecuteResend`). Anything but
194
+ * `true` is a thrown execute like any other: the poll decides it.
195
+ */
196
+ notAccepted?: boolean;
197
+ }
198
+ export type Outcome<T> = ({
199
+ ok: true;
200
+ } & T) | {
201
+ ok: false;
202
+ error: ErrorReport;
203
+ };
204
+ export type MsgBody = {
205
+ type: "ContextChanged";
206
+ ctx: Context;
207
+ } | {
208
+ type: "QuoteRequested";
209
+ amount: string;
210
+ }
211
+ /** `key` is a fresh idempotency key, used only if none is held for this amount. */
212
+ | {
213
+ type: "AllocateRequested";
214
+ key: string;
215
+ ref: string;
216
+ } | {
217
+ type: "RefreshRequested";
218
+ } | {
219
+ type: "SignRequested";
220
+ } | {
221
+ type: "PullSignRequested";
222
+ } | {
223
+ type: "CancelSignRequested";
224
+ } | {
225
+ type: "ResetRequested";
226
+ } | {
227
+ type: "ResumeRequested";
228
+ intentId: string;
229
+ status: IntentStatus;
230
+ owner: string;
231
+ depositDeadline: number | null;
232
+ /**
233
+ * The record's `substatus` as `getIntent` returns it now
234
+ * (`IntentRecord.substatus`). A resumed `failed` offers Retry only with
235
+ * `NOT_EXECUTED_SUBSTATUS`; any other, or none, resumes as "may have
236
+ * moved" (README "Fund safety"). Read it from the Worker at resume, never
237
+ * from host storage: the machine trusts it as it trusts every reply the
238
+ * host relays, and a stored copy can be stale or altered.
239
+ */
240
+ substatus?: string | null;
241
+ } | {
242
+ type: "VisibilityChanged";
243
+ } | {
244
+ type: "StreamStatus";
245
+ message: IntentWebSocketMessage;
246
+ } | {
247
+ type: "TimerFired";
248
+ timer: TimerName;
249
+ } | {
250
+ type: "SessionReplied";
251
+ flight: string;
252
+ result: "ok" | "refused" | "skipped";
253
+ } | {
254
+ type: "QuoteReplied";
255
+ flight: string;
256
+ result: Outcome<{
257
+ quote: QuoteResponse;
258
+ }>;
259
+ } | {
260
+ type: "SignReplied";
261
+ flow: string;
262
+ purpose: "intent" | "pull";
263
+ result: Outcome<{
264
+ signature: string;
265
+ }>;
266
+ } | {
267
+ type: "ApprovalReplied";
268
+ flow: string;
269
+ result: Outcome<{
270
+ approved: boolean;
271
+ }>;
272
+ }
273
+ /**
274
+ * The execute response with its `readToken` removed (`ExecuteReply`): the
275
+ * host keeps the bearer secret outside the machine and relays the rest. The
276
+ * machine itself decides failed vs in doubt (#226); a reply it cannot read
277
+ * as an execute response for this intent is in doubt, like a thrown execute.
278
+ */
279
+ | {
280
+ type: "ExecuteReplied";
281
+ flow: string;
282
+ result: Outcome<{
283
+ response: ExecuteReply;
284
+ }>;
285
+ } | {
286
+ type: "StatusReplied";
287
+ gen: number;
288
+ attempt: number;
289
+ result: Outcome<{
290
+ status: IntentStatusResult;
291
+ }>;
292
+ };
293
+ /** Every message carries the clock and tab visibility it was observed under. */
294
+ export type Msg = MsgBody & {
295
+ now: number;
296
+ hidden: boolean;
297
+ };
298
+ export interface QuoteFlight {
299
+ id: string;
300
+ /** Waiting on the wallet session or the quote itself. */
301
+ stage: "session" | "api";
302
+ amount: string;
303
+ /**
304
+ * Refresh in place without churning the confirm surface: no "quoting" flash,
305
+ * and a failed refresh keeps the last good quote.
306
+ */
307
+ silent: boolean;
308
+ forceRefresh: boolean;
309
+ allocate: {
310
+ key: string;
311
+ ref: string;
312
+ } | null;
313
+ /** The context the request was built from, held so the gates check that deal. */
314
+ ctx: Context;
315
+ depositOrigin: boolean;
316
+ preview: boolean;
317
+ request: QuoteRequest | null;
318
+ }
319
+ export interface SignFlow {
320
+ id: string;
321
+ /** Which user action started this leg: Confirm, or the "Sign 2 of 2" tap. */
322
+ origin: "sign" | "pull";
323
+ stage: "intent" | "approval" | "pull" | "execute";
324
+ intentSignature: string | null;
325
+ /** Stage `execute`: the request dispatched and its not-accepted resends. */
326
+ execute?: ExecuteAttempt;
327
+ }
328
+ /**
329
+ * The execute of a sign flow. `request` is held so an execute the server did
330
+ * not accept (`ErrorReport.notAccepted`) is sent again unchanged; `resendAt`
331
+ * (epoch ms) is set only while waiting for that resend, with no request in
332
+ * flight, and `error` is the refusal shown if the wait ends in Retry.
333
+ */
334
+ export interface ExecuteAttempt {
335
+ request: ExecuteRequest;
336
+ resends: number;
337
+ resendAt: number | null;
338
+ error: string | StructuredError | null;
339
+ }
340
+ export interface PollState {
341
+ key: string;
342
+ intentId: string;
343
+ gen: number;
344
+ /** Every poll performed in this arming (what the reconcile escalation counts). */
345
+ polls: number;
346
+ /**
347
+ * The newest attempt answered so far. Replies can land out of order (a timer
348
+ * read and an off-interval read in flight together).
349
+ */
350
+ answered: number;
351
+ /** Elapsed intervals with nothing observed (the give-up budget). */
352
+ spent: number;
353
+ max: number;
354
+ stopped: boolean;
355
+ }
356
+ export interface Model {
357
+ config: MachineConfig;
358
+ ctx: Context;
359
+ hidden: boolean;
360
+ intent: IntentState;
361
+ /** The pay amount (base units) the panel last asked to quote. */
362
+ amount: string;
363
+ allocationKey: string | null;
364
+ awaitingPull: boolean;
365
+ isSigningPull: boolean;
366
+ /** The intent signature held between signature 1 and the deferred pull. */
367
+ pendingIntentSig: string | null;
368
+ quoteExpiresIn: number | null;
369
+ /** When the signed intent went to the solver (fill clock start). */
370
+ submittedAt: number | null;
371
+ /**
372
+ * Submit -> filled, set only on an inline (execute-reply) completion: a
373
+ * poll-observed completion is up to a poll interval late.
374
+ */
375
+ clientFillMs: number | null;
376
+ notFoundReported: string | null;
377
+ seq: number;
378
+ flights: QuoteFlight[];
379
+ sign: SignFlow | null;
380
+ poll: PollState | null;
381
+ refresh: {
382
+ amount: string;
383
+ hasAllocatedDeposit: boolean;
384
+ } | null;
385
+ /**
386
+ * The trade (`tradeKey`) the held quote was requested for. With
387
+ * `intent.quotedFromAmount` it decides whether the quote on screen still
388
+ * describes the form (`holdsCurrentQuote`); null until a quote lands.
389
+ */
390
+ quotedTrade: string | null;
391
+ /**
392
+ * Deposit address -> the intent it was attested for, for every deposit route
393
+ * verified this session (keyed as `@xochi/signer`'s `recordDepositAddress`
394
+ * keys it). A per-session tripwire, not a ledger: a fresh `init` clears it.
395
+ */
396
+ seenDepositAddresses: Readonly<Record<string, string>>;
397
+ /** Last-evaluated keys of the reactive rules in `settle`. */
398
+ watched: {
399
+ wallet: string;
400
+ upgrade: string;
401
+ expiry: string | null;
402
+ };
403
+ }
404
+ export declare function init(config: MachineConfig, ctx: Context, hidden?: boolean): Model;
405
+ export declare function update(model: Model, msg: Msg): [Model, Effect[]];
406
+ /**
407
+ * The held quote still describes the trade on the form: the amount and every
408
+ * request field it was quoted for. Confirm and the receipt's signing slot both
409
+ * read it, so neither offers to sign a quote for a trade the user has edited.
410
+ */
411
+ export declare function holdsCurrentQuote(m: Model): boolean;