Skip to content

[Issue 4210] Implement EIP7702 #4303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: subwallet-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@
"@polkadot/util-crypto": "^13.4.3",
"@polkadot/x-global": "^13.4.3",
"@subwallet/chain-list": "0.2.102",
"@subwallet/keyring": "^0.1.9",
"@subwallet/keyring": "file:../SubWallet-Base/packages/keyring/build/",
"@subwallet/react-ui": "5.1.2-b79",
"@subwallet/ui-keyring": "^0.1.9",
"@subwallet/ui-keyring": "file:../SubWallet-Base/packages/ui-keyring/build/",
"@types/bn.js": "^5.1.6",
"@zondax/ledger-substrate": "1.0.1",
"babel-core": "^7.0.0-bridge.0",
Expand Down
1 change: 1 addition & 0 deletions packages/extension-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@walletconnect/sign-client": "^2.14.0",
"@walletconnect/types": "^2.14.0",
"@walletconnect/utils": "^2.14.0",
"abstractionkit": "^0.2.16",
"avail-js-sdk": "^0.2.12",
"axios": "^1.6.2",
"bignumber.js": "^9.1.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/extension-base/src/background/KoniTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { SignerResult } from '@polkadot/types/types/extrinsic';
import { HexString } from '@polkadot/util/types';

import { TransactionWarning } from './warnings/TransactionWarning';
import {UserOperationV8} from "abstractionkit";

export enum RuntimeEnvironment {
Web = 'Web',
Expand Down Expand Up @@ -1201,7 +1202,7 @@ export interface ErrorNetworkConnection {
export interface ConfirmationDefinitions {
addNetworkRequest: [ConfirmationsQueueItem<_NetworkUpsertParams>, ConfirmationResult<null>],
addTokenRequest: [ConfirmationsQueueItem<AddTokenRequestExternal>, ConfirmationResult<boolean>],
evmSignatureRequest: [ConfirmationsQueueItem<EvmSignatureRequest>, ConfirmationResult<string>],
evmSignatureRequest: [ConfirmationsQueueItem<EvmSignatureRequest>, ConfirmationResult<string | UserOperationV8>],
evmSendTransactionRequest: [ConfirmationsQueueItem<EvmSendTransactionRequest>, ConfirmationResult<string>]
evmWatchTransactionRequest: [ConfirmationsQueueItem<EvmWatchTransactionRequest>, ConfirmationResult<string>],
errorConnectNetwork: [ConfirmationsQueueItem<ErrorNetworkConnection>, ConfirmationResult<null>]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { TokenHasBalanceInfo, TokenPayFeeInfo } from '@subwallet/extension-base/
import { calculateToAmountByReservePool } from '@subwallet/extension-base/services/fee-service/utils';
import { batchExtrinsicSetFeeHydration, getAssetHubTokensCanPayFee, getHydrationTokensCanPayFee } from '@subwallet/extension-base/services/fee-service/utils/tokenPayFee';
import { ClaimPolygonBridgeNotificationMetadata, NotificationSetup } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
import { convertEVMTransactionConfigToEip7702UserOp } from '@subwallet/extension-base/services/keyring-service/context/handlers/Eip7702';
import { AppBannerData, AppConfirmationData, AppPopupData } from '@subwallet/extension-base/services/mkt-campaign-service/types';
import { EXTENSION_REQUEST_URL } from '@subwallet/extension-base/services/request-service/constants';
import { AuthUrls } from '@subwallet/extension-base/services/request-service/types';
Expand Down Expand Up @@ -1343,6 +1344,7 @@ export default class KoniExtension {
const errors = validateTransferRequest(transferTokenInfo, from, to, value, transferAll);
const warnings: TransactionWarning[] = [];

const fromChainInfo = this.#koniState.getChainInfo(chain);
const nativeTokenInfo = this.#koniState.getNativeTokenInfo(chain);
const nativeTokenSlug: string = nativeTokenInfo.slug;
const isTransferNativeToken = nativeTokenSlug === tokenSlug;
Expand Down Expand Up @@ -1400,6 +1402,8 @@ export default class KoniExtension {
value: txVal
});
}

transaction = await convertEVMTransactionConfigToEip7702UserOp(transaction, evmApi, fromChainInfo);
} else if (_isMantaZkAsset(transferTokenInfo)) {
transaction = undefined;
transferAmount.value = '0';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2019-2022 @subwallet/extension-base authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { _ChainInfo } from '@subwallet/chain-list/types';
import { _EvmApi } from '@subwallet/extension-base/services/chain-service/types';
import { _getEvmChainId } from '@subwallet/extension-base/services/chain-service/utils';
import { CandidePaymaster, MetaTransaction, Simple7702Account, UserOperationV8 } from 'abstractionkit';
import { TransactionConfig } from 'web3-core';

export const CANDIDE_BUNDLER_URL = 'https://api.candide.dev/bundler/v3/sepolia/99700fa2b1177145a779792fdeab5cd9';
export const CANDIDE_PAYMASTER_URL = 'https://api.candide.dev/paymaster/v3/sepolia/99700fa2b1177145a779792fdeab5cd9';

export const CANDIDE_CHAIN_SLUG_MAPPING = {
sepolia_ethereum: 'sepolia',
arbitrum_sepolia: 'arbitrum-sepolia'
};

export async function convertEVMTransactionConfigToEip7702UserOp (transactionConfig: TransactionConfig, evmApi: _EvmApi, chainInfo: _ChainInfo): Promise<UserOperationV8> {
if (!transactionConfig.from || !transactionConfig.to || !transactionConfig.value) {
throw new Error('Invalid transaction config');
}

const smartAccount = new Simple7702Account(transactionConfig.from as string);
const tx: MetaTransaction = {
to: transactionConfig.to,
value: BigInt(transactionConfig.value as string),
data: transactionConfig.data || ''
};

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
let userOp: UserOperationV8 = await smartAccount.createUserOperation(
[tx],
evmApi.apiUrl,
CANDIDE_BUNDLER_URL,
{
eip7702Auth: {
chainId: BigInt(_getEvmChainId(chainInfo) as number)
}
});

const paymaster = new CandidePaymaster(CANDIDE_PAYMASTER_URL);

// sponsor user operation
// const [paymasterUserOperation, _sponsorMetadata] = await paymaster.createSponsorPaymasterUserOperation(
// userOp,
// CANDIDE_BUNDLER_URL,
// 'c27a8e5e0e9c3146'
// );
//
// userOp = paymasterUserOperation;

// pay with erc20 token
userOp = await paymaster.createTokenPaymasterUserOperation(
smartAccount,
userOp,
'0xFa5854FBf9964330d761961F46565AB7326e5a3b',
CANDIDE_BUNDLER_URL
);

// console.log('sponsor data', _sponsorMetadata);

return userOp;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import RequestService from '@subwallet/extension-base/services/request-service';
import { anyNumberToBN } from '@subwallet/extension-base/utils/eth';
import { isInternalRequest } from '@subwallet/extension-base/utils/request';
import keyring from '@subwallet/ui-keyring';
import { UserOperationV8 } from 'abstractionkit';
import BigN from 'bignumber.js';
import BN from 'bn.js';
import { toBuffer } from 'ethereumjs-util';
Expand Down Expand Up @@ -53,6 +54,79 @@ export default class EvmRequestHandler {
return this.confirmationsQueueSubject;
}

public async addEip7702Confirmation<CT extends ConfirmationType> (
id: string,
url: string,
type: CT,
payload: ConfirmationDefinitions[CT][0]['payload'],
options: ConfirmationsQueueItemOptions = {},
validator?: (input: ConfirmationDefinitions[CT][1]) => Error | undefined
): Promise<ConfirmationDefinitions[CT][1]> {
const confirmations = this.confirmationsQueueSubject.getValue();
const confirmationType = confirmations[type] as Record<string, ConfirmationDefinitions[CT][0]>;

const payloadJson = JSON.stringify(payload, (key: string, value: any) => {
if (typeof value === 'bigint') {
return value.toString() + 'n';
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value;
});
// only for displaying purposes on UI, not for processing
const modifiedPayload = JSON.parse(payloadJson) as ConfirmationDefinitions[CT][0]['payload'];

const isInternal = isInternalRequest(url);

if (['evmSignatureRequest', 'evmSendTransactionRequest'].includes(type)) {
const isAlwaysRequired = await this.#requestService.settingService.isAlwaysRequired;

if (isAlwaysRequired) {
this.#requestService.keyringService.lock();
}
}

// Check duplicate request
const duplicated = Object.values(confirmationType).find((c) => (c.url === url) && (c.payloadJson === payloadJson));

if (duplicated) {
throw new EvmProviderError(EvmProviderErrorType.INVALID_PARAMS, t('Duplicate request'));
}

confirmationType[id] = {
id,
url,
isInternal,
payload: modifiedPayload,
payloadJson,
...options
} as ConfirmationDefinitions[CT][0];

const promise = new Promise<ConfirmationDefinitions[CT][1]>((resolve, reject) => {
this.confirmationsPromiseMap[id] = {
validator: validator,
resolver: {
resolve: resolve,
reject: reject
}
};
});

this.confirmationsQueueSubject.next(confirmations);

if (!isInternal) {
this.#requestService.popupOpen();
}

if (options.isPassConfirmation) {
await this.completeConfirmation({ [type]: { id, url, isApproved: true, payload: '' } });
}

this.#requestService.updateIconV2();

return promise;
}

public async addConfirmation<CT extends ConfirmationType> (
id: string,
url: string,
Expand Down Expand Up @@ -239,7 +313,33 @@ export default class EvmRequestHandler {
private async decorateResult<T extends ConfirmationType> (t: T, request: ConfirmationDefinitions[T][0], result: ConfirmationDefinitions[T][1]) {
if (result.payload === '') {
if (t === 'evmSignatureRequest') {
result.payload = await this.signMessage(request as ConfirmationDefinitions['evmSignatureRequest'][0]);
// result.payload = await this.signMessage(request as ConfirmationDefinitions['evmSignatureRequest'][0]);

const data = request as ConfirmationDefinitions['evmSignatureRequest'][0];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const eip7702Payload = JSON.parse(data.payloadJson, (key: string, value: any) => {
if (typeof value === 'string' && /^\d+n$/.test(value)) {
return BigInt(value.slice(0, -1));
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value;
});

// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const userOp = eip7702Payload?.payload as UserOperationV8;

const pair = keyring.getPair(userOp.sender);

if (pair.isLocked) {
keyring.unlockPair(pair.address);
}

console.log('user op', eip7702Payload);

result.payload = pair.evm.signUserOperationWith7702(userOp);
console.log('signed', userOp);
} else if (t === 'evmSendTransactionRequest') {
result.payload = await this.signTransaction(request as ConfirmationDefinitions['evmSendTransactionRequest'][0]);
}
Expand Down Expand Up @@ -276,6 +376,8 @@ export default class EvmRequestHandler {
// Validate response from confirmation popup some info like password, response format....
const error = validator && validator(result);

console.log('error', error);

if (error) {
resolver.reject(error);
}
Expand Down
10 changes: 10 additions & 0 deletions packages/extension-base/src/services/request-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ export default class RequestService {
return this.#evmRequestHandler.addConfirmation(id, url, type, payload, options, validator);
}

public addEip7702Confirmation<CT extends ConfirmationType> (
id: string,
url: string,
type: CT,
payload: ConfirmationDefinitions[CT][0]['payload'],
options: ConfirmationsQueueItemOptions = {}
): Promise<ConfirmationDefinitions[CT][1]> {
return this.#evmRequestHandler.addEip7702Confirmation(id, url, type, payload, options);
}

public addConfirmationTon<CT extends ConfirmationTypeTon> (
id: string,
url: string,
Expand Down
Loading