Skip to content

Commit

Permalink
feat: use eth_sendTransactionBatch for swaps requiring allowance incr…
Browse files Browse the repository at this point in the history
…ease
  • Loading branch information
meeh0w committed Jan 10, 2025
1 parent 8cce5d2 commit 9436ddc
Show file tree
Hide file tree
Showing 17 changed files with 836 additions and 121 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import { Action } from '@src/background/services/actions/models';
import { Action, MultiTxAction } from '@src/background/services/actions/models';
import {
DAppProviderRequest,
JsonRpcRequestParams,
Expand All @@ -13,7 +13,7 @@ export interface DAppRequestHandler {
* Called by the ActionsService after the user confirms the request on the approval popup.
*/
onActionApproved?: (
pendingAction: Action,
pendingAction: Action | MultiTxAction,
result: any,
onSuccess: (result: unknown) => Promise<void>,
onError: (error: Error) => Promise<void>,
Expand Down
7 changes: 5 additions & 2 deletions src/background/runtime/openApprovalWindow.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { container } from 'tsyringe';

import { Action } from '../services/actions/models';
import { Action, MultiTxAction } from '../services/actions/models';
import { ApprovalService } from '../services/approvals/ApprovalService';

export const openApprovalWindow = async (action: Action, url: string) => {
export const openApprovalWindow = async (
action: Action | MultiTxAction,
url: string
) => {
const actionId = crypto.randomUUID();
// using direct injection instead of the constructor to prevent circular dependencies
const approvalService = container.resolve(ApprovalService);
Expand Down
101 changes: 78 additions & 23 deletions src/background/services/actions/ActionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ActionStatus,
ACTIONS_STORAGE_KEY,
ActionUpdate,
MultiTxAction,
} from './models';
import { ethErrors } from 'eth-rpc-errors';
import { DAppRequestHandler } from '@src/background/connections/dAppConnection/DAppRequestHandler';
Expand All @@ -19,7 +20,12 @@ import { ACTION_HANDLED_BY_MODULE } from '@src/background/models';
import { DAppProviderRequest } from '@src/background/connections/dAppConnection/models';
import { getUpdatedSigningData } from '@src/utils/actions/getUpdatedActionData';
import { ApprovalController } from '@src/background/vmModules/ApprovalController';
import { BtcTxUpdateFn, EvmTxUpdateFn } from '@avalabs/vm-module-types';
import {
BtcTxUpdateFn,
EvmTxUpdateFn,
RpcMethod,
} from '@avalabs/vm-module-types';
import { omit } from 'lodash';

@singleton()
export class ActionsService implements OnStorageReady {
Expand Down Expand Up @@ -78,8 +84,10 @@ export class ActionsService implements OnStorageReady {
this.eventEmitter.emit(ActionsEvent.ACTION_UPDATED, actions);
}

async addAction(action: Action): Promise<Action> {
const pendingAction: Action = {
async addAction(
action: Action | MultiTxAction,
): Promise<Action | MultiTxAction> {
const pendingAction: Action | MultiTxAction = {
...action,
time: Date.now(),
status: ActionStatus.PENDING,
Expand All @@ -104,16 +112,22 @@ export class ActionsService implements OnStorageReady {

async emitResult(
id: string,
action: Action,
action: Action | MultiTxAction,
isSuccess: boolean,
result: any,
) {
await this.removeAction(id);

// We dont want display data to be emitted. Sometimes it can not be serialized and it's content is internal to Core
// Make sure not to modify the original action object

const { displayData, ...actionWithoutDisplayData } = action;
const actionWithoutDisplayData =
'signingRequests' in action
? {
...action,
signingRequests: action.signingRequests.map((req) =>
omit(req, 'displayData'),
),
}
: omit(action, 'displayData');

this.eventEmitter.emit(ActionsEvent.ACTION_COMPLETED, {
type: isSuccess
Expand Down Expand Up @@ -193,23 +207,36 @@ export class ActionsService implements OnStorageReady {
ethErrors.provider.userRejectedRequest(),
);
} else {
await this.saveActions({
...currentPendingActions,
[id]: {
...pendingMessage,
displayData: {
...pendingMessage.displayData,
...displayData,
if ('signingRequests' in pendingMessage) {
await this.saveActions({
...currentPendingActions,
[id]: {
...pendingMessage,
signingRequests: pendingMessage.signingRequests,
status,
result,
error,
},
signingData: getUpdatedSigningData(
pendingMessage.signingData,
signingData,
),
status,
result,
error,
},
});
});
} else {
await this.saveActions({
...currentPendingActions,
[id]: {
...pendingMessage,
displayData: {
...pendingMessage.displayData,
...displayData,
},
signingData: getUpdatedSigningData(
pendingMessage.signingData,
signingData,
),
status,
result,
error,
},
});
}
}
}

Expand All @@ -224,6 +251,34 @@ export class ActionsService implements OnStorageReady {
throw new Error(`No request found with id: ${id}`);
}

if ('signingRequests' in pendingRequest && 'maxFeeRate' in newData) {
const updated = {
...pendingRequest,
signingRequests: pendingRequest.signingRequests.map((req) => {
if (req.signingData.type !== RpcMethod.ETH_SEND_TRANSACTION) {
throw new Error('Unsupported bulk operation');
}

return {
...req,
signingData: {
...req.signingData,
data: {
...req.signingData.data,
maxFeePerGas: newData.maxFeeRate,
maxPriorityFeePerGas: newData.maxTipRate,
},
},
};
}),
};
await this.saveActions({
...currentPendingRequests,
[id]: updated,
});
return;
}

const { signingData, displayData } = this.approvalController.updateTx(
id,
newData,
Expand Down
4 changes: 2 additions & 2 deletions src/background/services/actions/handlers/getActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { ExtensionRequest } from '@src/background/connections/extensionConnectio
import { ExtensionRequestHandler } from '@src/background/connections/models';
import { injectable } from 'tsyringe';
import { ActionsService } from '../ActionsService';
import { Action } from '../models';
import { Action, MultiTxAction } from '../models';

type HandlerType = ExtensionRequestHandler<
ExtensionRequest.ACTION_GET,
Action,
Action | MultiTxAction,
[messageId: string]
>;

Expand Down
18 changes: 14 additions & 4 deletions src/background/services/actions/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { DappInfo, RpcMethod, SigningData } from '@avalabs/vm-module-types';
import {
DappInfo,
RpcMethod,
SigningData,
SigningRequest,
} from '@avalabs/vm-module-types';
import {
DAppProviderRequest,
JsonRpcRequestPayload,
Expand All @@ -21,23 +26,28 @@ export type Action<DisplayData = any, Params = any> = JsonRpcRequestPayload<
> & {
scope: string;
context?: Record<string, unknown>;
signingData?: SigningData;
dappInfo?: DappInfo;
[ACTION_HANDLED_BY_MODULE]?: boolean;
time?: number;
status?: ActionStatus;
result?: any;
error?: string;
displayData: DisplayData;
// we store the window ID of the confirmation popup so
// that we can clean up stale actions later
popupWindowId?: number;
inAppPromptId?: number;
actionId?: string;
} & {
signingData?: SigningData;
displayData: DisplayData;
};

export type MultiTxAction = Omit<Action, 'signingData'> & {
signingRequests: SigningRequest[];
};

export interface Actions {
[id: string]: Action;
[id: string]: Action | MultiTxAction;
}

export interface ActionUpdate<DisplayData = any> {
Expand Down
9 changes: 6 additions & 3 deletions src/background/services/approvals/ApprovalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import browser from 'webextension-polyfill';

import { openExtensionNewWindow } from '@src/utils/extensionUtils';

import { Action } from '../actions/models';
import { Action, MultiTxAction } from '../actions/models';
import { ActionsService } from '../actions/ActionsService';
import { ApprovalEvent } from './models';

Expand All @@ -14,7 +14,7 @@ export class ApprovalService {

constructor(private actionsService: ActionsService) {}

#isInAppRequest(action: Action): boolean {
#isInAppRequest(action: Action | MultiTxAction): boolean {
if (action.site?.domain === browser.runtime.id) {
return true;
}
Expand All @@ -26,7 +26,10 @@ export class ApprovalService {

return false;
}
async requestApproval(action: Action, route: string): Promise<Action> {
async requestApproval(
action: Action | MultiTxAction,
route: string
): Promise<Action | MultiTxAction> {
const url = `${route}?actionId=${action.actionId}`;

if (this.#isInAppRequest(action)) {
Expand Down
4 changes: 4 additions & 0 deletions src/background/services/wallet/WalletService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,10 @@ export class WalletService implements OnUnlock {
throw new Error(`no active network found`);
}

if (!('displayData' in action) || !action.displayData) {
throw new Error('missing required information');
}

const wallet = await this.getWallet({
accountIndex: action.displayData.messageParams.accountIndex,
network,
Expand Down
Loading

0 comments on commit 9436ddc

Please sign in to comment.