@metamask-previews/bridge-status-controller 73.1.0-preview-e12e4a38f → 74.0.0-preview-4f9d41d
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 +9 -1
- package/dist/bridge-status-controller.cjs +167 -21
- package/dist/bridge-status-controller.cjs.map +1 -1
- package/dist/bridge-status-controller.d.cts +6 -1
- package/dist/bridge-status-controller.d.cts.map +1 -1
- package/dist/bridge-status-controller.d.mts +6 -1
- package/dist/bridge-status-controller.d.mts.map +1 -1
- package/dist/bridge-status-controller.mjs +167 -21
- package/dist/bridge-status-controller.mjs.map +1 -1
- package/dist/constants.cjs +1 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.mts.map +1 -1
- package/dist/constants.mjs +1 -0
- package/dist/constants.mjs.map +1 -1
- package/dist/index.cjs +12 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/quote-status-manager/constants.cjs +181 -0
- package/dist/quote-status-manager/constants.cjs.map +1 -0
- package/dist/quote-status-manager/constants.d.cts +137 -0
- package/dist/quote-status-manager/constants.d.cts.map +1 -0
- package/dist/quote-status-manager/constants.d.mts +137 -0
- package/dist/quote-status-manager/constants.d.mts.map +1 -0
- package/dist/quote-status-manager/constants.mjs +178 -0
- package/dist/quote-status-manager/constants.mjs.map +1 -0
- package/dist/quote-status-manager/errors.cjs +51 -0
- package/dist/quote-status-manager/errors.cjs.map +1 -0
- package/dist/quote-status-manager/errors.d.cts +45 -0
- package/dist/quote-status-manager/errors.d.cts.map +1 -0
- package/dist/quote-status-manager/errors.d.mts +45 -0
- package/dist/quote-status-manager/errors.d.mts.map +1 -0
- package/dist/quote-status-manager/errors.mjs +46 -0
- package/dist/quote-status-manager/errors.mjs.map +1 -0
- package/dist/quote-status-manager/quote-status-api-service.cjs +241 -0
- package/dist/quote-status-manager/quote-status-api-service.cjs.map +1 -0
- package/dist/quote-status-manager/quote-status-api-service.d.cts +93 -0
- package/dist/quote-status-manager/quote-status-api-service.d.cts.map +1 -0
- package/dist/quote-status-manager/quote-status-api-service.d.mts +93 -0
- package/dist/quote-status-manager/quote-status-api-service.d.mts.map +1 -0
- package/dist/quote-status-manager/quote-status-api-service.mjs +237 -0
- package/dist/quote-status-manager/quote-status-api-service.mjs.map +1 -0
- package/dist/quote-status-manager/quote-status-entry-store.cjs +271 -0
- package/dist/quote-status-manager/quote-status-entry-store.cjs.map +1 -0
- package/dist/quote-status-manager/quote-status-entry-store.d.cts +144 -0
- package/dist/quote-status-manager/quote-status-entry-store.d.cts.map +1 -0
- package/dist/quote-status-manager/quote-status-entry-store.d.mts +144 -0
- package/dist/quote-status-manager/quote-status-entry-store.d.mts.map +1 -0
- package/dist/quote-status-manager/quote-status-entry-store.mjs +267 -0
- package/dist/quote-status-manager/quote-status-entry-store.mjs.map +1 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.cjs +29 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.cjs.map +1 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.d.cts +32 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.d.cts.map +1 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.d.mts +32 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.d.mts.map +1 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.mjs +25 -0
- package/dist/quote-status-manager/quote-status-get-with-retry-outcome.mjs.map +1 -0
- package/dist/quote-status-manager/quote-status-state-fsm.cjs +107 -0
- package/dist/quote-status-manager/quote-status-state-fsm.cjs.map +1 -0
- package/dist/quote-status-manager/quote-status-state-fsm.d.cts +57 -0
- package/dist/quote-status-manager/quote-status-state-fsm.d.cts.map +1 -0
- package/dist/quote-status-manager/quote-status-state-fsm.d.mts +57 -0
- package/dist/quote-status-manager/quote-status-state-fsm.d.mts.map +1 -0
- package/dist/quote-status-manager/quote-status-state-fsm.mjs +103 -0
- package/dist/quote-status-manager/quote-status-state-fsm.mjs.map +1 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.cjs +27 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.cjs.map +1 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.d.cts +30 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.d.cts.map +1 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.d.mts +30 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.d.mts.map +1 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.mjs +23 -0
- package/dist/quote-status-manager/quote-status-update-with-retry-outcome.mjs.map +1 -0
- package/dist/quote-status-manager/quotes-status-manager.cjs +477 -0
- package/dist/quote-status-manager/quotes-status-manager.cjs.map +1 -0
- package/dist/quote-status-manager/quotes-status-manager.d.cts +124 -0
- package/dist/quote-status-manager/quotes-status-manager.d.cts.map +1 -0
- package/dist/quote-status-manager/quotes-status-manager.d.mts +124 -0
- package/dist/quote-status-manager/quotes-status-manager.d.mts.map +1 -0
- package/dist/quote-status-manager/quotes-status-manager.mjs +473 -0
- package/dist/quote-status-manager/quotes-status-manager.mjs.map +1 -0
- package/dist/quote-status-manager/types.cjs +8 -0
- package/dist/quote-status-manager/types.cjs.map +1 -0
- package/dist/quote-status-manager/types.d.cts +145 -0
- package/dist/quote-status-manager/types.d.cts.map +1 -0
- package/dist/quote-status-manager/types.d.mts +145 -0
- package/dist/quote-status-manager/types.d.mts.map +1 -0
- package/dist/quote-status-manager/types.mjs +6 -0
- package/dist/quote-status-manager/types.mjs.map +1 -0
- package/dist/quote-status-manager/utils.cjs +11 -0
- package/dist/quote-status-manager/utils.cjs.map +1 -0
- package/dist/quote-status-manager/utils.d.cts +7 -0
- package/dist/quote-status-manager/utils.d.cts.map +1 -0
- package/dist/quote-status-manager/utils.d.mts +7 -0
- package/dist/quote-status-manager/utils.d.mts.map +1 -0
- package/dist/quote-status-manager/utils.mjs +7 -0
- package/dist/quote-status-manager/utils.mjs.map +1 -0
- package/dist/quote-status-manager/validators.cjs +44 -0
- package/dist/quote-status-manager/validators.cjs.map +1 -0
- package/dist/quote-status-manager/validators.d.cts +172 -0
- package/dist/quote-status-manager/validators.d.cts.map +1 -0
- package/dist/quote-status-manager/validators.d.mts +172 -0
- package/dist/quote-status-manager/validators.d.mts.map +1 -0
- package/dist/quote-status-manager/validators.mjs +39 -0
- package/dist/quote-status-manager/validators.mjs.map +1 -0
- package/dist/types.cjs +2 -0
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +15 -3
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +15 -3
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +2 -0
- package/dist/types.mjs.map +1 -1
- package/dist/utils/history.cjs +1 -0
- package/dist/utils/history.cjs.map +1 -1
- package/dist/utils/history.d.cts.map +1 -1
- package/dist/utils/history.d.mts.map +1 -1
- package/dist/utils/history.mjs +1 -0
- package/dist/utils/history.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _QuoteStatusManager_instances, _QuoteStatusManager_messenger, _QuoteStatusManager_quoteStatusApiService, _QuoteStatusManager_quoteStatusEntryStore, _QuoteStatusManager_isEnabled, _QuoteStatusManager_onError, _QuoteStatusManager_updateIntervalMs, _QuoteStatusManager_retryIntervalId, _QuoteStatusManager_processInitial, _QuoteStatusManager_ensureRetryTimerRunning, _QuoteStatusManager_stopRetryTimer, _QuoteStatusManager_processRetries, _QuoteStatusManager_hasPendingUpdates, _QuoteStatusManager_stopRetryTimerIfIdle, _QuoteStatusManager_processEntry, _QuoteStatusManager_markCompleted, _QuoteStatusManager_markExpired, _QuoteStatusManager_handleNonRetryableUpdateStatusError;
|
|
13
|
+
import { TransactionStatus } from "@metamask/transaction-controller";
|
|
14
|
+
import { BridgeClientId } from "../types.mjs";
|
|
15
|
+
import { getTransactionMetaById, hasNestedSwapTransactions, isCrossChainTx } from "../utils/transaction.mjs";
|
|
16
|
+
import { QuoteStatusState, QuoteStatusStateToBackendStatus, QuoteStatusUpdateBackendErrorType, QuoteStatusBackendStatus, QuoteStatusFetchWithRetryOutcomeType } from "./constants.mjs";
|
|
17
|
+
import { QuoteStatusUpdateError } from "./errors.mjs";
|
|
18
|
+
import { QuoteStatusApiService } from "./quote-status-api-service.mjs";
|
|
19
|
+
import { QuoteStatusEntryStore } from "./quote-status-entry-store.mjs";
|
|
20
|
+
import { QuoteStatusStateFsm } from "./quote-status-state-fsm.mjs";
|
|
21
|
+
import { QuoteStatusUpdateWithRetryOutcome } from "./quote-status-update-with-retry-outcome.mjs";
|
|
22
|
+
/**
|
|
23
|
+
* Tracks bridge/swap quotes through their lifecycle and keeps the backend in
|
|
24
|
+
* sync with the latest known status of each quote.
|
|
25
|
+
*
|
|
26
|
+
* Quotes are reported via {@link reportSubmitted} and {@link reportFinalised},
|
|
27
|
+
* stored as runtime entries, and pushed to the backend. Updates that fail in a
|
|
28
|
+
* retryable way are retried on a periodic timer until each entry reaches a
|
|
29
|
+
* terminal state, at which point it is evicted and the timer stops once the
|
|
30
|
+
* store is empty.
|
|
31
|
+
*/
|
|
32
|
+
export class QuoteStatusManager {
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new manager and immediately processes any persisted entries.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Constructor options.
|
|
37
|
+
* @param options.messenger - Messenger used to communicate with the backend
|
|
38
|
+
* API service.
|
|
39
|
+
* @param options.clientId - Identifier of the client making the requests.
|
|
40
|
+
* @param options.clientProduct - Name of the client product making the
|
|
41
|
+
* requests.
|
|
42
|
+
* @param options.clientVersion - Optional version of the client product.
|
|
43
|
+
* @param options.apiBaseUrl - Base URL of the quote status backend API.
|
|
44
|
+
* @param options.onPersistUpdates - Callback invoked to persist entry updates.
|
|
45
|
+
* @param options.onError - Optional callback invoked when a non-recoverable
|
|
46
|
+
* error occurs.
|
|
47
|
+
* @param options.isEnabled - Optional predicate gating whether the manager
|
|
48
|
+
* performs any work.
|
|
49
|
+
* @param options.entryTtlMs - Time-to-live, in milliseconds, after which a
|
|
50
|
+
* tracked entry is evicted.
|
|
51
|
+
* @param options.updateIntervalMs - How often the manager re-processes entries that
|
|
52
|
+
* have not yet reached a terminal state
|
|
53
|
+
* @param options.initialData - Persisted entries to rehydrate on startup.
|
|
54
|
+
*/
|
|
55
|
+
constructor({ messenger, clientId, clientProduct, clientVersion, apiBaseUrl, onError, isEnabled, onPersistUpdates, entryTtlMs, updateIntervalMs, initialData, }) {
|
|
56
|
+
_QuoteStatusManager_instances.add(this);
|
|
57
|
+
_QuoteStatusManager_messenger.set(this, void 0);
|
|
58
|
+
_QuoteStatusManager_quoteStatusApiService.set(this, void 0);
|
|
59
|
+
_QuoteStatusManager_quoteStatusEntryStore.set(this, void 0);
|
|
60
|
+
_QuoteStatusManager_isEnabled.set(this, void 0);
|
|
61
|
+
_QuoteStatusManager_onError.set(this, void 0);
|
|
62
|
+
_QuoteStatusManager_updateIntervalMs.set(this, void 0);
|
|
63
|
+
/**
|
|
64
|
+
* Handle for the periodic retry timer. While running, every
|
|
65
|
+
* {@link QUOTE_STATUS_UPDATE_RETRY_INTERVAL_MS} all entries that have not yet
|
|
66
|
+
* reached a terminal state (Completed/Expired) are re-processed. It is started
|
|
67
|
+
* lazily when there is work to do and stopped once the store is empty.
|
|
68
|
+
*/
|
|
69
|
+
_QuoteStatusManager_retryIntervalId.set(this, null);
|
|
70
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_isEnabled, isEnabled, "f");
|
|
71
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_onError, onError, "f");
|
|
72
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_updateIntervalMs, updateIntervalMs, "f");
|
|
73
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_messenger, messenger, "f");
|
|
74
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_quoteStatusApiService, new QuoteStatusApiService({
|
|
75
|
+
messenger,
|
|
76
|
+
clientId,
|
|
77
|
+
clientProduct,
|
|
78
|
+
clientVersion,
|
|
79
|
+
apiBaseUrl,
|
|
80
|
+
onError,
|
|
81
|
+
}), "f");
|
|
82
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_quoteStatusEntryStore, new QuoteStatusEntryStore({
|
|
83
|
+
onPersistUpdates,
|
|
84
|
+
entryTtlMs,
|
|
85
|
+
initial: initialData,
|
|
86
|
+
}), "f");
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether quote-status tracking and backend sync are currently active.
|
|
90
|
+
*
|
|
91
|
+
* Reflects the latest value returned by the optional `isEnabled` predicate
|
|
92
|
+
* supplied at construction time. When no predicate was provided, this is
|
|
93
|
+
* always `false` and all public methods that gate on enablement no-op.
|
|
94
|
+
*
|
|
95
|
+
* @returns `true` when the `isEnabled` predicate returns a truthy value.
|
|
96
|
+
*/
|
|
97
|
+
get enabled() {
|
|
98
|
+
return Boolean(__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Reports that a previously submitted quote has finalized on-chain.
|
|
102
|
+
*
|
|
103
|
+
* Looks up the tracked entry by its transaction metadata id, transitions it to
|
|
104
|
+
* the appropriate terminal state, and processes the update. No-ops when the
|
|
105
|
+
* manager is disabled, and surfaces an error when the entry is missing or
|
|
106
|
+
* cannot transition to the finalized state.
|
|
107
|
+
*
|
|
108
|
+
* @param txMetaId - Transaction metadata id of the finalized quote.
|
|
109
|
+
* @param success - Whether the transaction finalized successfully.
|
|
110
|
+
*/
|
|
111
|
+
reportFinalised(txMetaId, success) {
|
|
112
|
+
if (!__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const entry = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").getByTxMetaId(txMetaId);
|
|
116
|
+
if (!entry) {
|
|
117
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_onError, "f")?.call(this, new QuoteStatusUpdateError('reporting finalization status but entry was not found', { quoteId: '' }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const nextState = success
|
|
121
|
+
? QuoteStatusState.FinalizedSuccess
|
|
122
|
+
: QuoteStatusState.FinalizedFailed;
|
|
123
|
+
if (!entry.status.canTransitionTo(nextState)) {
|
|
124
|
+
// This is expected, there are race conditions where
|
|
125
|
+
// reportFinalized can be called twice. If the second
|
|
126
|
+
// call fails due to the first completed sucesfully
|
|
127
|
+
// backend will report that we cannot transition outside
|
|
128
|
+
// a final state, which is correct and we can safely abort
|
|
129
|
+
// the flow.
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
entry.status.transitionTo(nextState);
|
|
133
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_ensureRetryTimerRunning).call(this);
|
|
134
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Reports that a quote has been submitted on-chain and begins tracking it.
|
|
138
|
+
*
|
|
139
|
+
* Creates a new entry in the `Submitted` state, starts the retry timer, and
|
|
140
|
+
* processes the initial status update. No-ops when the manager is disabled,
|
|
141
|
+
* and surfaces an error if the entry cannot be retrieved after being stored.
|
|
142
|
+
*
|
|
143
|
+
* @param quoteId - Identifier of the submitted quote.
|
|
144
|
+
* @param srcTxHash - Hash of the source-chain transaction for the quote.
|
|
145
|
+
* @param txMetaId - Optional transaction metadata id used to correlate
|
|
146
|
+
* finalization reports.
|
|
147
|
+
*/
|
|
148
|
+
reportSubmitted(quoteId, srcTxHash, txMetaId) {
|
|
149
|
+
if (!__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// Once a quote has advanced past `Submitted` (finalized or terminal), it is
|
|
153
|
+
// done: reporting `SUBMITTED` again would be rejected by the backend as an
|
|
154
|
+
// invalid transition. Retained terminal entries let us recognize and drop
|
|
155
|
+
// these late/duplicate submissions instead of looping on a 400.
|
|
156
|
+
const isQuoteAlreadyTracked = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f")
|
|
157
|
+
.getByQuoteId(quoteId)
|
|
158
|
+
.some((entry) => entry.status.state !== QuoteStatusState.Submitted);
|
|
159
|
+
if (isQuoteAlreadyTracked) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const entryKey = QuoteStatusEntryStore.hash({
|
|
163
|
+
quoteId,
|
|
164
|
+
srcTxHash,
|
|
165
|
+
});
|
|
166
|
+
const entry = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").put(entryKey, {
|
|
167
|
+
quoteId,
|
|
168
|
+
srcTxHash,
|
|
169
|
+
txMetaId,
|
|
170
|
+
status: new QuoteStatusStateFsm(QuoteStatusState.Submitted),
|
|
171
|
+
});
|
|
172
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_ensureRetryTimerRunning).call(this);
|
|
173
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Tears down the manager by stopping the retry timer and clearing all tracked
|
|
177
|
+
* entries.
|
|
178
|
+
*/
|
|
179
|
+
destroy() {
|
|
180
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimer).call(this);
|
|
181
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").clear();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Reconciles quote-status entries whose finalization was missed while the
|
|
185
|
+
* client was closed. Called once during initialization.
|
|
186
|
+
*
|
|
187
|
+
* Swap/bridge source transactions report their final status through the
|
|
188
|
+
* `TransactionController:transactionStatusUpdated` subscription
|
|
189
|
+
* ({@link #onTransactionConfirmed}/{@link #onTransactionFailed}). When a source
|
|
190
|
+
* transaction reaches a terminal state while the client is not running, that
|
|
191
|
+
* event is never re-emitted on the next startup, so the persisted entry would
|
|
192
|
+
* remain `Submitted` until it expires via TTL. This replays the missed terminal
|
|
193
|
+
* event for both swaps and bridges: a confirmed source transaction is reported
|
|
194
|
+
* as a finalized success and a failed/dropped one as a finalized failure.
|
|
195
|
+
*
|
|
196
|
+
* `rejected` is ignored because the transaction was never broadcast.
|
|
197
|
+
*/
|
|
198
|
+
init() {
|
|
199
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processInitial).call(this);
|
|
200
|
+
for (const entry of __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").values()) {
|
|
201
|
+
// Only entries still awaiting finalization need catching up. Entries
|
|
202
|
+
// already in a finalized state are re-sent by `processInitial()`.
|
|
203
|
+
if (entry.status.state !== QuoteStatusState.Submitted ||
|
|
204
|
+
!entry.txMetaId) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const txMeta = getTransactionMetaById(__classPrivateFieldGet(this, _QuoteStatusManager_messenger, "f"), entry.txMetaId);
|
|
208
|
+
if (!txMeta) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
// Reconcile swaps and bridges alike: a terminal source transaction
|
|
212
|
+
// finalizes the quote status. `hasNestedSwapTransactions` also covers
|
|
213
|
+
// batch/7702 swaps whose type may still read as `batch` rather than `swap`.
|
|
214
|
+
const isCrossChainTrade = (txMeta.type !== undefined && isCrossChainTx(txMeta.type)) ||
|
|
215
|
+
hasNestedSwapTransactions(txMeta);
|
|
216
|
+
if (!isCrossChainTrade) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (txMeta.status === TransactionStatus.confirmed) {
|
|
220
|
+
this.reportFinalised(entry.txMetaId, true);
|
|
221
|
+
}
|
|
222
|
+
else if (txMeta.status === TransactionStatus.failed ||
|
|
223
|
+
txMeta.status === TransactionStatus.dropped) {
|
|
224
|
+
this.reportFinalised(entry.txMetaId, false);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Fetches the current quote status from the backend with automatic retries.
|
|
230
|
+
*
|
|
231
|
+
* Unlike {@link reportSubmitted} and {@link reportFinalised}, this is a
|
|
232
|
+
* read-only query and does not mutate tracked entries. Returns `null` when
|
|
233
|
+
* the manager is disabled ({@link enabled} is `false`).
|
|
234
|
+
*
|
|
235
|
+
* @param quoteId - Identifier of the quote whose status should be fetched.
|
|
236
|
+
* @param options - Retry configuration.
|
|
237
|
+
* @param options.maxRetries - Maximum number of retries after the initial attempt.
|
|
238
|
+
* @param options.delayMsBetweenRetries - Delay in milliseconds between attempts.
|
|
239
|
+
*/
|
|
240
|
+
getStatus(quoteId, options = {
|
|
241
|
+
maxRetries: 0,
|
|
242
|
+
delayMsBetweenRetries: 1000,
|
|
243
|
+
}) {
|
|
244
|
+
if (!__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this)) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusApiService, "f")
|
|
248
|
+
.getQuoteStatusWithRetry({
|
|
249
|
+
quoteId,
|
|
250
|
+
}, {
|
|
251
|
+
maxRetries: options.maxRetries ?? 0,
|
|
252
|
+
delayMsBetweenRetries: options.delayMsBetweenRetries ?? 1000,
|
|
253
|
+
})
|
|
254
|
+
.catch(() => undefined);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
_QuoteStatusManager_messenger = new WeakMap(), _QuoteStatusManager_quoteStatusApiService = new WeakMap(), _QuoteStatusManager_quoteStatusEntryStore = new WeakMap(), _QuoteStatusManager_isEnabled = new WeakMap(), _QuoteStatusManager_onError = new WeakMap(), _QuoteStatusManager_updateIntervalMs = new WeakMap(), _QuoteStatusManager_retryIntervalId = new WeakMap(), _QuoteStatusManager_instances = new WeakSet(), _QuoteStatusManager_processInitial = function _QuoteStatusManager_processInitial() {
|
|
258
|
+
for (const entry of __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").values()) {
|
|
259
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
260
|
+
}
|
|
261
|
+
// Terminal entries (Completed/Expired) are retained, so the store is never
|
|
262
|
+
// empty. Only start the retry timer when there is an entry whose status
|
|
263
|
+
// still needs to be reported; otherwise it would tick forever doing nothing.
|
|
264
|
+
if (__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_hasPendingUpdates).call(this)) {
|
|
265
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_ensureRetryTimerRunning).call(this);
|
|
266
|
+
}
|
|
267
|
+
}, _QuoteStatusManager_ensureRetryTimerRunning = function _QuoteStatusManager_ensureRetryTimerRunning() {
|
|
268
|
+
if (__classPrivateFieldGet(this, _QuoteStatusManager_retryIntervalId, "f") !== null) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_retryIntervalId, setInterval(() => __classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processRetries).call(this), __classPrivateFieldGet(this, _QuoteStatusManager_updateIntervalMs, "f")), "f");
|
|
272
|
+
}, _QuoteStatusManager_stopRetryTimer = function _QuoteStatusManager_stopRetryTimer() {
|
|
273
|
+
if (__classPrivateFieldGet(this, _QuoteStatusManager_retryIntervalId, "f") !== null) {
|
|
274
|
+
clearInterval(__classPrivateFieldGet(this, _QuoteStatusManager_retryIntervalId, "f"));
|
|
275
|
+
__classPrivateFieldSet(this, _QuoteStatusManager_retryIntervalId, null, "f");
|
|
276
|
+
}
|
|
277
|
+
}, _QuoteStatusManager_processRetries = function _QuoteStatusManager_processRetries() {
|
|
278
|
+
if (!__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this)) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Snapshot first: `#processEntry` can mutate the store (e.g. transitioning an
|
|
282
|
+
// accepted entry to a terminal state), so iterating a live iterator would be
|
|
283
|
+
// unsafe.
|
|
284
|
+
const entries = [...__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").values()];
|
|
285
|
+
if (!__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_hasPendingUpdates).call(this)) {
|
|
286
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimer).call(this);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (const entry of entries) {
|
|
290
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
291
|
+
}
|
|
292
|
+
}, _QuoteStatusManager_hasPendingUpdates = function _QuoteStatusManager_hasPendingUpdates() {
|
|
293
|
+
for (const entry of __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").values()) {
|
|
294
|
+
const { state } = entry.status;
|
|
295
|
+
const isTerminal = state === QuoteStatusState.Completed ||
|
|
296
|
+
state === QuoteStatusState.Expired;
|
|
297
|
+
if (!isTerminal && entry.acknowledgedState !== state) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}, _QuoteStatusManager_stopRetryTimerIfIdle = function _QuoteStatusManager_stopRetryTimerIfIdle() {
|
|
303
|
+
if (!__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_hasPendingUpdates).call(this)) {
|
|
304
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimer).call(this);
|
|
305
|
+
}
|
|
306
|
+
}, _QuoteStatusManager_processEntry = function _QuoteStatusManager_processEntry(entry) {
|
|
307
|
+
// The backend already accepted the entry's current status, so there is
|
|
308
|
+
// nothing new to report. The entry is kept tracked (e.g. a `Submitted`
|
|
309
|
+
// quote awaiting finalization) until its status advances past the
|
|
310
|
+
// acknowledged one, at which point it is reprocessed.
|
|
311
|
+
if (entry.acknowledgedState === entry.status.state) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const sentStatus = entry.status.state;
|
|
315
|
+
const sentStatusBackend = QuoteStatusStateToBackendStatus[sentStatus];
|
|
316
|
+
// Terminal states (`Completed`/`Expired`) have no backend status to report.
|
|
317
|
+
// The entry is retained so future interactions with the quote are rejected.
|
|
318
|
+
if (sentStatusBackend === null) {
|
|
319
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimerIfIdle).call(this);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
// Re-checked before each retry attempt so an in-flight retry stops early when
|
|
323
|
+
// the entry has since advanced, been acknowledged, become terminal, or
|
|
324
|
+
// expired (a later `reportFinalised` re-triggers processing). This avoids
|
|
325
|
+
// firing a request the backend would reject (e.g. `SUBMITTED` after
|
|
326
|
+
// finalization).
|
|
327
|
+
const retry = () => {
|
|
328
|
+
const live = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").get(QuoteStatusEntryStore.hash(entry));
|
|
329
|
+
return Boolean(live &&
|
|
330
|
+
live.status.state === sentStatus &&
|
|
331
|
+
live.acknowledgedState !== sentStatus);
|
|
332
|
+
};
|
|
333
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusApiService, "f")
|
|
334
|
+
.updateQuoteStatusWithRetry({
|
|
335
|
+
quoteId: entry.quoteId,
|
|
336
|
+
srcTxHash: entry.srcTxHash,
|
|
337
|
+
newStatus: sentStatusBackend,
|
|
338
|
+
}, {
|
|
339
|
+
maxRetries: 5,
|
|
340
|
+
delayMsBetweenRetries: 3000,
|
|
341
|
+
retry,
|
|
342
|
+
})
|
|
343
|
+
.then((outcome) => {
|
|
344
|
+
switch (outcome.type) {
|
|
345
|
+
case QuoteStatusFetchWithRetryOutcomeType.Accepted: {
|
|
346
|
+
const current = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").get(QuoteStatusEntryStore.hash(entry));
|
|
347
|
+
// The entry can only be absent if the store was cleared (e.g. via
|
|
348
|
+
// `destroy`) while the request was in flight; nothing left to do.
|
|
349
|
+
if (!current) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
if (current.status.state !== sentStatus) {
|
|
353
|
+
// The status advanced mid-flight; report the newer status.
|
|
354
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, current);
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
if (sentStatus === QuoteStatusState.FinalizedSuccess ||
|
|
358
|
+
sentStatus === QuoteStatusState.FinalizedFailed) {
|
|
359
|
+
// A finalized status was accepted; advance to the terminal
|
|
360
|
+
// `Completed` state and keep the entry so any later duplicate
|
|
361
|
+
// `reportSubmitted` for this quote is rejected instead of looping.
|
|
362
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_markCompleted).call(this, current);
|
|
363
|
+
// Call getStatus to complete the flow on the backend.
|
|
364
|
+
// We discard any value for now, this is used purely
|
|
365
|
+
// to notify backend that the flow is complete.
|
|
366
|
+
this.getStatus(entry.quoteId, { maxRetries: 1 });
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
// A non-final status (e.g. `Submitted`) was accepted. The quote is
|
|
370
|
+
// not done yet: it still needs to be finalized via a later
|
|
371
|
+
// `reportFinalised`, which looks the entry up by `txMetaId`. Keep
|
|
372
|
+
// the entry tracked and record the acknowledgement so the retry
|
|
373
|
+
// loop stops re-sending the already-accepted status.
|
|
374
|
+
current.acknowledgedState = sentStatus;
|
|
375
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").update(current);
|
|
376
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimerIfIdle).call(this);
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
case QuoteStatusFetchWithRetryOutcomeType.NonRetryable:
|
|
380
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_handleNonRetryableUpdateStatusError).call(this, entry, outcome);
|
|
381
|
+
return undefined;
|
|
382
|
+
case QuoteStatusFetchWithRetryOutcomeType.Interrupted:
|
|
383
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
384
|
+
return undefined;
|
|
385
|
+
case QuoteStatusFetchWithRetryOutcomeType.RetryableExhausted:
|
|
386
|
+
entry.lastAttemptAt = Date.now();
|
|
387
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").update(entry);
|
|
388
|
+
return undefined;
|
|
389
|
+
default:
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
.catch(() => {
|
|
394
|
+
entry.lastAttemptAt = Date.now();
|
|
395
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").update(entry);
|
|
396
|
+
});
|
|
397
|
+
}, _QuoteStatusManager_markCompleted = function _QuoteStatusManager_markCompleted(entry, finalizedState) {
|
|
398
|
+
if (finalizedState && entry.status.canTransitionTo(finalizedState)) {
|
|
399
|
+
entry.status.transitionTo(finalizedState);
|
|
400
|
+
}
|
|
401
|
+
if (entry.status.canTransitionTo(QuoteStatusState.Completed)) {
|
|
402
|
+
entry.status.transitionTo(QuoteStatusState.Completed);
|
|
403
|
+
}
|
|
404
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimerIfIdle).call(this);
|
|
405
|
+
}, _QuoteStatusManager_markExpired = function _QuoteStatusManager_markExpired(entry) {
|
|
406
|
+
if (entry.status.canTransitionTo(QuoteStatusState.Expired)) {
|
|
407
|
+
entry.status.transitionTo(QuoteStatusState.Expired);
|
|
408
|
+
}
|
|
409
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_stopRetryTimerIfIdle).call(this);
|
|
410
|
+
}, _QuoteStatusManager_handleNonRetryableUpdateStatusError = function _QuoteStatusManager_handleNonRetryableUpdateStatusError(entry, outcome) {
|
|
411
|
+
const { response } = outcome;
|
|
412
|
+
const backendFinalizedToState = {
|
|
413
|
+
[QuoteStatusBackendStatus.FinalizedSuccess]: QuoteStatusState.FinalizedSuccess,
|
|
414
|
+
[QuoteStatusBackendStatus.FinalizedFailed]: QuoteStatusState.FinalizedFailed,
|
|
415
|
+
};
|
|
416
|
+
// The transition we requested is invalid because the backend is already in a
|
|
417
|
+
// terminal state (e.g. we re-sent `SUBMITTED` after finalization). There is
|
|
418
|
+
// nothing left to report, so advance the entry to `Completed` and keep it.
|
|
419
|
+
if (response?.type ===
|
|
420
|
+
QuoteStatusUpdateBackendErrorType.InvalidStatusTransaction &&
|
|
421
|
+
(response.currentStatus === QuoteStatusBackendStatus.FinalizedSuccess ||
|
|
422
|
+
response.currentStatus === QuoteStatusBackendStatus.FinalizedFailed)) {
|
|
423
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_markCompleted).call(this, entry, backendFinalizedToState[response.currentStatus]);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
// For mismatch errors the backend reports the status it currently has, which
|
|
427
|
+
// lets us reconcile our local state. The discriminant check also narrows
|
|
428
|
+
// `response` to the variant carrying `currentStatus`.
|
|
429
|
+
if (response?.type ===
|
|
430
|
+
QuoteStatusUpdateBackendErrorType.InvalidStatusTransaction ||
|
|
431
|
+
response?.type ===
|
|
432
|
+
QuoteStatusUpdateBackendErrorType.QuoteStatusOnChainMismatch) {
|
|
433
|
+
const nextState = backendFinalizedToState[response.currentStatus];
|
|
434
|
+
if (nextState) {
|
|
435
|
+
// We are behind the backend's terminal status; advance our local state
|
|
436
|
+
// and reprocess so the correct finalization status is reported.
|
|
437
|
+
if (entry.status.canTransitionTo(nextState)) {
|
|
438
|
+
entry.status.transitionTo(nextState);
|
|
439
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
// The backend reports a finalized status but we cannot transition there.
|
|
443
|
+
// If we already hold that same finalized state, this can be a stale
|
|
444
|
+
// `SUBMITTED` response racing with an in-flight finalized update. Do not
|
|
445
|
+
// complete in this case: keep the entry pending so the finalized update
|
|
446
|
+
// can continue retrying on transient backend errors such as
|
|
447
|
+
// `CONCURRENT_UPDATE`.
|
|
448
|
+
if (entry.status.state === nextState) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
// Any other non-transitionable finalized mismatch indicates we've reached
|
|
452
|
+
// (or passed) a terminal state but disagree with the backend's final
|
|
453
|
+
// status. Converge to `Completed` (kept) rather than expiring.
|
|
454
|
+
if (entry.status.state !== QuoteStatusState.Completed &&
|
|
455
|
+
entry.status.state !== QuoteStatusState.Expired) {
|
|
456
|
+
// Genuine discrepancy between our finalized status and the backend's
|
|
457
|
+
// (e.g. we observed the opposite outcome); surface it for visibility,
|
|
458
|
+
// but still complete the entry instead of looping or abandoning it.
|
|
459
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_onError, "f")?.call(this, new QuoteStatusUpdateError(`reporting finalization status but entry cannot transition from "${entry.status.state}" to "${nextState}"`, { quoteId: entry.quoteId }));
|
|
460
|
+
}
|
|
461
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_markCompleted).call(this, entry);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Any non-retryable error we could not reconcile means we cannot make
|
|
466
|
+
// progress on this entry, so abandon it rather than leaving it stuck.
|
|
467
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_markExpired).call(this, entry);
|
|
468
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_onError, "f")?.call(this, new QuoteStatusUpdateError(`abandoning entry due to non-retryable error`, {
|
|
469
|
+
quoteId: entry.quoteId,
|
|
470
|
+
errorType: response?.type,
|
|
471
|
+
}));
|
|
472
|
+
};
|
|
473
|
+
//# sourceMappingURL=quotes-status-manager.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quotes-status-manager.mjs","sourceRoot":"","sources":["../../src/quote-status-manager/quotes-status-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { TransactionStatus } from '@metamask/transaction-controller';\n\nimport { BridgeClientId, BridgeStatusControllerMessenger } from '../types';\nimport {\n getTransactionMetaById,\n hasNestedSwapTransactions,\n isCrossChainTx,\n} from '../utils/transaction';\nimport {\n QuoteStatusState,\n QuoteStatusStateToBackendStatus,\n QuoteStatusUpdateBackendErrorType,\n QuoteStatusBackendStatus,\n QuoteStatusFetchWithRetryOutcomeType,\n} from './constants';\nimport { QuoteStatusUpdateError } from './errors';\nimport { QuoteStatusApiService } from './quote-status-api-service';\nimport { QuoteStatusEntryStore } from './quote-status-entry-store';\nimport { QuoteStatusStateFsm } from './quote-status-state-fsm';\nimport { QuoteStatusUpdateWithRetryOutcome } from './quote-status-update-with-retry-outcome';\nimport { QuoteStatusPersistEntry, QuoteStatusRuntimeEntry } from './types';\n\n/**\n * Tracks bridge/swap quotes through their lifecycle and keeps the backend in\n * sync with the latest known status of each quote.\n *\n * Quotes are reported via {@link reportSubmitted} and {@link reportFinalised},\n * stored as runtime entries, and pushed to the backend. Updates that fail in a\n * retryable way are retried on a periodic timer until each entry reaches a\n * terminal state, at which point it is evicted and the timer stops once the\n * store is empty.\n */\nexport class QuoteStatusManager {\n readonly #messenger: BridgeStatusControllerMessenger;\n\n readonly #quoteStatusApiService: QuoteStatusApiService;\n\n readonly #quoteStatusEntryStore: QuoteStatusEntryStore;\n\n readonly #isEnabled: (() => boolean) | undefined;\n\n readonly #onError: ((error: QuoteStatusUpdateError) => void) | undefined;\n\n readonly #updateIntervalMs: number;\n\n /**\n * Handle for the periodic retry timer. While running, every\n * {@link QUOTE_STATUS_UPDATE_RETRY_INTERVAL_MS} all entries that have not yet\n * reached a terminal state (Completed/Expired) are re-processed. It is started\n * lazily when there is work to do and stopped once the store is empty.\n */\n #retryIntervalId: ReturnType<typeof setInterval> | null = null;\n\n /**\n * Creates a new manager and immediately processes any persisted entries.\n *\n * @param options - Constructor options.\n * @param options.messenger - Messenger used to communicate with the backend\n * API service.\n * @param options.clientId - Identifier of the client making the requests.\n * @param options.clientProduct - Name of the client product making the\n * requests.\n * @param options.clientVersion - Optional version of the client product.\n * @param options.apiBaseUrl - Base URL of the quote status backend API.\n * @param options.onPersistUpdates - Callback invoked to persist entry updates.\n * @param options.onError - Optional callback invoked when a non-recoverable\n * error occurs.\n * @param options.isEnabled - Optional predicate gating whether the manager\n * performs any work.\n * @param options.entryTtlMs - Time-to-live, in milliseconds, after which a\n * tracked entry is evicted.\n * @param options.updateIntervalMs - How often the manager re-processes entries that\n * have not yet reached a terminal state\n * @param options.initialData - Persisted entries to rehydrate on startup.\n */\n constructor({\n messenger,\n clientId,\n clientProduct,\n clientVersion,\n apiBaseUrl,\n onError,\n isEnabled,\n onPersistUpdates,\n entryTtlMs,\n updateIntervalMs,\n initialData,\n }: {\n messenger: BridgeStatusControllerMessenger;\n clientId: BridgeClientId;\n clientProduct: string;\n clientVersion?: string;\n apiBaseUrl: string;\n onPersistUpdates: (\n updates: Record<string, QuoteStatusPersistEntry>,\n ) => void;\n onError?: (error: QuoteStatusUpdateError) => void;\n isEnabled?: () => boolean;\n entryTtlMs: number;\n updateIntervalMs: number;\n initialData: Record<string, QuoteStatusPersistEntry>;\n }) {\n this.#isEnabled = isEnabled;\n this.#onError = onError;\n this.#updateIntervalMs = updateIntervalMs;\n this.#messenger = messenger;\n\n this.#quoteStatusApiService = new QuoteStatusApiService({\n messenger,\n clientId,\n clientProduct,\n clientVersion,\n apiBaseUrl,\n onError,\n });\n\n this.#quoteStatusEntryStore = new QuoteStatusEntryStore({\n onPersistUpdates,\n entryTtlMs,\n initial: initialData,\n });\n }\n\n /**\n * Whether quote-status tracking and backend sync are currently active.\n *\n * Reflects the latest value returned by the optional `isEnabled` predicate\n * supplied at construction time. When no predicate was provided, this is\n * always `false` and all public methods that gate on enablement no-op.\n *\n * @returns `true` when the `isEnabled` predicate returns a truthy value.\n */\n get enabled(): boolean {\n return Boolean(this.#isEnabled?.());\n }\n\n /**\n * Reports that a previously submitted quote has finalized on-chain.\n *\n * Looks up the tracked entry by its transaction metadata id, transitions it to\n * the appropriate terminal state, and processes the update. No-ops when the\n * manager is disabled, and surfaces an error when the entry is missing or\n * cannot transition to the finalized state.\n *\n * @param txMetaId - Transaction metadata id of the finalized quote.\n * @param success - Whether the transaction finalized successfully.\n */\n reportFinalised(txMetaId: string, success: boolean): void {\n if (!this.#isEnabled?.()) {\n return;\n }\n\n const entry = this.#quoteStatusEntryStore.getByTxMetaId(txMetaId);\n\n if (!entry) {\n this.#onError?.(\n new QuoteStatusUpdateError(\n 'reporting finalization status but entry was not found',\n { quoteId: '' },\n ),\n );\n return;\n }\n\n const nextState = success\n ? QuoteStatusState.FinalizedSuccess\n : QuoteStatusState.FinalizedFailed;\n\n if (!entry.status.canTransitionTo(nextState)) {\n // This is expected, there are race conditions where\n // reportFinalized can be called twice. If the second\n // call fails due to the first completed sucesfully\n // backend will report that we cannot transition outside\n // a final state, which is correct and we can safely abort\n // the flow.\n return;\n }\n\n entry.status.transitionTo(nextState);\n this.#ensureRetryTimerRunning();\n this.#processEntry(entry);\n }\n\n /**\n * Reports that a quote has been submitted on-chain and begins tracking it.\n *\n * Creates a new entry in the `Submitted` state, starts the retry timer, and\n * processes the initial status update. No-ops when the manager is disabled,\n * and surfaces an error if the entry cannot be retrieved after being stored.\n *\n * @param quoteId - Identifier of the submitted quote.\n * @param srcTxHash - Hash of the source-chain transaction for the quote.\n * @param txMetaId - Optional transaction metadata id used to correlate\n * finalization reports.\n */\n reportSubmitted(quoteId: string, srcTxHash: string, txMetaId?: string): void {\n if (!this.#isEnabled?.()) {\n return;\n }\n\n // Once a quote has advanced past `Submitted` (finalized or terminal), it is\n // done: reporting `SUBMITTED` again would be rejected by the backend as an\n // invalid transition. Retained terminal entries let us recognize and drop\n // these late/duplicate submissions instead of looping on a 400.\n const isQuoteAlreadyTracked = this.#quoteStatusEntryStore\n .getByQuoteId(quoteId)\n .some((entry) => entry.status.state !== QuoteStatusState.Submitted);\n if (isQuoteAlreadyTracked) {\n return;\n }\n\n const entryKey = QuoteStatusEntryStore.hash({\n quoteId,\n srcTxHash,\n });\n\n const entry = this.#quoteStatusEntryStore.put(entryKey, {\n quoteId,\n srcTxHash,\n txMetaId,\n status: new QuoteStatusStateFsm(QuoteStatusState.Submitted),\n });\n\n this.#ensureRetryTimerRunning();\n this.#processEntry(entry);\n }\n\n /**\n * Tears down the manager by stopping the retry timer and clearing all tracked\n * entries.\n */\n destroy(): void {\n this.#stopRetryTimer();\n this.#quoteStatusEntryStore.clear();\n }\n\n /**\n * Reconciles quote-status entries whose finalization was missed while the\n * client was closed. Called once during initialization.\n *\n * Swap/bridge source transactions report their final status through the\n * `TransactionController:transactionStatusUpdated` subscription\n * ({@link #onTransactionConfirmed}/{@link #onTransactionFailed}). When a source\n * transaction reaches a terminal state while the client is not running, that\n * event is never re-emitted on the next startup, so the persisted entry would\n * remain `Submitted` until it expires via TTL. This replays the missed terminal\n * event for both swaps and bridges: a confirmed source transaction is reported\n * as a finalized success and a failed/dropped one as a finalized failure.\n *\n * `rejected` is ignored because the transaction was never broadcast.\n */\n init(): void {\n this.#processInitial();\n\n for (const entry of this.#quoteStatusEntryStore.values()) {\n // Only entries still awaiting finalization need catching up. Entries\n // already in a finalized state are re-sent by `processInitial()`.\n if (\n entry.status.state !== QuoteStatusState.Submitted ||\n !entry.txMetaId\n ) {\n continue;\n }\n\n const txMeta = getTransactionMetaById(this.#messenger, entry.txMetaId);\n if (!txMeta) {\n continue;\n }\n\n // Reconcile swaps and bridges alike: a terminal source transaction\n // finalizes the quote status. `hasNestedSwapTransactions` also covers\n // batch/7702 swaps whose type may still read as `batch` rather than `swap`.\n const isCrossChainTrade =\n (txMeta.type !== undefined && isCrossChainTx(txMeta.type)) ||\n hasNestedSwapTransactions(txMeta);\n if (!isCrossChainTrade) {\n continue;\n }\n\n if (txMeta.status === TransactionStatus.confirmed) {\n this.reportFinalised(entry.txMetaId, true);\n } else if (\n txMeta.status === TransactionStatus.failed ||\n txMeta.status === TransactionStatus.dropped\n ) {\n this.reportFinalised(entry.txMetaId, false);\n }\n }\n }\n\n /**\n * Fetches the current quote status from the backend with automatic retries.\n *\n * Unlike {@link reportSubmitted} and {@link reportFinalised}, this is a\n * read-only query and does not mutate tracked entries. Returns `null` when\n * the manager is disabled ({@link enabled} is `false`).\n *\n * @param quoteId - Identifier of the quote whose status should be fetched.\n * @param options - Retry configuration.\n * @param options.maxRetries - Maximum number of retries after the initial attempt.\n * @param options.delayMsBetweenRetries - Delay in milliseconds between attempts.\n */\n getStatus(\n quoteId: string,\n options: {\n maxRetries?: number;\n delayMsBetweenRetries?: number;\n } = {\n maxRetries: 0,\n delayMsBetweenRetries: 1000,\n },\n ): void {\n if (!this.#isEnabled?.()) {\n return;\n }\n\n this.#quoteStatusApiService\n .getQuoteStatusWithRetry(\n {\n quoteId,\n },\n {\n maxRetries: options.maxRetries ?? 0,\n delayMsBetweenRetries: options.delayMsBetweenRetries ?? 1000,\n },\n // Errors already reported by #onError handlers of `getQuoteStatusWithRetry()`\n )\n .catch(() => undefined);\n }\n\n /**\n * Processes every entry rehydrated from persisted data on startup and starts\n * the retry timer if any entries still require further updates.\n */\n #processInitial(): void {\n for (const entry of this.#quoteStatusEntryStore.values()) {\n this.#processEntry(entry);\n }\n\n // Terminal entries (Completed/Expired) are retained, so the store is never\n // empty. Only start the retry timer when there is an entry whose status\n // still needs to be reported; otherwise it would tick forever doing nothing.\n if (this.#hasPendingUpdates()) {\n this.#ensureRetryTimerRunning();\n }\n }\n\n /**\n * Starts the periodic retry timer if it is not already running.\n *\n * The timer re-processes every non-terminal entry on each tick. It is\n * idempotent so callers can invoke it freely whenever new work is enqueued.\n */\n #ensureRetryTimerRunning(): void {\n if (this.#retryIntervalId !== null) {\n return;\n }\n\n this.#retryIntervalId = setInterval(\n () => this.#processRetries(),\n this.#updateIntervalMs,\n );\n }\n\n /**\n * Stops the periodic retry timer if it is running.\n */\n #stopRetryTimer(): void {\n if (this.#retryIntervalId !== null) {\n clearInterval(this.#retryIntervalId);\n this.#retryIntervalId = null;\n }\n }\n\n /**\n * Retry tick: re-processes every entry. Reading `values()` first transitions\n * TTL-expired entries to `Expired` (keeping them), and `#processEntry` no-ops\n * for terminal/acknowledged entries. When no entry has a status left to report\n * the timer stops until new work is enqueued.\n */\n #processRetries(): void {\n if (!this.#isEnabled?.()) {\n return;\n }\n\n // Snapshot first: `#processEntry` can mutate the store (e.g. transitioning an\n // accepted entry to a terminal state), so iterating a live iterator would be\n // unsafe.\n const entries = [...this.#quoteStatusEntryStore.values()];\n\n if (!this.#hasPendingUpdates()) {\n this.#stopRetryTimer();\n return;\n }\n\n for (const entry of entries) {\n this.#processEntry(entry);\n }\n }\n\n /**\n * Returns whether any tracked entry still has a status that needs to be\n * reported to the backend.\n *\n * Terminal entries (`Completed`/`Expired`) and entries whose current status\n * has already been acknowledged are excluded, so this is the signal used to\n * decide whether the retry timer has any work left to do.\n *\n * @returns `true` when at least one entry has an unreported status.\n */\n #hasPendingUpdates(): boolean {\n for (const entry of this.#quoteStatusEntryStore.values()) {\n const { state } = entry.status;\n const isTerminal =\n state === QuoteStatusState.Completed ||\n state === QuoteStatusState.Expired;\n if (!isTerminal && entry.acknowledgedState !== state) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Stops the retry timer when there are no entries left whose status needs\n * reporting. Since terminal entries are retained, the store is never empty, so\n * the timer's idle condition is \"no pending updates\" rather than \"no entries\".\n */\n #stopRetryTimerIfIdle(): void {\n if (!this.#hasPendingUpdates()) {\n this.#stopRetryTimer();\n }\n }\n\n /**\n * Pushes a single entry's current status to the backend and reconciles the\n * local state with the request outcome.\n *\n * Terminal entries are kept but no longer reported. Accepted finalized updates\n * advance the entry to `Completed` (kept so duplicate reports are rejected);\n * accepted non-final updates (e.g. `Submitted`) are kept tracked so a later\n * {@link reportFinalised} can find them, while being flagged so the retry loop\n * stops re-sending the acknowledged status. Updates whose status advanced\n * mid-flight are reprocessed, retryable failures are left for the next retry\n * tick, and non-retryable failures are delegated to\n * {@link #handleNonRetryableUpdateStatusError}.\n *\n * @param entry - The runtime entry to process.\n */\n #processEntry(entry: QuoteStatusRuntimeEntry): void {\n // The backend already accepted the entry's current status, so there is\n // nothing new to report. The entry is kept tracked (e.g. a `Submitted`\n // quote awaiting finalization) until its status advances past the\n // acknowledged one, at which point it is reprocessed.\n if (entry.acknowledgedState === entry.status.state) {\n return;\n }\n\n const sentStatus = entry.status.state;\n const sentStatusBackend = QuoteStatusStateToBackendStatus[sentStatus];\n\n // Terminal states (`Completed`/`Expired`) have no backend status to report.\n // The entry is retained so future interactions with the quote are rejected.\n if (sentStatusBackend === null) {\n this.#stopRetryTimerIfIdle();\n return;\n }\n\n // Re-checked before each retry attempt so an in-flight retry stops early when\n // the entry has since advanced, been acknowledged, become terminal, or\n // expired (a later `reportFinalised` re-triggers processing). This avoids\n // firing a request the backend would reject (e.g. `SUBMITTED` after\n // finalization).\n const retry = (): boolean => {\n const live = this.#quoteStatusEntryStore.get(\n QuoteStatusEntryStore.hash(entry),\n );\n return Boolean(\n live &&\n live.status.state === sentStatus &&\n live.acknowledgedState !== sentStatus,\n );\n };\n\n this.#quoteStatusApiService\n .updateQuoteStatusWithRetry(\n {\n quoteId: entry.quoteId,\n srcTxHash: entry.srcTxHash,\n newStatus: sentStatusBackend,\n },\n {\n maxRetries: 5,\n delayMsBetweenRetries: 3000,\n retry,\n },\n )\n .then((outcome) => {\n switch (outcome.type) {\n case QuoteStatusFetchWithRetryOutcomeType.Accepted: {\n const current = this.#quoteStatusEntryStore.get(\n QuoteStatusEntryStore.hash(entry),\n );\n // The entry can only be absent if the store was cleared (e.g. via\n // `destroy`) while the request was in flight; nothing left to do.\n if (!current) {\n return undefined;\n }\n if (current.status.state !== sentStatus) {\n // The status advanced mid-flight; report the newer status.\n this.#processEntry(current);\n return undefined;\n }\n if (\n sentStatus === QuoteStatusState.FinalizedSuccess ||\n sentStatus === QuoteStatusState.FinalizedFailed\n ) {\n // A finalized status was accepted; advance to the terminal\n // `Completed` state and keep the entry so any later duplicate\n // `reportSubmitted` for this quote is rejected instead of looping.\n this.#markCompleted(current);\n // Call getStatus to complete the flow on the backend.\n // We discard any value for now, this is used purely\n // to notify backend that the flow is complete.\n this.getStatus(entry.quoteId, { maxRetries: 1 });\n return undefined;\n }\n // A non-final status (e.g. `Submitted`) was accepted. The quote is\n // not done yet: it still needs to be finalized via a later\n // `reportFinalised`, which looks the entry up by `txMetaId`. Keep\n // the entry tracked and record the acknowledgement so the retry\n // loop stops re-sending the already-accepted status.\n current.acknowledgedState = sentStatus;\n this.#quoteStatusEntryStore.update(current);\n this.#stopRetryTimerIfIdle();\n return undefined;\n }\n case QuoteStatusFetchWithRetryOutcomeType.NonRetryable:\n this.#handleNonRetryableUpdateStatusError(entry, outcome);\n return undefined;\n case QuoteStatusFetchWithRetryOutcomeType.Interrupted:\n this.#processEntry(entry);\n return undefined;\n case QuoteStatusFetchWithRetryOutcomeType.RetryableExhausted:\n entry.lastAttemptAt = Date.now();\n this.#quoteStatusEntryStore.update(entry);\n return undefined;\n default:\n return undefined;\n }\n })\n .catch(() => {\n entry.lastAttemptAt = Date.now();\n this.#quoteStatusEntryStore.update(entry);\n });\n }\n\n /**\n * Advances an entry to the terminal `Completed` state and stops the retry\n * timer if no work remains. The entry is kept in the store so later duplicate\n * reports for the same quote are recognized and rejected.\n *\n * @param entry - The runtime entry to complete.\n * @param finalizedState - Optional finalized state the backend reports as\n * current. When provided and reachable, the entry is first advanced through it\n * so the FSM's forward-only path (`Submitted -> FinalizedX -> Completed`) is\n * respected before reaching `Completed`.\n */\n #markCompleted(\n entry: QuoteStatusRuntimeEntry,\n finalizedState?: QuoteStatusState,\n ): void {\n if (finalizedState && entry.status.canTransitionTo(finalizedState)) {\n entry.status.transitionTo(finalizedState);\n }\n\n if (entry.status.canTransitionTo(QuoteStatusState.Completed)) {\n entry.status.transitionTo(QuoteStatusState.Completed);\n }\n\n this.#stopRetryTimerIfIdle();\n }\n\n /**\n * Advances an entry to the terminal `Expired` state (abandoning it) and stops\n * the retry timer if no work remains. The entry is kept in the store so later\n * interactions with the same quote are rejected.\n *\n * @param entry - The runtime entry to abandon.\n */\n #markExpired(entry: QuoteStatusRuntimeEntry): void {\n if (entry.status.canTransitionTo(QuoteStatusState.Expired)) {\n entry.status.transitionTo(QuoteStatusState.Expired);\n }\n\n this.#stopRetryTimerIfIdle();\n }\n\n /**\n * Reconciles local state in response to a non-retryable backend error.\n *\n * When the backend reports a terminal status, the entry is either advanced to\n * match and reprocessed (if it is behind) or converged to `Completed` (if it\n * is already finalized), surfacing an error only for a genuine status\n * discrepancy. Other non-retryable errors that cannot be reconciled mark the\n * entry `Expired` (abandoned). Entries are always kept in the store so future\n * interactions with the quote are rejected rather than looping.\n *\n * @param entry - The runtime entry whose update failed.\n * @param outcome - The non-retryable outcome returned by the API service,\n * including the backend response used for reconciliation.\n */\n #handleNonRetryableUpdateStatusError(\n entry: QuoteStatusRuntimeEntry,\n outcome: QuoteStatusUpdateWithRetryOutcome,\n ): void {\n const { response } = outcome;\n\n const backendFinalizedToState: Partial<\n Record<QuoteStatusBackendStatus, QuoteStatusState>\n > = {\n [QuoteStatusBackendStatus.FinalizedSuccess]:\n QuoteStatusState.FinalizedSuccess,\n [QuoteStatusBackendStatus.FinalizedFailed]:\n QuoteStatusState.FinalizedFailed,\n };\n\n // The transition we requested is invalid because the backend is already in a\n // terminal state (e.g. we re-sent `SUBMITTED` after finalization). There is\n // nothing left to report, so advance the entry to `Completed` and keep it.\n if (\n response?.type ===\n QuoteStatusUpdateBackendErrorType.InvalidStatusTransaction &&\n (response.currentStatus === QuoteStatusBackendStatus.FinalizedSuccess ||\n response.currentStatus === QuoteStatusBackendStatus.FinalizedFailed)\n ) {\n this.#markCompleted(\n entry,\n backendFinalizedToState[response.currentStatus],\n );\n return;\n }\n\n // For mismatch errors the backend reports the status it currently has, which\n // lets us reconcile our local state. The discriminant check also narrows\n // `response` to the variant carrying `currentStatus`.\n if (\n response?.type ===\n QuoteStatusUpdateBackendErrorType.InvalidStatusTransaction ||\n response?.type ===\n QuoteStatusUpdateBackendErrorType.QuoteStatusOnChainMismatch\n ) {\n const nextState = backendFinalizedToState[response.currentStatus];\n\n if (nextState) {\n // We are behind the backend's terminal status; advance our local state\n // and reprocess so the correct finalization status is reported.\n if (entry.status.canTransitionTo(nextState)) {\n entry.status.transitionTo(nextState);\n this.#processEntry(entry);\n return;\n }\n\n // The backend reports a finalized status but we cannot transition there.\n // If we already hold that same finalized state, this can be a stale\n // `SUBMITTED` response racing with an in-flight finalized update. Do not\n // complete in this case: keep the entry pending so the finalized update\n // can continue retrying on transient backend errors such as\n // `CONCURRENT_UPDATE`.\n if (entry.status.state === nextState) {\n return;\n }\n\n // Any other non-transitionable finalized mismatch indicates we've reached\n // (or passed) a terminal state but disagree with the backend's final\n // status. Converge to `Completed` (kept) rather than expiring.\n if (\n entry.status.state !== QuoteStatusState.Completed &&\n entry.status.state !== QuoteStatusState.Expired\n ) {\n // Genuine discrepancy between our finalized status and the backend's\n // (e.g. we observed the opposite outcome); surface it for visibility,\n // but still complete the entry instead of looping or abandoning it.\n this.#onError?.(\n new QuoteStatusUpdateError(\n `reporting finalization status but entry cannot transition from \"${entry.status.state}\" to \"${nextState}\"`,\n { quoteId: entry.quoteId },\n ),\n );\n }\n this.#markCompleted(entry);\n return;\n }\n }\n\n // Any non-retryable error we could not reconcile means we cannot make\n // progress on this entry, so abandon it rather than leaving it stuck.\n this.#markExpired(entry);\n this.#onError?.(\n new QuoteStatusUpdateError(\n `abandoning entry due to non-retryable error`,\n {\n quoteId: entry.quoteId,\n errorType: response?.type,\n },\n ),\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const bridge_controller_1 = require("@metamask/bridge-controller");
|
|
4
|
+
const constants_1 = require("./constants.cjs");
|
|
5
|
+
const errors_1 = require("./errors.cjs");
|
|
6
|
+
const quote_status_state_fsm_1 = require("./quote-status-state-fsm.cjs");
|
|
7
|
+
const validators_1 = require("./validators.cjs");
|
|
8
|
+
//# sourceMappingURL=types.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/quote-status-manager/types.ts"],"names":[],"mappings":";;AAAA,mEAA6D;AAI7D,+CAGqB;AACrB,yCAAuE;AACvE,yEAA+D;AAC/D,iDAGsB","sourcesContent":["import { BridgeClientId } from '@metamask/bridge-controller';\nimport { Infer } from '@metamask/superstruct';\n\nimport { BridgeStatusControllerMessenger } from '../types';\nimport {\n QuoteStatusState,\n QuoteStatusUpdateBackendErrorType,\n} from './constants';\nimport { QuoteStatusGetError, QuoteStatusUpdateError } from './errors';\nimport { QuoteStatusStateFsm } from './quote-status-state-fsm';\nimport {\n QuoteStatusGetResponseSchema,\n QuoteStatusUpdateResponseSchema,\n} from './validators';\n\n/**\n * Persisted queue entry describing a single quote status update attempt.\n */\nexport type QuoteStatusPersistEntry = {\n /**\n * Unique quote identifier.\n */\n quoteId: string;\n\n /**\n * Source transaction hash used to correlate updates.\n */\n srcTxHash: string;\n\n /**\n * Current persisted status lifecycle value.\n */\n status: QuoteStatusState;\n\n /**\n * Timestamp in milliseconds when the entry was first created.\n */\n createdAt: number;\n\n /**\n * Timestamp in milliseconds for the most recent attempt.\n */\n lastAttemptAt: number;\n\n /**\n * Optional transaction metadata identifier assigned after submission.\n */\n txMetaId?: string;\n\n /**\n * The lifecycle status most recently accepted (2xx) by the backend, if any.\n *\n * Used to avoid redundantly re-sending a status the backend has already\n * acknowledged. In particular, a `Submitted` entry stays tracked while it\n * awaits finalization, and this flag stops the retry loop from re-sending the\n * already-accepted `SUBMITTED` update (which the backend would reject as an\n * invalid/duplicate transition). It is implicitly superseded once the status\n * advances past it (e.g. via {@link QuoteStatusUpdateManager.reportFinalised}).\n */\n acknowledgedState?: QuoteStatusState;\n};\n\n/**\n * In-memory queue entry with an FSM instance for status transitions.\n */\nexport type QuoteStatusRuntimeEntry = Omit<\n QuoteStatusPersistEntry,\n 'status'\n> & {\n /**\n * Runtime status FSM used to validate and apply transitions.\n */\n status: QuoteStatusStateFsm;\n};\n\n/**\n * Validated non-`2xx` response payload from the quote update status API.\n */\nexport type QuoteStatusUpdateResponse = Infer<\n typeof QuoteStatusUpdateResponseSchema\n>;\n\n/**\n * Validated non-`2xx` response payload from the quote get status API.\n */\nexport type QuoteStatusGetResponse = Infer<typeof QuoteStatusGetResponseSchema>;\n\n/**\n * Options required to create quote status API service instances.\n */\nexport type QuoteStatusApiServiceOptions = {\n /**\n * Messenger used to retrieve the authentication token.\n */\n messenger: BridgeStatusControllerMessenger;\n\n /**\n * Bridge client identifier used for request headers.\n */\n clientId: BridgeClientId;\n\n /**\n * Product name sent as the `x-metamask-clientproduct` header.\n */\n clientProduct: string;\n\n /**\n * Optional client version sent as the `x-metamask-clientversion` header.\n */\n clientVersion?: string;\n\n /**\n * Base URL for the quote status API.\n */\n apiBaseUrl: string;\n\n /**\n * Optional callback invoked when an unexpected error response shape is returned.\n */\n onError?: (error: QuoteStatusUpdateError | QuoteStatusGetError) => void;\n};\n\n/**\n * Context information attached to a {@link QuoteStatusUpdateError}.\n */\nexport type QuoteStatusUpdateErrorDetails = {\n /**\n * Unique quote identifier associated with the error.\n */\n quoteId: string;\n\n /**\n * Optional error type used to categorize known quote update failures.\n */\n errorType?: QuoteStatusUpdateBackendErrorType;\n};\n\n/**\n * Context information attached to a {@link QuoteStatusGetError}.\n */\nexport type QuoteStatusGetErrorDetails = {\n /**\n * Unique quote identifier associated with the error.\n */\n quoteId: string;\n\n validationFailures?: string[];\n};\n\n/**\n * Construction options for quote status entry store instances.\n */\nexport type QuoteStatusEntryStoreOptions = {\n /**\n * Callback used to persist the current in-memory queue snapshot.\n */\n onPersistUpdates: (updates: Record<string, QuoteStatusPersistEntry>) => void;\n /**\n * Entry time-to-live in milliseconds before automatic eviction.\n */\n entryTtlMs: number;\n\n /**\n * Optional initial persisted entries used to seed the store.\n */\n initial?: Record<string, QuoteStatusPersistEntry>;\n};\n\n/**\n * Event payload emitted whenever the FSM state changes.\n */\nexport type QuoteStatusStateUpdateEvent = {\n previousState: QuoteStatusState;\n nextState: QuoteStatusState;\n};\n\n/**\n * Listener signature for quote-status state changes.\n */\nexport type QuoteStatusStateUpdateListener = (\n event: QuoteStatusStateUpdateEvent,\n) => void;\n"]}
|