Skip to content
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
19 changes: 15 additions & 4 deletions src/wallet-account-evm-7702-gasless.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Contract } from 'ethers'

import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'

import { ENTRYPOINT_V8, Simple7702Account } from 'abstractionkit'
import { ENTRYPOINT_V8, Simple7702Account, fetchAccountNonce } from 'abstractionkit'

import WalletAccountReadOnlyEvm7702Gasless from './wallet-account-read-only-evm-7702-gasless.js'

Expand Down Expand Up @@ -191,7 +191,8 @@ export default class WalletAccountEvm7702Gasless extends WalletAccountReadOnlyEv
* Quotes the costs of a send transaction operation. Caches the built user
* operation against the serialized transaction so that a subsequent
* sendTransaction call with the same tx can skip the gas-estimation +
* paymaster round-trip. Cache entries expire after 2 minutes.
* paymaster round-trip, after a lightweight on-chain nonce check that
* re-quotes only if the nonce has moved. Cache entries expire after 2 minutes.
*
* @param {EvmTransaction | EvmTransaction[]} tx - The transaction, or an array of multiple transactions to send in batch.
* @param {Partial<Evm7702GaslessPaymasterTokenConfig | Evm7702GaslessSponsorshipPolicyConfig>} [config] - If set, overrides the given configuration options.
Expand Down Expand Up @@ -240,7 +241,7 @@ export default class WalletAccountEvm7702Gasless extends WalletAccountReadOnlyEv

const { isSponsored } = mergedConfig

let cached = this._consumeCachedQuote(tx)
let cached = await this._consumeFreshQuote(tx)
let fee = 0n

if (cached) {
Expand Down Expand Up @@ -275,7 +276,7 @@ export default class WalletAccountEvm7702Gasless extends WalletAccountReadOnlyEv

const tx = await WalletAccountEvm._getTransferTransaction(options)

let cached = this._consumeCachedQuote(tx)
let cached = await this._consumeFreshQuote(tx)
let fee = 0n

if (cached) {
Expand Down Expand Up @@ -362,6 +363,16 @@ export default class WalletAccountEvm7702Gasless extends WalletAccountReadOnlyEv
return await this._getBundler().sendUserOperation(sponsoredOp, ENTRYPOINT_V8)
}

/** @private */
async _consumeFreshQuote (tx) {
const cached = this._consumeCachedQuote(tx)
if (!cached?.sponsoredOp) return cached

const onChainNonce = await fetchAccountNonce(this._provider, ENTRYPOINT_V8, this._address)

return cached.sponsoredOp.nonce === onChainNonce ? cached : null
}

/** @private */
_consumeCachedQuote (tx) {
const key = WalletAccountEvm7702Gasless._getTxKey(tx)
Expand Down
7 changes: 4 additions & 3 deletions tests/integration/module.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ describe('@wdk/wallet-evm-7702-gasless', () => {
gasCostSpy.mockRestore()
}, TIMEOUT)

test.skip('should not consume cached transfer fee when approve is called in between', async () => {
test('should re-quote a cached transfer whose nonce is stale after an intervening send', async () => {
const account0 = await wallet.getAccountByPath("0'/0/0")
account0._quoteCache.clear()
const gasCostSpy = jest.spyOn(account0, '_getUserOperationGasCost')
Expand All @@ -712,6 +712,7 @@ describe('@wdk/wallet-evm-7702-gasless', () => {

const { fee: quotedFee } = await account0.quoteTransfer(TRANSFER)
expect(gasCostSpy).toHaveBeenCalledTimes(1)
expect(quotedFee).toBeGreaterThan(0n)

const APPROVE_TRANSACTION = {
to: testToken.target,
Expand All @@ -725,9 +726,9 @@ describe('@wdk/wallet-evm-7702-gasless', () => {

const { hash, fee: transferFee } = await account0.transfer(TRANSFER)
await waitForTx(hash, account0)
expect(gasCostSpy).toHaveBeenCalledTimes(2)
expect(gasCostSpy).toHaveBeenCalledTimes(3)

expect(transferFee).toBe(quotedFee)
expect(transferFee).toBeGreaterThan(0n)

gasCostSpy.mockRestore()
}, TIMEOUT)
Expand Down
5 changes: 4 additions & 1 deletion types/src/wallet-account-evm-7702-gasless.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export default class WalletAccountEvm7702Gasless extends WalletAccountReadOnlyEv
* Quotes the costs of a send transaction operation. Caches the built user
* operation against the serialized transaction so that a subsequent
* sendTransaction call with the same tx can skip the gas-estimation +
* paymaster round-trip. Cache entries expire after 2 minutes.
* paymaster round-trip, after a lightweight on-chain nonce check that
* re-quotes only if the nonce has moved. Cache entries expire after 2 minutes.
*
* @param {EvmTransaction | EvmTransaction[]} tx - The transaction, or an array of multiple transactions to send in batch.
* @param {Partial<Evm7702GaslessPaymasterTokenConfig | Evm7702GaslessSponsorshipPolicyConfig>} [config] - If set, overrides the given configuration options.
Expand Down Expand Up @@ -106,6 +107,8 @@ export default class WalletAccountEvm7702Gasless extends WalletAccountReadOnlyEv
/** @private */
private _sendUserOperation;
/** @private */
private _consumeFreshQuote;
/** @private */
private _consumeCachedQuote;
/** @private */
private _sweepExpiredQuotes;
Expand Down
Loading