@metamask/bridge-status-controller 74.2.0 → 74.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/dist/bridge-status-controller.cjs +32 -4
- package/dist/bridge-status-controller.cjs.map +1 -1
- package/dist/bridge-status-controller.d.cts.map +1 -1
- package/dist/bridge-status-controller.d.mts.map +1 -1
- package/dist/bridge-status-controller.mjs +32 -4
- package/dist/bridge-status-controller.mjs.map +1 -1
- package/dist/quote-status-manager/quote-status-entry-store.cjs +22 -0
- package/dist/quote-status-manager/quote-status-entry-store.cjs.map +1 -1
- package/dist/quote-status-manager/quote-status-entry-store.d.cts +13 -0
- package/dist/quote-status-manager/quote-status-entry-store.d.cts.map +1 -1
- package/dist/quote-status-manager/quote-status-entry-store.d.mts +13 -0
- package/dist/quote-status-manager/quote-status-entry-store.d.mts.map +1 -1
- package/dist/quote-status-manager/quote-status-entry-store.mjs +22 -0
- package/dist/quote-status-manager/quote-status-entry-store.mjs.map +1 -1
- package/dist/quote-status-manager/quotes-status-manager.cjs +26 -12
- package/dist/quote-status-manager/quotes-status-manager.cjs.map +1 -1
- package/dist/quote-status-manager/quotes-status-manager.d.cts +4 -1
- package/dist/quote-status-manager/quotes-status-manager.d.cts.map +1 -1
- package/dist/quote-status-manager/quotes-status-manager.d.mts +4 -1
- package/dist/quote-status-manager/quotes-status-manager.d.mts.map +1 -1
- package/dist/quote-status-manager/quotes-status-manager.mjs +26 -12
- package/dist/quote-status-manager/quotes-status-manager.mjs.map +1 -1
- package/dist/strategy/batch-sell-strategy.cjs +10 -1
- package/dist/strategy/batch-sell-strategy.cjs.map +1 -1
- package/dist/strategy/batch-sell-strategy.d.cts.map +1 -1
- package/dist/strategy/batch-sell-strategy.d.mts.map +1 -1
- package/dist/strategy/batch-sell-strategy.mjs +10 -1
- package/dist/strategy/batch-sell-strategy.mjs.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +9 -0
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +9 -0
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/package.json +3 -1
|
@@ -80,6 +80,19 @@ export declare class QuoteStatusEntryStore {
|
|
|
80
80
|
* @returns The matching entry, or `null` if none exists.
|
|
81
81
|
*/
|
|
82
82
|
getByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry | null;
|
|
83
|
+
/**
|
|
84
|
+
* Looks up every entry sharing the given transaction metadata identifier.
|
|
85
|
+
*
|
|
86
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
87
|
+
* source tx hash and one `txMetaId`, producing several entries that must all
|
|
88
|
+
* be finalized together. Each matching entry has its TTL checked (and is
|
|
89
|
+
* transitioned to {@link QuoteStatusState.Expired} if stale) before being
|
|
90
|
+
* returned.
|
|
91
|
+
*
|
|
92
|
+
* @param txMetaId - Transaction metadata identifier to search for.
|
|
93
|
+
* @returns The matching entries (empty when none exist).
|
|
94
|
+
*/
|
|
95
|
+
getAllByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry[];
|
|
83
96
|
/**
|
|
84
97
|
* Retrieves an entry by key.
|
|
85
98
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quote-status-entry-store.d.cts","sourceRoot":"","sources":["../../src/quote-status-manager/quote-status-entry-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACxB,oBAAgB;AAEjB;;;;;GAKG;AACH,qBAAa,qBAAqB;;IAKhC;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,GAAG,WAAW,CAAC,GAC5D,MAAM;IAQT;;;;;;;OAOG;gBACS,EACV,gBAAgB,EAChB,UAAU,EACV,OAAO,GACR,EAAE,4BAA4B;IAqC/B;;;;;;;;;;;;;;OAcG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,WAAW,GAAG,eAAe,CAAC,GAClE,uBAAuB;IAyB1B;;;;;;;OAOG;IACH,MAAM,IAAI,gBAAgB,CAAC,uBAAuB,CAAC;IAKnD;;;;;;;;OAQG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE;IAaxD;;;;;;;;OAQG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;OAQG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAW/D;;;;;;;;OAQG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAUhD;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI;IAuB5C;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAQ1B;;;;;;;;OAQG;IACH,eAAe,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAKxD;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAS3D;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAOd"}
|
|
1
|
+
{"version":3,"file":"quote-status-entry-store.d.cts","sourceRoot":"","sources":["../../src/quote-status-manager/quote-status-entry-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACxB,oBAAgB;AAEjB;;;;;GAKG;AACH,qBAAa,qBAAqB;;IAKhC;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,GAAG,WAAW,CAAC,GAC5D,MAAM;IAQT;;;;;;;OAOG;gBACS,EACV,gBAAgB,EAChB,UAAU,EACV,OAAO,GACR,EAAE,4BAA4B;IAqC/B;;;;;;;;;;;;;;OAcG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,WAAW,GAAG,eAAe,CAAC,GAClE,uBAAuB;IAyB1B;;;;;;;OAOG;IACH,MAAM,IAAI,gBAAgB,CAAC,uBAAuB,CAAC;IAKnD;;;;;;;;OAQG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE;IAaxD;;;;;;;;OAQG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;OAQG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAW/D;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,EAAE;IAY7D;;;;;;;;OAQG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAUhD;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI;IAuB5C;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAQ1B;;;;;;;;OAQG;IACH,eAAe,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAKxD;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAS3D;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAOd"}
|
|
@@ -80,6 +80,19 @@ export declare class QuoteStatusEntryStore {
|
|
|
80
80
|
* @returns The matching entry, or `null` if none exists.
|
|
81
81
|
*/
|
|
82
82
|
getByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry | null;
|
|
83
|
+
/**
|
|
84
|
+
* Looks up every entry sharing the given transaction metadata identifier.
|
|
85
|
+
*
|
|
86
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
87
|
+
* source tx hash and one `txMetaId`, producing several entries that must all
|
|
88
|
+
* be finalized together. Each matching entry has its TTL checked (and is
|
|
89
|
+
* transitioned to {@link QuoteStatusState.Expired} if stale) before being
|
|
90
|
+
* returned.
|
|
91
|
+
*
|
|
92
|
+
* @param txMetaId - Transaction metadata identifier to search for.
|
|
93
|
+
* @returns The matching entries (empty when none exist).
|
|
94
|
+
*/
|
|
95
|
+
getAllByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry[];
|
|
83
96
|
/**
|
|
84
97
|
* Retrieves an entry by key.
|
|
85
98
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quote-status-entry-store.d.mts","sourceRoot":"","sources":["../../src/quote-status-manager/quote-status-entry-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACxB,oBAAgB;AAEjB;;;;;GAKG;AACH,qBAAa,qBAAqB;;IAKhC;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,GAAG,WAAW,CAAC,GAC5D,MAAM;IAQT;;;;;;;OAOG;gBACS,EACV,gBAAgB,EAChB,UAAU,EACV,OAAO,GACR,EAAE,4BAA4B;IAqC/B;;;;;;;;;;;;;;OAcG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,WAAW,GAAG,eAAe,CAAC,GAClE,uBAAuB;IAyB1B;;;;;;;OAOG;IACH,MAAM,IAAI,gBAAgB,CAAC,uBAAuB,CAAC;IAKnD;;;;;;;;OAQG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE;IAaxD;;;;;;;;OAQG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;OAQG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAW/D;;;;;;;;OAQG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAUhD;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI;IAuB5C;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAQ1B;;;;;;;;OAQG;IACH,eAAe,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAKxD;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAS3D;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAOd"}
|
|
1
|
+
{"version":3,"file":"quote-status-entry-store.d.mts","sourceRoot":"","sources":["../../src/quote-status-manager/quote-status-entry-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACxB,oBAAgB;AAEjB;;;;;GAKG;AACH,qBAAa,qBAAqB;;IAKhC;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,GAAG,WAAW,CAAC,GAC5D,MAAM;IAQT;;;;;;;OAOG;gBACS,EACV,gBAAgB,EAChB,UAAU,EACV,OAAO,GACR,EAAE,4BAA4B;IAqC/B;;;;;;;;;;;;;;OAcG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,WAAW,GAAG,eAAe,CAAC,GAClE,uBAAuB;IAyB1B;;;;;;;OAOG;IACH,MAAM,IAAI,gBAAgB,CAAC,uBAAuB,CAAC;IAKnD;;;;;;;;OAQG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE;IAaxD;;;;;;;;OAQG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;OAQG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAW/D;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,EAAE;IAY7D;;;;;;;;OAQG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAUhD;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI;IAuB5C;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAQ1B;;;;;;;;OAQG;IACH,eAAe,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAKxD;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAS3D;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;CAOd"}
|
|
@@ -162,6 +162,28 @@ export class QuoteStatusEntryStore {
|
|
|
162
162
|
}
|
|
163
163
|
return null;
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Looks up every entry sharing the given transaction metadata identifier.
|
|
167
|
+
*
|
|
168
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
169
|
+
* source tx hash and one `txMetaId`, producing several entries that must all
|
|
170
|
+
* be finalized together. Each matching entry has its TTL checked (and is
|
|
171
|
+
* transitioned to {@link QuoteStatusState.Expired} if stale) before being
|
|
172
|
+
* returned.
|
|
173
|
+
*
|
|
174
|
+
* @param txMetaId - Transaction metadata identifier to search for.
|
|
175
|
+
* @returns The matching entries (empty when none exist).
|
|
176
|
+
*/
|
|
177
|
+
getAllByTxMetaId(txMetaId) {
|
|
178
|
+
const matches = [];
|
|
179
|
+
for (const entry of __classPrivateFieldGet(this, _QuoteStatusEntryStore_items, "f").values()) {
|
|
180
|
+
if (entry.txMetaId === txMetaId) {
|
|
181
|
+
this.expireEntryIfStale(entry);
|
|
182
|
+
matches.push(entry);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return matches;
|
|
186
|
+
}
|
|
165
187
|
/**
|
|
166
188
|
* Retrieves an entry by key.
|
|
167
189
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quote-status-entry-store.mjs","sourceRoot":"","sources":["../../src/quote-status-manager/quote-status-entry-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { QuoteStatusState } from './constants';\nimport { QuoteStatusStateFsm } from './quote-status-state-fsm';\nimport {\n QuoteStatusEntryStoreOptions,\n QuoteStatusPersistEntry,\n QuoteStatusRuntimeEntry,\n} from './types';\n\n/**\n * In-memory store for quote status update entries.\n *\n * The store deduplicates entries by key, tracks timestamps used for retries and\n * TTL eviction, and persists a cloned snapshot on each mutating operation.\n */\nexport class QuoteStatusEntryStore {\n readonly #items: Map<string, QuoteStatusRuntimeEntry>;\n\n readonly #entryTtlMs: number;\n\n /**\n * Creates a deterministic key for persisted quote status entries.\n *\n * @param entry - Entry identity fields.\n * @param entry.quoteId - Quote identifier.\n * @param entry.srcTxHash - Source transaction hash.\n * @returns Stable key in `${quoteId}:${srcTxHash}` format.\n */\n static hash(\n entry: Pick<QuoteStatusPersistEntry, 'quoteId' | 'srcTxHash'>,\n ): string {\n return `${entry.quoteId}:${entry.srcTxHash}`;\n }\n\n readonly #onPersistUpdates: (\n updates: Record<string, QuoteStatusPersistEntry>,\n ) => void;\n\n /**\n * Creates a quote status entry store.\n *\n * @param options - Store dependencies and retention configuration.\n * @param options.onPersistUpdates - Callback invoked with cloned snapshot updates.\n * @param options.entryTtlMs - Entry time-to-live in milliseconds.\n * @param options.initial - Optional initial persisted entries used to seed the store.\n */\n constructor({\n onPersistUpdates,\n entryTtlMs,\n initial,\n }: QuoteStatusEntryStoreOptions) {\n this.#onPersistUpdates = onPersistUpdates;\n this.#entryTtlMs = entryTtlMs;\n this.#items = new Map(\n Object.entries(initial ?? {}).map(([key, entry]) => {\n const quoteStatusStateFsm = new QuoteStatusStateFsm(entry.status);\n\n return [\n key,\n // Entries from `initialDeferredUpdates` come from Immer-managed controller\n // state, which deep-freezes all nested objects. Cloning each entry here\n // ensures the in-memory queue holds mutable objects so that mutations\n // (e.g. updating `status` or `lastAttemptAt`) work correctly without\n // throwing a \"read only property\" error.\n { ...entry, status: quoteStatusStateFsm },\n ];\n }),\n );\n\n // Subscribe each seeded FSM after the map (and `#onPersistUpdates`) are set\n // so that any later state transition persists the updated snapshot.\n for (const entry of this.#items.values()) {\n this.#subscribeToStatusUpdates(entry);\n }\n }\n\n /**\n * Subscribes the entry's FSM so that every state transition persists the\n * current snapshot. Kept in one place so seeded and runtime-added entries\n * behave identically.\n *\n * @param entry - Entry whose FSM should trigger persistence on transition.\n */\n #subscribeToStatusUpdates(entry: QuoteStatusRuntimeEntry): void {\n entry.status.onStateUpdate(() => this.#persistToState());\n }\n\n /**\n * Adds a new entry when the key is not already present.\n *\n * If an entry already exists, only a missing `txMetaId` is backfilled to avoid\n * resetting timestamps and creating duplicate submission updates.\n *\n * @param key - Unique map key for the entry.\n * @param value - Entry payload without internal timestamps.\n * @param value.quoteId - Quote identifier.\n * @param value.srcTxHash - Source transaction hash.\n * @param value.status - Latest quote status.\n * @param value.txMetaId - Optional transaction metadata identifier.\n * @returns The tracked entry (the existing one when the key is already\n * present, otherwise the newly created entry).\n */\n put(\n key: string,\n value: Omit<QuoteStatusRuntimeEntry, 'createdAt' | 'lastAttemptAt'>,\n ): QuoteStatusRuntimeEntry {\n // If an entry for this key is already in the queue\n // do not overwrite it. Re-enqueueing would reset state and\n // could cause duplicate SUBMITTED events.\n const existing = this.#items.get(key);\n if (existing) {\n if (!existing.txMetaId && value.txMetaId) {\n existing.txMetaId = value.txMetaId;\n this.#persistToState();\n }\n return existing;\n }\n\n const now = Date.now();\n const entry: QuoteStatusRuntimeEntry = {\n ...value,\n createdAt: now,\n lastAttemptAt: now,\n };\n this.#items.set(key, entry);\n this.#subscribeToStatusUpdates(entry);\n this.#persistToState();\n return entry;\n }\n\n /**\n * Returns an iterator over all tracked entries.\n *\n * Stale entries are transitioned to {@link QuoteStatusState.Expired} first (but\n * kept in the store), so the iterator reflects their up-to-date terminal state.\n *\n * @returns An iterator over the currently tracked entries.\n */\n values(): IterableIterator<QuoteStatusRuntimeEntry> {\n this.expireStaleEntries();\n return this.#items.values();\n }\n\n /**\n * Returns every tracked entry matching the provided quote identifier.\n *\n * Stale entries are transitioned to {@link QuoteStatusState.Expired} first so\n * callers observe their up-to-date terminal state.\n *\n * @param quoteId - Quote identifier to match.\n * @returns The matching entries (empty when none exist).\n */\n getByQuoteId(quoteId: string): QuoteStatusRuntimeEntry[] {\n this.expireStaleEntries();\n\n const matches: QuoteStatusRuntimeEntry[] = [];\n for (const entry of this.#items.values()) {\n if (entry.quoteId === quoteId) {\n matches.push(entry);\n }\n }\n\n return matches;\n }\n\n /**\n * Number of entries currently tracked by the store.\n *\n * Note that this includes terminal entries (`Completed`/`Expired`), which are\n * retained rather than evicted; call {@link expireStaleEntries} (or\n * {@link values}) first when an up-to-date view of entry states is required.\n *\n * @returns The count of tracked entries.\n */\n get size(): number {\n return this.#items.size;\n }\n\n /**\n * Looks up an entry by its transaction metadata identifier.\n *\n * If the matching entry has exceeded its TTL it is transitioned to\n * {@link QuoteStatusState.Expired} (but kept) before being returned.\n *\n * @param txMetaId - Transaction metadata identifier to search for.\n * @returns The matching entry, or `null` if none exists.\n */\n getByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry | null {\n for (const entry of this.#items.values()) {\n if (entry.txMetaId === txMetaId) {\n this.expireEntryIfStale(entry);\n return entry;\n }\n }\n\n return null;\n }\n\n /**\n * Retrieves an entry by key.\n *\n * If the entry has exceeded its TTL it is transitioned to\n * {@link QuoteStatusState.Expired} (but kept) before being returned.\n *\n * @param key - Unique map key for the entry.\n * @returns The matching entry, or `null` if absent.\n */\n get(key: string): QuoteStatusRuntimeEntry | null {\n const entry = this.#items.get(key);\n if (!entry) {\n return null;\n }\n\n this.expireEntryIfStale(entry);\n return entry;\n }\n\n /**\n * Persists the current snapshot after an in-place mutation of a tracked entry.\n *\n * Used when fields such as `lastAttemptAt` are mutated directly on an entry\n * the caller already holds. No-ops if the entry is no longer tracked (e.g. the\n * store was cleared in the meantime), so persistence only reflects live entries.\n *\n * @param entry - The mutated entry to persist.\n */\n update(entry: QuoteStatusRuntimeEntry): void {\n if (!this.#items.has(QuoteStatusEntryStore.hash(entry))) {\n return;\n }\n\n this.#persistToState();\n }\n\n /**\n * Emits a cloned, serializable snapshot of all tracked entries via the\n * `onPersistUpdates` callback.\n *\n * Each entry's FSM is flattened to its plain `status` value so the snapshot\n * contains no class instances and is safe to store in controller state.\n */\n #persistToState(): void {\n const cloned: Record<string, QuoteStatusPersistEntry> = {};\n for (const [key, entry] of this.#items) {\n cloned[key] = { ...entry, status: entry.status.state };\n }\n this.#onPersistUpdates(cloned);\n }\n\n /**\n * Transitions every stale entry to {@link QuoteStatusState.Expired}, keeping it\n * in the store.\n *\n * Expired entries are retained so that later interactions with the same quote\n * (e.g. a duplicate `reportSubmitted`) can be recognized and rejected. Each\n * transition persists the updated snapshot via the entry's FSM subscription;\n * already-terminal entries cannot transition and are left untouched.\n */\n expireStaleEntries(): void {\n for (const entry of this.#items.values()) {\n if (this.entryHasExpired(entry)) {\n entry.status.transitionTo(QuoteStatusState.Expired);\n }\n }\n }\n\n /**\n * Determines whether an entry has outlived the configured TTL.\n *\n * Expiry is measured from the entry's `createdAt` timestamp, not its last\n * attempt, so an entry's total lifetime is bounded regardless of retries.\n *\n * @param entry - Entry to check.\n * @returns `true` if the entry's age exceeds the TTL.\n */\n entryHasExpired(entry: QuoteStatusRuntimeEntry): boolean {\n const now = Date.now();\n return now - entry.createdAt > this.#entryTtlMs;\n }\n\n /**\n * Transitions an entry to {@link QuoteStatusState.Expired} (keeping it) if it\n * has exceeded its TTL.\n *\n * The transition persists the updated snapshot via the entry's FSM\n * subscription. Already-terminal entries cannot transition and are left\n * untouched.\n *\n * @param entry - Entry to check and possibly expire.\n * @returns `true` if the entry was stale, otherwise `false`.\n */\n expireEntryIfStale(entry: QuoteStatusRuntimeEntry): boolean {\n if (this.entryHasExpired(entry)) {\n entry.status.transitionTo(QuoteStatusState.Expired);\n return true;\n }\n\n return false;\n }\n\n /**\n * Removes every entry from the store.\n *\n * Detaches each entry's FSM listeners before clearing to prevent leaks and\n * stale persist callbacks. Does not emit a persistence update; callers are\n * expected to use this during teardown.\n */\n clear(): void {\n for (const entry of this.#items.values()) {\n entry.status.removeAllListeners();\n }\n\n this.#items.clear();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"quote-status-entry-store.mjs","sourceRoot":"","sources":["../../src/quote-status-manager/quote-status-entry-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { QuoteStatusState } from './constants';\nimport { QuoteStatusStateFsm } from './quote-status-state-fsm';\nimport {\n QuoteStatusEntryStoreOptions,\n QuoteStatusPersistEntry,\n QuoteStatusRuntimeEntry,\n} from './types';\n\n/**\n * In-memory store for quote status update entries.\n *\n * The store deduplicates entries by key, tracks timestamps used for retries and\n * TTL eviction, and persists a cloned snapshot on each mutating operation.\n */\nexport class QuoteStatusEntryStore {\n readonly #items: Map<string, QuoteStatusRuntimeEntry>;\n\n readonly #entryTtlMs: number;\n\n /**\n * Creates a deterministic key for persisted quote status entries.\n *\n * @param entry - Entry identity fields.\n * @param entry.quoteId - Quote identifier.\n * @param entry.srcTxHash - Source transaction hash.\n * @returns Stable key in `${quoteId}:${srcTxHash}` format.\n */\n static hash(\n entry: Pick<QuoteStatusPersistEntry, 'quoteId' | 'srcTxHash'>,\n ): string {\n return `${entry.quoteId}:${entry.srcTxHash}`;\n }\n\n readonly #onPersistUpdates: (\n updates: Record<string, QuoteStatusPersistEntry>,\n ) => void;\n\n /**\n * Creates a quote status entry store.\n *\n * @param options - Store dependencies and retention configuration.\n * @param options.onPersistUpdates - Callback invoked with cloned snapshot updates.\n * @param options.entryTtlMs - Entry time-to-live in milliseconds.\n * @param options.initial - Optional initial persisted entries used to seed the store.\n */\n constructor({\n onPersistUpdates,\n entryTtlMs,\n initial,\n }: QuoteStatusEntryStoreOptions) {\n this.#onPersistUpdates = onPersistUpdates;\n this.#entryTtlMs = entryTtlMs;\n this.#items = new Map(\n Object.entries(initial ?? {}).map(([key, entry]) => {\n const quoteStatusStateFsm = new QuoteStatusStateFsm(entry.status);\n\n return [\n key,\n // Entries from `initialDeferredUpdates` come from Immer-managed controller\n // state, which deep-freezes all nested objects. Cloning each entry here\n // ensures the in-memory queue holds mutable objects so that mutations\n // (e.g. updating `status` or `lastAttemptAt`) work correctly without\n // throwing a \"read only property\" error.\n { ...entry, status: quoteStatusStateFsm },\n ];\n }),\n );\n\n // Subscribe each seeded FSM after the map (and `#onPersistUpdates`) are set\n // so that any later state transition persists the updated snapshot.\n for (const entry of this.#items.values()) {\n this.#subscribeToStatusUpdates(entry);\n }\n }\n\n /**\n * Subscribes the entry's FSM so that every state transition persists the\n * current snapshot. Kept in one place so seeded and runtime-added entries\n * behave identically.\n *\n * @param entry - Entry whose FSM should trigger persistence on transition.\n */\n #subscribeToStatusUpdates(entry: QuoteStatusRuntimeEntry): void {\n entry.status.onStateUpdate(() => this.#persistToState());\n }\n\n /**\n * Adds a new entry when the key is not already present.\n *\n * If an entry already exists, only a missing `txMetaId` is backfilled to avoid\n * resetting timestamps and creating duplicate submission updates.\n *\n * @param key - Unique map key for the entry.\n * @param value - Entry payload without internal timestamps.\n * @param value.quoteId - Quote identifier.\n * @param value.srcTxHash - Source transaction hash.\n * @param value.status - Latest quote status.\n * @param value.txMetaId - Optional transaction metadata identifier.\n * @returns The tracked entry (the existing one when the key is already\n * present, otherwise the newly created entry).\n */\n put(\n key: string,\n value: Omit<QuoteStatusRuntimeEntry, 'createdAt' | 'lastAttemptAt'>,\n ): QuoteStatusRuntimeEntry {\n // If an entry for this key is already in the queue\n // do not overwrite it. Re-enqueueing would reset state and\n // could cause duplicate SUBMITTED events.\n const existing = this.#items.get(key);\n if (existing) {\n if (!existing.txMetaId && value.txMetaId) {\n existing.txMetaId = value.txMetaId;\n this.#persistToState();\n }\n return existing;\n }\n\n const now = Date.now();\n const entry: QuoteStatusRuntimeEntry = {\n ...value,\n createdAt: now,\n lastAttemptAt: now,\n };\n this.#items.set(key, entry);\n this.#subscribeToStatusUpdates(entry);\n this.#persistToState();\n return entry;\n }\n\n /**\n * Returns an iterator over all tracked entries.\n *\n * Stale entries are transitioned to {@link QuoteStatusState.Expired} first (but\n * kept in the store), so the iterator reflects their up-to-date terminal state.\n *\n * @returns An iterator over the currently tracked entries.\n */\n values(): IterableIterator<QuoteStatusRuntimeEntry> {\n this.expireStaleEntries();\n return this.#items.values();\n }\n\n /**\n * Returns every tracked entry matching the provided quote identifier.\n *\n * Stale entries are transitioned to {@link QuoteStatusState.Expired} first so\n * callers observe their up-to-date terminal state.\n *\n * @param quoteId - Quote identifier to match.\n * @returns The matching entries (empty when none exist).\n */\n getByQuoteId(quoteId: string): QuoteStatusRuntimeEntry[] {\n this.expireStaleEntries();\n\n const matches: QuoteStatusRuntimeEntry[] = [];\n for (const entry of this.#items.values()) {\n if (entry.quoteId === quoteId) {\n matches.push(entry);\n }\n }\n\n return matches;\n }\n\n /**\n * Number of entries currently tracked by the store.\n *\n * Note that this includes terminal entries (`Completed`/`Expired`), which are\n * retained rather than evicted; call {@link expireStaleEntries} (or\n * {@link values}) first when an up-to-date view of entry states is required.\n *\n * @returns The count of tracked entries.\n */\n get size(): number {\n return this.#items.size;\n }\n\n /**\n * Looks up an entry by its transaction metadata identifier.\n *\n * If the matching entry has exceeded its TTL it is transitioned to\n * {@link QuoteStatusState.Expired} (but kept) before being returned.\n *\n * @param txMetaId - Transaction metadata identifier to search for.\n * @returns The matching entry, or `null` if none exists.\n */\n getByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry | null {\n for (const entry of this.#items.values()) {\n if (entry.txMetaId === txMetaId) {\n this.expireEntryIfStale(entry);\n return entry;\n }\n }\n\n return null;\n }\n\n /**\n * Looks up every entry sharing the given transaction metadata identifier.\n *\n * A single 7702/nested batch transaction submits multiple quotes under one\n * source tx hash and one `txMetaId`, producing several entries that must all\n * be finalized together. Each matching entry has its TTL checked (and is\n * transitioned to {@link QuoteStatusState.Expired} if stale) before being\n * returned.\n *\n * @param txMetaId - Transaction metadata identifier to search for.\n * @returns The matching entries (empty when none exist).\n */\n getAllByTxMetaId(txMetaId: string): QuoteStatusRuntimeEntry[] {\n const matches: QuoteStatusRuntimeEntry[] = [];\n for (const entry of this.#items.values()) {\n if (entry.txMetaId === txMetaId) {\n this.expireEntryIfStale(entry);\n matches.push(entry);\n }\n }\n\n return matches;\n }\n\n /**\n * Retrieves an entry by key.\n *\n * If the entry has exceeded its TTL it is transitioned to\n * {@link QuoteStatusState.Expired} (but kept) before being returned.\n *\n * @param key - Unique map key for the entry.\n * @returns The matching entry, or `null` if absent.\n */\n get(key: string): QuoteStatusRuntimeEntry | null {\n const entry = this.#items.get(key);\n if (!entry) {\n return null;\n }\n\n this.expireEntryIfStale(entry);\n return entry;\n }\n\n /**\n * Persists the current snapshot after an in-place mutation of a tracked entry.\n *\n * Used when fields such as `lastAttemptAt` are mutated directly on an entry\n * the caller already holds. No-ops if the entry is no longer tracked (e.g. the\n * store was cleared in the meantime), so persistence only reflects live entries.\n *\n * @param entry - The mutated entry to persist.\n */\n update(entry: QuoteStatusRuntimeEntry): void {\n if (!this.#items.has(QuoteStatusEntryStore.hash(entry))) {\n return;\n }\n\n this.#persistToState();\n }\n\n /**\n * Emits a cloned, serializable snapshot of all tracked entries via the\n * `onPersistUpdates` callback.\n *\n * Each entry's FSM is flattened to its plain `status` value so the snapshot\n * contains no class instances and is safe to store in controller state.\n */\n #persistToState(): void {\n const cloned: Record<string, QuoteStatusPersistEntry> = {};\n for (const [key, entry] of this.#items) {\n cloned[key] = { ...entry, status: entry.status.state };\n }\n this.#onPersistUpdates(cloned);\n }\n\n /**\n * Transitions every stale entry to {@link QuoteStatusState.Expired}, keeping it\n * in the store.\n *\n * Expired entries are retained so that later interactions with the same quote\n * (e.g. a duplicate `reportSubmitted`) can be recognized and rejected. Each\n * transition persists the updated snapshot via the entry's FSM subscription;\n * already-terminal entries cannot transition and are left untouched.\n */\n expireStaleEntries(): void {\n for (const entry of this.#items.values()) {\n if (this.entryHasExpired(entry)) {\n entry.status.transitionTo(QuoteStatusState.Expired);\n }\n }\n }\n\n /**\n * Determines whether an entry has outlived the configured TTL.\n *\n * Expiry is measured from the entry's `createdAt` timestamp, not its last\n * attempt, so an entry's total lifetime is bounded regardless of retries.\n *\n * @param entry - Entry to check.\n * @returns `true` if the entry's age exceeds the TTL.\n */\n entryHasExpired(entry: QuoteStatusRuntimeEntry): boolean {\n const now = Date.now();\n return now - entry.createdAt > this.#entryTtlMs;\n }\n\n /**\n * Transitions an entry to {@link QuoteStatusState.Expired} (keeping it) if it\n * has exceeded its TTL.\n *\n * The transition persists the updated snapshot via the entry's FSM\n * subscription. Already-terminal entries cannot transition and are left\n * untouched.\n *\n * @param entry - Entry to check and possibly expire.\n * @returns `true` if the entry was stale, otherwise `false`.\n */\n expireEntryIfStale(entry: QuoteStatusRuntimeEntry): boolean {\n if (this.entryHasExpired(entry)) {\n entry.status.transitionTo(QuoteStatusState.Expired);\n return true;\n }\n\n return false;\n }\n\n /**\n * Removes every entry from the store.\n *\n * Detaches each entry's FSM listeners before clearing to prevent leaks and\n * stale persist callbacks. Does not emit a persistence update; callers are\n * expected to use this during teardown.\n */\n clear(): void {\n for (const entry of this.#items.values()) {\n entry.status.removeAllListeners();\n }\n\n this.#items.clear();\n }\n}\n"]}
|
|
@@ -109,33 +109,47 @@ class QuoteStatusManager {
|
|
|
109
109
|
* manager is disabled, and surfaces an error when the entry is missing or
|
|
110
110
|
* cannot transition to the finalized state.
|
|
111
111
|
*
|
|
112
|
-
*
|
|
112
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
113
|
+
* `txMetaId`, so every entry sharing that id is finalized together.
|
|
114
|
+
*
|
|
115
|
+
* @param txMetaId - Transaction metadata id of the finalized quote(s).
|
|
113
116
|
* @param success - Whether the transaction finalized successfully.
|
|
114
117
|
*/
|
|
115
118
|
reportFinalised(txMetaId, success) {
|
|
116
119
|
if (!__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this)) {
|
|
117
120
|
return;
|
|
118
121
|
}
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
122
|
+
const entries = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").getAllByTxMetaId(txMetaId);
|
|
123
|
+
if (entries.length === 0) {
|
|
121
124
|
__classPrivateFieldGet(this, _QuoteStatusManager_onError, "f")?.call(this, new errors_1.QuoteStatusUpdateError('reporting finalization status but entry was not found', { quoteId: '' }));
|
|
122
125
|
return;
|
|
123
126
|
}
|
|
124
127
|
const nextState = success
|
|
125
128
|
? constants_1.QuoteStatusState.FinalizedSuccess
|
|
126
129
|
: constants_1.QuoteStatusState.FinalizedFailed;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
let hasEntryToProcess = false;
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (!entry.status.canTransitionTo(nextState)) {
|
|
133
|
+
// This is expected, there are race conditions where
|
|
134
|
+
// reportFinalized can be called twice. If the second
|
|
135
|
+
// call fails due to the first completed sucesfully
|
|
136
|
+
// backend will report that we cannot transition outside
|
|
137
|
+
// a final state, which is correct and we can safely skip
|
|
138
|
+
// this entry.
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
entry.status.transitionTo(nextState);
|
|
142
|
+
hasEntryToProcess = true;
|
|
143
|
+
}
|
|
144
|
+
if (!hasEntryToProcess) {
|
|
134
145
|
return;
|
|
135
146
|
}
|
|
136
|
-
entry.status.transitionTo(nextState);
|
|
137
147
|
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_ensureRetryTimerRunning).call(this);
|
|
138
|
-
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
if (entry.status.state === nextState) {
|
|
150
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
139
153
|
}
|
|
140
154
|
/**
|
|
141
155
|
* Reports that a quote has been submitted on-chain and begins tracking it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes-status-manager.cjs","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 { QuoteStatusGetWithRetryOutcome } from './quote-status-get-with-retry-outcome';\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 * @returns The quote status outcome, or `undefined` when the manager is disabled.\n */\n async getStatus(\n quoteId: string,\n options: {\n maxRetries?: number;\n delayMsBetweenRetries?: number;\n } = {\n maxRetries: 0,\n delayMsBetweenRetries: 1000,\n },\n ): Promise<QuoteStatusGetWithRetryOutcome | undefined> {\n if (!this.#isEnabled?.()) {\n return undefined;\n }\n\n const response = this.#quoteStatusApiService\n .getQuoteStatusWithRetry(\n {\n quoteId,\n },\n {\n maxRetries: options.maxRetries ?? 0,\n delayMsBetweenRetries: options.delayMsBetweenRetries ?? 1000,\n },\n )\n // Errors already reported by #onError handlers of `getQuoteStatusWithRetry()`\n .catch(() => undefined);\n\n return response;\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\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"]}
|
|
1
|
+
{"version":3,"file":"quotes-status-manager.cjs","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 { QuoteStatusGetWithRetryOutcome } from './quote-status-get-with-retry-outcome';\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 * A single 7702/nested batch transaction submits multiple quotes under one\n * `txMetaId`, so every entry sharing that id is finalized together.\n *\n * @param txMetaId - Transaction metadata id of the finalized quote(s).\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 entries = this.#quoteStatusEntryStore.getAllByTxMetaId(txMetaId);\n\n if (entries.length === 0) {\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 let hasEntryToProcess = false;\n\n for (const entry of entries) {\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 skip\n // this entry.\n continue;\n }\n\n entry.status.transitionTo(nextState);\n hasEntryToProcess = true;\n }\n\n if (!hasEntryToProcess) {\n return;\n }\n\n this.#ensureRetryTimerRunning();\n\n for (const entry of entries) {\n if (entry.status.state === nextState) {\n this.#processEntry(entry);\n }\n }\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 * @returns The quote status outcome, or `undefined` when the manager is disabled.\n */\n async getStatus(\n quoteId: string,\n options: {\n maxRetries?: number;\n delayMsBetweenRetries?: number;\n } = {\n maxRetries: 0,\n delayMsBetweenRetries: 1000,\n },\n ): Promise<QuoteStatusGetWithRetryOutcome | undefined> {\n if (!this.#isEnabled?.()) {\n return undefined;\n }\n\n const response = this.#quoteStatusApiService\n .getQuoteStatusWithRetry(\n {\n quoteId,\n },\n {\n maxRetries: options.maxRetries ?? 0,\n delayMsBetweenRetries: options.delayMsBetweenRetries ?? 1000,\n },\n )\n // Errors already reported by #onError handlers of `getQuoteStatusWithRetry()`\n .catch(() => undefined);\n\n return response;\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\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"]}
|
|
@@ -67,7 +67,10 @@ export declare class QuoteStatusManager {
|
|
|
67
67
|
* manager is disabled, and surfaces an error when the entry is missing or
|
|
68
68
|
* cannot transition to the finalized state.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
70
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
71
|
+
* `txMetaId`, so every entry sharing that id is finalized together.
|
|
72
|
+
*
|
|
73
|
+
* @param txMetaId - Transaction metadata id of the finalized quote(s).
|
|
71
74
|
* @param success - Whether the transaction finalized successfully.
|
|
72
75
|
*/
|
|
73
76
|
reportFinalised(txMetaId: string, success: boolean): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes-status-manager.d.cts","sourceRoot":"","sources":["../../src/quote-status-manager/quotes-status-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,+BAA+B,EAAE,qBAAiB;AAa3E,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAGlD,OAAO,EAAE,8BAA8B,EAAE,kDAA8C;AAGvF,OAAO,EAAE,uBAAuB,EAA2B,oBAAgB;AAE3E;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;;IAqB7B;;;;;;;;;;;;;;;;;;;;;OAqBG;gBACS,EACV,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,EACb,UAAU,EACV,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,EAAE;QACD,SAAS,EAAE,+BAA+B,CAAC;QAC3C,QAAQ,EAAE,cAAc,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,KAC7C,IAAI,CAAC;QACV,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;QAClD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;KACtD;IAsBD;;;;;;;;OAQG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED
|
|
1
|
+
{"version":3,"file":"quotes-status-manager.d.cts","sourceRoot":"","sources":["../../src/quote-status-manager/quotes-status-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,+BAA+B,EAAE,qBAAiB;AAa3E,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAGlD,OAAO,EAAE,8BAA8B,EAAE,kDAA8C;AAGvF,OAAO,EAAE,uBAAuB,EAA2B,oBAAgB;AAE3E;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;;IAqB7B;;;;;;;;;;;;;;;;;;;;;OAqBG;gBACS,EACV,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,EACb,UAAU,EACV,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,EAAE;QACD,SAAS,EAAE,+BAA+B,CAAC;QAC3C,QAAQ,EAAE,cAAc,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,KAC7C,IAAI,CAAC;QACV,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;QAClD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;KACtD;IAsBD;;;;;;;;OAQG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAmDzD;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAgC5E;;;OAGG;IACH,OAAO,IAAI,IAAI;IAKf;;;;;;;;;;;;;;OAcG;IACH,IAAI,IAAI,IAAI;IAuCZ;;;;;;;;;;;;OAYG;IACG,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAIhC,GACA,OAAO,CAAC,8BAA8B,GAAG,SAAS,CAAC;CA6YvD"}
|
|
@@ -67,7 +67,10 @@ export declare class QuoteStatusManager {
|
|
|
67
67
|
* manager is disabled, and surfaces an error when the entry is missing or
|
|
68
68
|
* cannot transition to the finalized state.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
70
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
71
|
+
* `txMetaId`, so every entry sharing that id is finalized together.
|
|
72
|
+
*
|
|
73
|
+
* @param txMetaId - Transaction metadata id of the finalized quote(s).
|
|
71
74
|
* @param success - Whether the transaction finalized successfully.
|
|
72
75
|
*/
|
|
73
76
|
reportFinalised(txMetaId: string, success: boolean): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes-status-manager.d.mts","sourceRoot":"","sources":["../../src/quote-status-manager/quotes-status-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,+BAA+B,EAAE,qBAAiB;AAa3E,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAGlD,OAAO,EAAE,8BAA8B,EAAE,kDAA8C;AAGvF,OAAO,EAAE,uBAAuB,EAA2B,oBAAgB;AAE3E;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;;IAqB7B;;;;;;;;;;;;;;;;;;;;;OAqBG;gBACS,EACV,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,EACb,UAAU,EACV,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,EAAE;QACD,SAAS,EAAE,+BAA+B,CAAC;QAC3C,QAAQ,EAAE,cAAc,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,KAC7C,IAAI,CAAC;QACV,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;QAClD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;KACtD;IAsBD;;;;;;;;OAQG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED
|
|
1
|
+
{"version":3,"file":"quotes-status-manager.d.mts","sourceRoot":"","sources":["../../src/quote-status-manager/quotes-status-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,+BAA+B,EAAE,qBAAiB;AAa3E,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAGlD,OAAO,EAAE,8BAA8B,EAAE,kDAA8C;AAGvF,OAAO,EAAE,uBAAuB,EAA2B,oBAAgB;AAE3E;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;;IAqB7B;;;;;;;;;;;;;;;;;;;;;OAqBG;gBACS,EACV,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,EACb,UAAU,EACV,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,EAAE;QACD,SAAS,EAAE,+BAA+B,CAAC;QAC3C,QAAQ,EAAE,cAAc,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,KAC7C,IAAI,CAAC;QACV,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;QAClD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;KACtD;IAsBD;;;;;;;;OAQG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAmDzD;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAgC5E;;;OAGG;IACH,OAAO,IAAI,IAAI;IAKf;;;;;;;;;;;;;;OAcG;IACH,IAAI,IAAI,IAAI;IAuCZ;;;;;;;;;;;;OAYG;IACG,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAIhC,GACA,OAAO,CAAC,8BAA8B,GAAG,SAAS,CAAC;CA6YvD"}
|
|
@@ -106,33 +106,47 @@ export class QuoteStatusManager {
|
|
|
106
106
|
* manager is disabled, and surfaces an error when the entry is missing or
|
|
107
107
|
* cannot transition to the finalized state.
|
|
108
108
|
*
|
|
109
|
-
*
|
|
109
|
+
* A single 7702/nested batch transaction submits multiple quotes under one
|
|
110
|
+
* `txMetaId`, so every entry sharing that id is finalized together.
|
|
111
|
+
*
|
|
112
|
+
* @param txMetaId - Transaction metadata id of the finalized quote(s).
|
|
110
113
|
* @param success - Whether the transaction finalized successfully.
|
|
111
114
|
*/
|
|
112
115
|
reportFinalised(txMetaId, success) {
|
|
113
116
|
if (!__classPrivateFieldGet(this, _QuoteStatusManager_isEnabled, "f")?.call(this)) {
|
|
114
117
|
return;
|
|
115
118
|
}
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
119
|
+
const entries = __classPrivateFieldGet(this, _QuoteStatusManager_quoteStatusEntryStore, "f").getAllByTxMetaId(txMetaId);
|
|
120
|
+
if (entries.length === 0) {
|
|
118
121
|
__classPrivateFieldGet(this, _QuoteStatusManager_onError, "f")?.call(this, new QuoteStatusUpdateError('reporting finalization status but entry was not found', { quoteId: '' }));
|
|
119
122
|
return;
|
|
120
123
|
}
|
|
121
124
|
const nextState = success
|
|
122
125
|
? QuoteStatusState.FinalizedSuccess
|
|
123
126
|
: QuoteStatusState.FinalizedFailed;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
let hasEntryToProcess = false;
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (!entry.status.canTransitionTo(nextState)) {
|
|
130
|
+
// This is expected, there are race conditions where
|
|
131
|
+
// reportFinalized can be called twice. If the second
|
|
132
|
+
// call fails due to the first completed sucesfully
|
|
133
|
+
// backend will report that we cannot transition outside
|
|
134
|
+
// a final state, which is correct and we can safely skip
|
|
135
|
+
// this entry.
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
entry.status.transitionTo(nextState);
|
|
139
|
+
hasEntryToProcess = true;
|
|
140
|
+
}
|
|
141
|
+
if (!hasEntryToProcess) {
|
|
131
142
|
return;
|
|
132
143
|
}
|
|
133
|
-
entry.status.transitionTo(nextState);
|
|
134
144
|
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_ensureRetryTimerRunning).call(this);
|
|
135
|
-
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (entry.status.state === nextState) {
|
|
147
|
+
__classPrivateFieldGet(this, _QuoteStatusManager_instances, "m", _QuoteStatusManager_processEntry).call(this, entry);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
136
150
|
}
|
|
137
151
|
/**
|
|
138
152
|
* Reports that a quote has been submitted on-chain and begins tracking it.
|