Skip to content

Commit 9fba9e2

Browse files
committed
feat: adding TransactionTimeToConfirmation event for confirmations
Signed-off-by: ryanml <[email protected]>
1 parent ce77002 commit 9fba9e2

File tree

7 files changed

+132
-21
lines changed

7 files changed

+132
-21
lines changed

src/background/services/wallet/handlers/avalanche_sendTransaction.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import getProvidedUtxos from '../utils/getProvidedUtxos';
2424
import { AnalyticsServicePosthog } from '../../analytics/AnalyticsServicePosthog';
2525
import { ChainId } from '@avalabs/core-chains-sdk';
2626
import { openApprovalWindow } from '@src/background/runtime/openApprovalWindow';
27+
import { measureTransactionTime } from '@src/background/services/wallet/utils/measureTransactionTime';
2728

2829
type TxParams = {
2930
transactionHex: string;
@@ -231,6 +232,7 @@ export class AvalancheSendTransactionHandler extends DAppRequestHandler<
231232
const usedNetwork = this.#getChainIdForVM(vm);
232233

233234
try {
235+
measureTransactionTime().startMeasure();
234236
// Parse the json into a tx object
235237
const unsignedTx =
236238
vm === EVM
@@ -272,6 +274,18 @@ export class AvalancheSendTransactionHandler extends DAppRequestHandler<
272274
},
273275
});
274276

277+
measureTransactionTime().endMeasure(async (duration) => {
278+
this.analyticsServicePosthog.captureEvent({
279+
name: 'TransactionTimeToConfirmation',
280+
windowId: crypto.randomUUID(),
281+
properties: {
282+
duration,
283+
txType: 'txType',
284+
chainId: usedNetwork,
285+
},
286+
});
287+
});
288+
275289
// If we already have the transaction hash (i.e. it was dispatched by WalletConnect),
276290
// we just return it to the caller.
277291
onSuccess(txHash);

src/background/services/wallet/handlers/bitcoin_sendTransaction.test.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
3232
const signMock = jest.fn();
3333
const sendTransactionMock = jest.fn();
3434
const getBalancesForNetworksMock = jest.fn();
35+
const captureEventMock = jest.fn();
3536

3637
const getBitcoinNetworkMock = jest.fn();
3738
const activeAccountMock = {
@@ -51,6 +52,9 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
5152
const balanceAggregatorServiceMock = {
5253
getBalancesForNetworks: getBalancesForNetworksMock,
5354
};
55+
const analyticsServiceMock = {
56+
captureEvent: captureEventMock,
57+
};
5458

5559
beforeEach(() => {
5660
jest.resetAllMocks();
@@ -91,6 +95,7 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
9195
{} as any,
9296
{} as any,
9397
{} as any,
98+
{} as any,
9499
{} as any
95100
);
96101
const result = await handler.handleUnauthenticated(buildRpcCall(request));
@@ -113,7 +118,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
113118
type: AccountType.PRIMARY,
114119
},
115120
} as any,
116-
balanceAggregatorServiceMock as any
121+
balanceAggregatorServiceMock as any,
122+
analyticsServiceMock as any
117123
);
118124

119125
it('returns error if the active account is imported via WalletConnect', async () => {
@@ -128,7 +134,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
128134
type: AccountType.WALLET_CONNECT,
129135
},
130136
} as any,
131-
balanceAggregatorServiceMock as any
137+
balanceAggregatorServiceMock as any,
138+
analyticsServiceMock as any
132139
);
133140

134141
const result = await sendHandler.handleAuthenticated(
@@ -153,7 +160,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
153160
addressC: 'abcd1234',
154161
},
155162
} as any,
156-
balanceAggregatorServiceMock as any
163+
balanceAggregatorServiceMock as any,
164+
analyticsServiceMock as any
157165
);
158166

159167
const result = await sendHandler.handleAuthenticated(
@@ -230,7 +238,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
230238
walletServiceMock as any,
231239
networkServiceMock as any,
232240
{} as any,
233-
balanceAggregatorServiceMock as any
241+
balanceAggregatorServiceMock as any,
242+
analyticsServiceMock as any
234243
);
235244
const result = await sendHandler.handleAuthenticated({ request } as any);
236245
expect(result).toEqual({
@@ -260,7 +269,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
260269
walletServiceMock as any,
261270
networkServiceMock as any,
262271
accountsServiceMock as any,
263-
balanceAggregatorServiceMock as any
272+
balanceAggregatorServiceMock as any,
273+
analyticsServiceMock as any
264274
);
265275

266276
const result = await sendHandler.handleAuthenticated(
@@ -298,7 +308,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
298308
},
299309
} as any,
300310
accountsServiceMock as any,
301-
balanceAggregatorServiceMock as any
311+
balanceAggregatorServiceMock as any,
312+
analyticsServiceMock as any
302313
);
303314

304315
const result = await sendHandler.handleAuthenticated(
@@ -329,7 +340,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
329340
walletServiceMock as any,
330341
networkServiceMock as any,
331342
accountsServiceMock as any,
332-
balanceAggregatorServiceMock as any
343+
balanceAggregatorServiceMock as any,
344+
analyticsServiceMock as any
333345
);
334346

335347
getBitcoinNetworkMock.mockResolvedValue({
@@ -362,7 +374,8 @@ describe('src/background/services/wallet/handlers/bitcoin_sendTransaction.ts', (
362374
walletServiceMock as any,
363375
networkServiceMock as any,
364376
accountsServiceMock as any,
365-
balanceAggregatorServiceMock as any
377+
balanceAggregatorServiceMock as any,
378+
analyticsServiceMock as any
366379
);
367380

368381
getBitcoinNetworkMock.mockResolvedValue({

src/background/services/wallet/handlers/bitcoin_sendTransaction.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { DAppRequestHandler } from '@src/background/connections/dAppConnection/D
88
import { Action } from '../../actions/models';
99
import { DEFERRED_RESPONSE } from '@src/background/connections/middlewares/models';
1010
import { NetworkService } from '@src/background/services/network/NetworkService';
11+
import { AnalyticsServicePosthog } from '@src/background/services/analytics/AnalyticsServicePosthog';
1112
import { ethErrors } from 'eth-rpc-errors';
1213
import {
1314
DisplayData_BitcoinSendTx,
@@ -33,6 +34,7 @@ import { resolve } from '@avalabs/core-utils-sdk';
3334

3435
import { openApprovalWindow } from '@src/background/runtime/openApprovalWindow';
3536
import { runtime } from 'webextension-polyfill';
37+
import { measureTransactionTime } from '@src/background/services/wallet/utils/measureTransactionTime';
3638

3739
type BitcoinTxParams = [
3840
address: string,
@@ -52,7 +54,8 @@ export class BitcoinSendTransactionHandler extends DAppRequestHandler<
5254
private walletService: WalletService,
5355
private networkService: NetworkService,
5456
private accountService: AccountsService,
55-
private balanceAggregatorService: BalanceAggregatorService
57+
private balanceAggregatorService: BalanceAggregatorService,
58+
private analyticsServicePosthog: AnalyticsServicePosthog
5659
) {
5760
super();
5861
}
@@ -256,8 +259,12 @@ export class BitcoinSendTransactionHandler extends DAppRequestHandler<
256259
frontendTabId?: number
257260
) => {
258261
try {
262+
measureTransactionTime().startMeasure();
259263
const { address, amount, from, feeRate, balance } =
260264
pendingAction.displayData;
265+
const btcChainID = this.networkService.isMainnet()
266+
? ChainId.BITCOIN
267+
: ChainId.BITCOIN_TESTNET;
261268

262269
const [network, networkError] = await resolve(
263270
this.networkService.getBitcoinNetwork()
@@ -266,16 +273,14 @@ export class BitcoinSendTransactionHandler extends DAppRequestHandler<
266273
throw new Error('Bitcoin network not found');
267274
}
268275

269-
const { inputs, outputs } = await buildBtcTx(
270-
from,
271-
getProviderForNetwork(network) as BitcoinProvider,
272-
{
273-
amount,
274-
address,
275-
token: balance,
276-
feeRate,
277-
}
278-
);
276+
const provider = getProviderForNetwork(network) as BitcoinProvider;
277+
278+
const { inputs, outputs } = await buildBtcTx(from, provider, {
279+
amount,
280+
address,
281+
token: balance,
282+
feeRate,
283+
});
279284

280285
if (!inputs || !outputs) {
281286
throw new Error('Unable to create transaction');
@@ -293,7 +298,23 @@ export class BitcoinSendTransactionHandler extends DAppRequestHandler<
293298
if (this.#isSupportedAccount(this.accountService.activeAccount)) {
294299
this.#getBalance(this.accountService.activeAccount);
295300
}
301+
296302
onSuccess(hash);
303+
304+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
305+
provider.waitForTx(hash).then((_tx) => {
306+
measureTransactionTime().endMeasure(async (duration) => {
307+
this.analyticsServicePosthog.captureEvent({
308+
name: 'TransactionTimeToConfirmation',
309+
windowId: crypto.randomUUID(),
310+
properties: {
311+
duration,
312+
txType: 'txType',
313+
chainId: btcChainID,
314+
},
315+
});
316+
});
317+
});
297318
} catch (e) {
298319
onError(e);
299320
}

src/background/services/wallet/handlers/eth_sendTransaction/eth_sendTransaction.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ jest.mock('@src/background/services/analytics/utils/encryptAnalyticsData');
5252
jest.mock('./contracts/contractParsers/parseWithERC20Abi');
5353
jest.mock('./utils/getTxDescription');
5454
jest.mock('./contracts/contractParsers/utils/parseBasicDisplayValues');
55+
jest.mock(
56+
'@src/background/services/wallet/utils/measureTransactionTime',
57+
() => ({
58+
measureTransactionTime: function () {
59+
return {
60+
startMeasure: jest.fn(),
61+
endMeasure: jest.fn(),
62+
};
63+
},
64+
})
65+
);
5566
jest.mock('./contracts/contractParsers/contractParserMap', () => ({
5667
contractParserMap: new Map([['function', jest.fn()]]),
5768
}));

src/background/services/wallet/handlers/eth_sendTransaction/eth_sendTransaction.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { openApprovalWindow } from '@src/background/runtime/openApprovalWindow';
4545
import { EnsureDefined } from '@src/background/models';
4646
import { caipToChainId } from '@src/utils/caipConversion';
4747
import { TxDisplayOptions } from '../models';
48+
import { measureTransactionTime } from '@src/background/services/wallet/utils/measureTransactionTime';
4849

4950
type TxPayload = EthSendTransactionParams | ContractTransaction;
5051
type Params = [TxPayload] | [TxPayload, TxDisplayOptions];
@@ -207,6 +208,7 @@ export class EthSendTransactionHandler extends DAppRequestHandler<
207208
tabId?: number | undefined
208209
) => {
209210
try {
211+
measureTransactionTime().startMeasure();
210212
const network = await getTargetNetworkForTx(
211213
pendingAction.displayData.txParams,
212214
this.networkService,
@@ -223,6 +225,7 @@ export class EthSendTransactionHandler extends DAppRequestHandler<
223225
const nonce = await provider.getTransactionCount(
224226
pendingAction.displayData.txParams.from
225227
);
228+
const chainId = pendingAction.displayData.chainId;
226229

227230
const {
228231
maxFeePerGas,
@@ -237,7 +240,7 @@ export class EthSendTransactionHandler extends DAppRequestHandler<
237240
const signingResult = await this.walletService.sign(
238241
{
239242
nonce,
240-
chainId: Number(BigInt(pendingAction.displayData.chainId)),
243+
chainId: Number(BigInt(chainId)),
241244
maxFeePerGas,
242245
maxPriorityFeePerGas,
243246
gasLimit: gasLimit,
@@ -296,11 +299,26 @@ export class EthSendTransactionHandler extends DAppRequestHandler<
296299
address: this.accountsService.activeAccount?.addressC,
297300
txHash,
298301
method: pendingAction.method,
299-
chainId: pendingAction.displayData.chainId,
302+
chainId,
300303
},
301304
});
302305

303306
onSuccess(txHash);
307+
308+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
309+
provider.waitForTransaction(txHash).then((_tx) => {
310+
measureTransactionTime().endMeasure(async (duration) => {
311+
this.analyticsServicePosthog.captureEvent({
312+
name: 'TransactionTimeToConfirmation',
313+
windowId: crypto.randomUUID(),
314+
properties: {
315+
duration,
316+
txType: 'txType',
317+
chainId,
318+
},
319+
});
320+
});
321+
});
304322
} catch (err: any) {
305323
const errorMessage: string =
306324
err instanceof Error ? err.message : err.toString();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
enum TransactionTimeEvents {
2+
TRANSACTION_TIMED = 'transaction-timed',
3+
TRANSACTION_SUCCEEDED = 'transaction-succeeded',
4+
TRANSACTION_APPROVED = 'transaction-approved',
5+
}
6+
7+
export const measureTransactionTime = (): {
8+
startMeasure: () => void;
9+
endMeasure: (callback: (duration: number) => void) => void;
10+
} => {
11+
const startMeasure = () => {
12+
performance.mark(TransactionTimeEvents.TRANSACTION_APPROVED);
13+
};
14+
15+
const endMeasure = (callback: (duration: number) => void) => {
16+
performance.mark(TransactionTimeEvents.TRANSACTION_SUCCEEDED);
17+
18+
const measurement = performance.measure(
19+
TransactionTimeEvents.TRANSACTION_TIMED,
20+
TransactionTimeEvents.TRANSACTION_APPROVED,
21+
TransactionTimeEvents.TRANSACTION_SUCCEEDED
22+
);
23+
24+
performance.clearMarks(TransactionTimeEvents.TRANSACTION_APPROVED);
25+
performance.clearMarks(TransactionTimeEvents.TRANSACTION_SUCCEEDED);
26+
performance.clearMarks(TransactionTimeEvents.TRANSACTION_TIMED);
27+
28+
callback(measurement.duration);
29+
};
30+
31+
return { startMeasure, endMeasure };
32+
};

src/tests/setupTests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Object.defineProperty(global.document, 'prerendering', {
2626
value: false,
2727
});
2828

29+
performance.mark = jest.fn();
30+
2931
global.chrome = {
3032
runtime: {
3133
id: 'testid',

0 commit comments

Comments
 (0)