Skip to content
Draft
1 change: 1 addition & 0 deletions packages/bridge-controller/src/utils/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ export const QuoteResponseSchema = type({
TronTradeDataSchema,
string(),
]),
quoteId: string(),
});

export const validateQuoteResponse = (
Expand Down
105 changes: 101 additions & 4 deletions packages/bridge-status-controller/src/bridge-status-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
MAX_ATTEMPTS,
REFRESH_INTERVAL_MS,
} from './constants';
import { QuoteStatusUpdateManager } from './quote-status-update-manager';
import type {
BridgeStatusControllerState,
StartPollingForBridgeTxStatusArgsSerialized,
Expand Down Expand Up @@ -109,6 +110,12 @@ const metadata: StateMetadata<BridgeStatusControllerState> = {
includeInDebugSnapshot: false,
usedInUi: true,
},
deferredStatusUpdates: {
includeInStateLogs: false,
persist: true,
includeInDebugSnapshot: false,
usedInUi: false,
},
};

/** The input to start polling for the {@link BridgeStatusController} */
Expand Down Expand Up @@ -138,6 +145,8 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid

readonly #intentManager: IntentManager;

readonly #quoteStatusUpdateManager: QuoteStatusUpdateManager;

readonly #clientId: BridgeClientId;

readonly #fetchFn: FetchFunction;
Expand Down Expand Up @@ -193,6 +202,18 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
customBridgeApiBaseUrl: this.#config.customBridgeApiBaseUrl,
fetchFn: this.#fetchFn,
});
this.#quoteStatusUpdateManager = new QuoteStatusUpdateManager({
messenger: this.messenger,
fetchFn: this.#fetchFn,
clientId: this.#clientId,
apiBaseUrl: this.#config.customBridgeApiBaseUrl,
initialDeferredUpdates: this.state.deferredStatusUpdates,
persistDeferredUpdates: (updates): void => {
this.update((draft) => {
draft.deferredStatusUpdates = updates;
});
},
});

// Register action handlers
this.messenger.registerMethodActionHandlers(
Expand Down Expand Up @@ -224,6 +245,8 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
) {
// Mark tx as failed in txHistory
this.#markTxAsFailed(transactionMeta);
console.log('1 wefwewef - finalized failed', txMetaId);
this.#quoteStatusUpdateManager.reportFinalised(txMetaId, false);
// Track failed event
if (status !== TransactionStatus.rejected) {
// Look up history by txMetaId first, then by actionId (for pre-submission failures)
Expand Down Expand Up @@ -257,6 +280,39 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
if (type === TransactionType.bridge && !isNonEvmChainId(chainId)) {
this.#startPollingForTxId(txMetaId);
}
if (
type &&
[TransactionType.bridge, TransactionType.swap].includes(type)
) {
console.log('2 wefwewef - finalized success', txMetaId);
this.#quoteStatusUpdateManager.reportFinalised(txMetaId, true);
}
},
);

// For batch EVM transactions (STX / gasIncluded7702) the tx hash is not
// available when submitTx returns, so we report the submitted status here
// once the TransactionController has broadcast the tx and assigned a hash.
this.messenger.subscribe(
'TransactionController:transactionSubmitted',
({ transactionMeta }) => {
const { type, id: txMetaId, hash } = transactionMeta;
if (
hash &&
type &&
[TransactionType.bridge, TransactionType.swap].includes(type)
) {
const historyItem = this.state.txHistory[txMetaId];
const requestId = historyItem?.quote?.requestId;
if (requestId) {
console.log('3 wefwewef - submitted', requestId, hash, txMetaId);
this.#quoteStatusUpdateManager.reportSubmitted(
requestId,
hash,
txMetaId,
);
}
}
},
);

Expand Down Expand Up @@ -296,8 +352,11 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
};

resetState = (): void => {
this.#quoteStatusUpdateManager.destroy();
this.update((state) => {
state.txHistory = DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE.txHistory;
state.deferredStatusUpdates =
DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE.deferredStatusUpdates;
});
};

Expand Down Expand Up @@ -584,6 +643,10 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
this.stopPollingByPollingToken(pollingToken);
delete this.#pollingTokensByTxMetaId[bridgeTxMetaId];

// Finalize the quote status update as a failure since we can no longer
// determine the outcome via polling.
this.#quoteStatusUpdateManager.reportFinalised(bridgeTxMetaId, false);

// Track max polling reached event
const historyItem = this.state.txHistory[bridgeTxMetaId];
if (historyItem && !historyItem.featureId) {
Expand Down Expand Up @@ -740,9 +803,16 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
status.status === StatusTypes.COMPLETE ||
status.status === StatusTypes.FAILED;

if (isFinalStatus && pollingToken) {
this.stopPollingByPollingToken(pollingToken);
delete this.#pollingTokensByTxMetaId[bridgeTxMetaId];
if (isFinalStatus) {
if (pollingToken) {
this.stopPollingByPollingToken(pollingToken);
delete this.#pollingTokensByTxMetaId[bridgeTxMetaId];
}

this.#quoteStatusUpdateManager.reportFinalised(
bridgeTxMetaId,
status.status === StatusTypes.COMPLETE,
);

// Skip tracking events when featureId is set (i.e. PERPS)
if (historyItem.featureId) {
Expand Down Expand Up @@ -1171,6 +1241,25 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
!quoteResponse.quote.gasIncluded7702 &&
!isDelegatedAccount;

console.log('wefwefwe', JSON.stringify(txMeta, null, 2));
// Report submitted status to the Bridge API.
// For non-batch EVM and non-EVM, the hash is available now.
// For batch EVM (STX/gasIncluded7702), the hash arrives later via the
// TransactionController:transactionSubmitted event subscriber.
if (txMeta.hash) {
console.log(
'5 wefwewef - submitted',
quoteResponse.quoteId,
txMeta.hash,
txMeta.id,
);
this.#quoteStatusUpdateManager.reportSubmitted(
quoteResponse.quoteId,
txMeta.hash,
txMeta.id,
);
}

if (!isNonBatchEvm) {
// Add swap or bridge tx to history
this.#addTxToHistory({
Expand All @@ -1190,8 +1279,16 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
if (isNonEvmChainId(quoteResponse.quote.srcChainId)) {
// Start polling for bridge tx status
this.#startPollingForTxId(txMeta.id);
// Track non-EVM Swap completed event
// Non-EVM swaps (same-chain) are considered finalized immediately
console.log(
'uh3r2ehi2eh ',
isBridgeTx,
isTronTx,
!(isBridgeTx || isTronTx),
);
if (!(isBridgeTx || isTronTx)) {
console.log('6 wefwewef - finalized sucess', txMeta.id);
this.#quoteStatusUpdateManager.reportFinalised(txMeta.id, true);
this.#trackUnifiedSwapBridgeEvent(
UnifiedSwapBridgeEventName.Completed,
txMeta.id,
Expand Down
10 changes: 10 additions & 0 deletions packages/bridge-status-controller/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@ export const BRIDGE_STATUS_CONTROLLER_NAME = 'BridgeStatusController';
export const DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE: BridgeStatusControllerState =
{
txHistory: {},
deferredStatusUpdates: {},
};

export const QUOTE_STATUS_UPDATE_RETRY_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
export const QUOTE_STATUS_UPDATE_RETRY_MAX_LIFETIME_MS = 12 * 60 * 60 * 1000; // 12 hours

export enum QuoteStatusUpdateType {
Submitted = 'SUBMITTED',
FinalizedSuccess = 'FINALIZED_SUCCESS',
FinalizedFailure = 'FINALIZED_FAILURE',
}

export const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io';

export const APPROVAL_DELAY_MS = 5000;
Expand Down
1 change: 1 addition & 0 deletions packages/bridge-status-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type {
RefuelStatusResponse,
BridgeHistoryItem,
BridgeStatusControllerState,
DeferredStatusUpdateEntry,
BridgeStatusControllerMessenger,
BridgeStatusControllerActions,
BridgeStatusControllerGetStateAction,
Expand Down
Loading
Loading