Skip to content
Merged
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
17 changes: 17 additions & 0 deletions .yarn/versions/e8690a75.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
releases:
"@fluent-wallet/backend-client": patch
"@fluent-wallet/bundler-client": patch
"@fluent-wallet/user-operation": patch
"@fluent-wallet/wallet_get-eip7702-account-states": patch
"@fluent-wallet/wallet_get-user-operation-nonce-state": patch
"@fluent-wallet/wallet_handle-user-operation": patch
"@fluent-wallet/wallet_prepare-sponsorship": patch
"@fluent-wallet/wallet_prepare-user-operation": patch
"@fluent-wallet/wallet_send-user-operation": patch
browser-extension: patch
helios-background: patch
helios-popup: patch

declined:
- helios
- "@fluent-wallet/consts"
49 changes: 38 additions & 11 deletions packages/backend-client/paymaster.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
/**
* @typedef {Object} PaymasterConfig
* @property {string[]} smartAccountWhitelist Supported EIP-7702 delegate addresses.
* @property {string[]} contractWhitelist Sponsored contract addresses.
* @property {number} maxGasCost Maximum sponsored gas cost.
*/

/**
* @typedef {Object} PaymasterStubRequest
* @property {string} sender Smart account address.
* @property {string} delegation EIP-7702 delegate address.
*/

/**
* @typedef {Object} PaymasterStub
* @property {string} address Verifying Paymaster address.
* @property {string} data 77-byte stub data for gas estimation.
* @property {string} data Stub paymaster data used for gas estimation.
*/

/**
Expand All @@ -20,30 +33,44 @@
* @property {string} paymaster Verifying Paymaster address.
* @property {string} paymasterVerificationGasLimit Paymaster verification gas limit.
* @property {string} paymasterPostOpGasLimit Paymaster post-operation gas limit.
* @property {string} paymasterData Stub Paymaster data used for estimation.
* @property {string} delegatedContract EIP-7702 delegate, or zero address.
* @property {string} paymasterData Stub paymaster data used for estimation.
*/

export function createPaymasterMethods(request) {
return {
/**
* Returns dummy Paymaster data for gas estimation.
* This endpoint does not check sponsorship eligibility.
* GET /aa/paymaster/stub
* Returns the Verifying Paymaster configuration exposed to clients.
*
* GET /aa/paymaster/config
*
* @returns {Promise<PaymasterConfig>}
*/
getPaymasterConfig() {
return request('/aa/paymaster/config')
},

/**
* Returns stub paymaster data for gas estimation.
*
* POST /aa/paymaster/stub
*
* @param {PaymasterStubRequest} params Stub request parameters.
* @returns {Promise<PaymasterStub>}
*/
getPaymasterStub() {
return request('/aa/paymaster/stub')
getPaymasterStub(params) {
return request('/aa/paymaster/stub', {
method: 'POST',
body: params,
})
},

/**
* Validates and signs a flat UserOperationWithAuth.
* Returns the signed 77-byte paymasterData only.
* Validates and signs a UserOperation.
*
* POST /aa/paymaster/sign
*
* @param {PaymasterSignRequest} userOperation UserOperation to sign.
* @returns {Promise<string>}
* @returns {Promise<string>} Signed paymaster data.
*/
signPaymasterUserOperation(userOperation) {
return request('/aa/paymaster/sign', {
Expand Down
2 changes: 0 additions & 2 deletions packages/consts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,12 @@ export const EIP7702_NETWORK_CONFIGS = {
[CFX_ESPACE_MAINNET_CHAINID]: {
entryPointAddress: ENTRY_POINT_CONTRACT_ADDRESS,
entryPointVersion: ENTRY_POINT_VERSION,
delegateAddress: '0x4938A2036200fcb9Bfb05368836CcE2CB8abd57f',
bundlerEndpoint: 'https://bundler.confluxrpc.org',
backendBaseUrl: FLUENT_BACKEND_BASE_URLS[CFX_ESPACE_MAINNET_CHAINID],
},
[CFX_ESPACE_TESTNET_CHAINID]: {
entryPointAddress: ENTRY_POINT_CONTRACT_ADDRESS,
entryPointVersion: ENTRY_POINT_VERSION,
delegateAddress: '0x8F5d8d7f3467Dd2e34186E232D8b5a5f35462949',
bundlerEndpoint: 'https://aa-bundle.confluxrpc.org',
backendBaseUrl: FLUENT_BACKEND_BASE_URLS[CFX_ESPACE_TESTNET_CHAINID],
},
Expand Down
11 changes: 6 additions & 5 deletions packages/popup/src/pages/EIP7702Upgrade/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ function EIP7702Upgrade() {
: [],
)
const [targetUpgradeAccountState = {}] = targetUpgradeAccountStates
const configuredDelegateAddress =
EIP7702_NETWORK_CONFIGS[targetUpgradeNetworkChainId]?.delegateAddress
const {preferredDelegateAddress} = targetUpgradeAccountState

const targetNetworkAccountStateValue = targetUpgradeAccountState.state
const showSwitchRequired =
targetNetworkAccountStateValue === 'delegatedToOther'

const canPrepareUpgradeTx = Boolean(
address && configuredDelegateAddress && targetNetworkAccountStateValue,
address && preferredDelegateAddress && targetNetworkAccountStateValue,
)

let supportedNetworkButtonLabelKey = 'bind'
Expand Down Expand Up @@ -120,8 +120,9 @@ function EIP7702Upgrade() {

const onClickSwitch = () => {
if (targetNetworkAccountStateValue !== 'delegatedToOther') return

openEip7702ConfirmPage({
delegateAddress: configuredDelegateAddress,
delegateAddress: preferredDelegateAddress,
action: 'switch',
})
}
Expand All @@ -137,7 +138,7 @@ function EIP7702Upgrade() {

if (targetNetworkAccountStateValue === 'notDelegated') {
openEip7702ConfirmPage({
delegateAddress: configuredDelegateAddress,
delegateAddress: preferredDelegateAddress,
action: 'bind',
})
}
Expand Down
71 changes: 40 additions & 31 deletions packages/rpcs/wallet_getEip7702AccountState/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {map, dbid, oneOrMore} from '@fluent-wallet/spec'
import {createBackendClient} from '@fluent-wallet/backend-client'
import {EIP7702_NETWORK_CONFIGS} from '@fluent-wallet/consts'
import {getEip7702DelegateAddressFromCode} from '@fluent-wallet/detect-address-type'
import {map, dbid, oneOrMore} from '@fluent-wallet/spec'

export const NAME = 'wallet_getEip7702AccountStates'

Expand All @@ -18,7 +19,7 @@ export const permissions = {
db: ['getAccountById', 'getNetworkById', 'accountAddrByNetwork'],
}

function parseEip7702AccountCode(accountCodeHex, configuredDelegateAddress) {
function parseEip7702AccountCode(accountCodeHex, supportedDelegateAddresses) {
if (!accountCodeHex || accountCodeHex === '0x') {
return {
state: 'notDelegated',
Expand All @@ -35,77 +36,85 @@ function parseEip7702AccountCode(accountCodeHex, configuredDelegateAddress) {
}
}

if (delegatedAddress === configuredDelegateAddress.toLowerCase()) {
return {
state: 'delegatedToConfigured',
delegatedAddress,
}
}

return {
state: 'delegatedToOther',
state: supportedDelegateAddresses.includes(delegatedAddress)
? 'delegatedToConfigured'
: 'delegatedToOther',
delegatedAddress,
}
}

export const main = async ({
Err: {InvalidParams},
Err: {InvalidParams, Server},
db: {getAccountById, getNetworkById, accountAddrByNetwork},
rpcs: {eth_getCode},
params: accountStateQueries,
}) => {
const accountStates = await Promise.all(
}) =>
Promise.all(
accountStateQueries.map(async ({accountId, networkId}) => {
const account = getAccountById(accountId)
if (!account) throw InvalidParams(`Invalid account id ${accountId}`)
if (!account) {
throw InvalidParams(`Invalid account id ${accountId}`)
}

const network = getNetworkById(networkId)
if (!network) throw InvalidParams(`Invalid network id ${networkId}`)
if (!network) {
throw InvalidParams(`Invalid network id ${networkId}`)
}

const targetNetworkAddressRecord = accountAddrByNetwork({
const accountAddress = accountAddrByNetwork({
account: accountId,
network: networkId,
})
const targetNetworkAccountAddress = targetNetworkAddressRecord?.value
})?.value

if (!targetNetworkAccountAddress) {
if (!accountAddress) {
throw InvalidParams(
`Account ${accountId} has no address on network ${networkId}`,
)
}

const configuredDelegateAddress =
EIP7702_NETWORK_CONFIGS[network.chainId]?.delegateAddress || null
const {backendBaseUrl} = EIP7702_NETWORK_CONFIGS[network.chainId] || {}

if (!configuredDelegateAddress) {
if (!backendBaseUrl) {
return {
accountId,
networkId,
state: 'unsupportedNetwork',
accountAddress: targetNetworkAccountAddress,
accountAddress,
chainId: network.chainId,
code: null,
delegatedAddress: null,
configuredDelegateAddress: null,
preferredDelegateAddress: null,
}
}

const {smartAccountWhitelist} = await createBackendClient({
baseUrl: backendBaseUrl,
}).getPaymasterConfig()

if (!smartAccountWhitelist?.length) {
throw Server('No EIP-7702 delegate address is configured')
}

const supportedDelegateAddresses = smartAccountWhitelist.map(address =>
address.toLowerCase(),
)

const preferredDelegateAddress = supportedDelegateAddresses[0]

const accountCodeHex = await eth_getCode({networkName: network.name}, [
targetNetworkAccountAddress,
accountAddress,
'latest',
])

return {
accountId,
networkId,
...parseEip7702AccountCode(accountCodeHex, configuredDelegateAddress),
accountAddress: targetNetworkAccountAddress,
...parseEip7702AccountCode(accountCodeHex, supportedDelegateAddresses),
accountAddress,
chainId: network.chainId,
code: accountCodeHex,
configuredDelegateAddress,
preferredDelegateAddress,
}
}),
)

return accountStates
}
1 change: 1 addition & 0 deletions packages/rpcs/wallet_getEip7702AccountState/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"packageManager": "yarn@3.0.1",
"version": "0.0.9",
"dependencies": {
"@fluent-wallet/backend-client": "workspace:packages/backend-client",
"@fluent-wallet/consts": "workspace:packages/consts",
"@fluent-wallet/detect-address-type": "workspace:packages/detect-address-type",
"@fluent-wallet/spec": "1.0.22"
Expand Down
32 changes: 17 additions & 15 deletions packages/rpcs/wallet_prepareSponsorship/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {EIP7702_NETWORK_CONFIGS, NULL_HEX_ADDRESS} from '@fluent-wallet/consts'
import {EIP7702_NETWORK_CONFIGS} from '@fluent-wallet/consts'
import {
createBackendClient,
BackendServiceError,
Expand Down Expand Up @@ -53,8 +53,7 @@ const PAYMASTER_ERROR_REASONS = {
4001: 'maxGasCostExceeded',
4002: 'smartAccountNotWhitelisted',
4003: 'contractNotWhitelisted',
4004: 'paymasterPaused',
4005: 'tooManyPendingUserOperations',
4004: 'tooManyPendingUserOperations',
}

function getPaymasterErrorReason(error) {
Expand Down Expand Up @@ -146,11 +145,12 @@ export const main = async ({
}

const sender = accountState.accountAddress.toLowerCase()
const {backendBaseUrl, delegateAddress} = networkConfig
const {backendBaseUrl} = networkConfig
const sponsorshipDelegateAddress =
accountState.state === 'delegatedToConfigured'
? accountState.delegatedAddress
: accountState.preferredDelegateAddress

if (!backendBaseUrl) {
return unsupportedResult('sponsorshipNotConfigured')
}
let authorization

if (requiredDelegationAction) {
Expand Down Expand Up @@ -192,7 +192,7 @@ export const main = async ({

authorization = {
chainId: network.chainId,
address: delegateAddress,
address: sponsorshipDelegateAddress,
nonce: networkLatestNonce,
}
}
Expand All @@ -214,7 +214,10 @@ export const main = async ({
let paymasterStub

try {
paymasterStub = await backendClient.getPaymasterStub()
paymasterStub = await backendClient.getPaymasterStub({
sender,
delegation: sponsorshipDelegateAddress,
})
} catch (error) {
return unavailableResult(getPaymasterErrorReason(error))
}
Expand All @@ -234,16 +237,15 @@ export const main = async ({
},
)

const {authorization: preparedAuthorization, ...paymasterUserOperation} =
prepared.userOperation
const paymasterUserOperation = {...prepared.userOperation}
delete paymasterUserOperation.authorization

let signedPaymasterData

try {
signedPaymasterData = await backendClient.signPaymasterUserOperation({
...paymasterUserOperation,
delegatedContract: preparedAuthorization?.address ?? NULL_HEX_ADDRESS,
})
signedPaymasterData = await backendClient.signPaymasterUserOperation(
paymasterUserOperation,
)
} catch (error) {
return unavailableResult(
getPaymasterErrorReason(error),
Expand Down
Loading
Loading