Skip to content

Commit

Permalink
chore: fix infinite pendingTransactions event and add `pendingTrans…
Browse files Browse the repository at this point in the history
…actionFilter` option (#3514)
  • Loading branch information
magiziz authored Dec 19, 2024
1 parent 0a8ead2 commit 15bfe49
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 11 deletions.
45 changes: 45 additions & 0 deletions .changeset/thirty-pumpkins-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
'@reown/appkit-adapter-wagmi': patch
'@reown/appkit': patch
'@reown/appkit-common': patch
'@reown/appkit-adapter-bitcoin': patch
'@reown/appkit-adapter-ethers': patch
'@reown/appkit-adapter-ethers5': patch
'@reown/appkit-adapter-solana': patch
'@reown/appkit-utils': patch
'@reown/appkit-cdn': patch
'@reown/appkit-cli': patch
'@reown/appkit-core': patch
'@reown/appkit-experimental': patch
'@reown/appkit-polyfills': patch
'@reown/appkit-scaffold-ui': patch
'@reown/appkit-siwe': patch
'@reown/appkit-siwx': patch
'@reown/appkit-ui': patch
'@reown/appkit-wallet': patch
'@reown/appkit-wallet-button': patch
---

Fixed an issue where the `pendingTransactions` event was being emitted infinitely in wagmi adapter.

Additionally another option was added to wagmi adapter called `pendingTransactionsFilter`.

**Example usage**

```ts
const wagmiAdapter = new WagmiAdapter({
networks: [/* Your Networks */],
projectId: "YOUR_PROJECT_ID",
pendingTransactionsFilter: {
enable: true,
pollingInterval: 15_000
}
});

createAppKit({
adapters: [wagmiAdapter],
networks: [/* Your Networks */],
projectId: "YOUR_PROJECT_ID"
});
```

44 changes: 41 additions & 3 deletions packages/adapters/wagmi/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,31 @@ import {
import type { W3mFrameProvider } from '@reown/appkit-wallet'
import { normalize } from 'viem/ens'
import { parseWalletCapabilities } from './utils/helpers.js'
import { LimitterUtil } from './utils/LimitterUtil.js'

interface PendingTransactionsFilter {
enable: boolean
pollingInterval?: number
}

// --- Constants ---------------------------------------------------- //
const DEFAULT_PENDING_TRANSACTIONS_FILTER = {
enable: false,
pollingInterval: 30_000
}

export class WagmiAdapter extends AdapterBlueprint {
public wagmiChains: readonly [Chain, ...Chain[]] | undefined
public wagmiConfig!: Config
public adapterType = 'wagmi'

private pendingTransactionsFilter: PendingTransactionsFilter
private unwatchPendingTransactions: (() => void) | undefined

constructor(
configParams: Partial<CreateConfigParameters> & {
networks: AppKitNetwork[]
pendingTransactionsFilter?: PendingTransactionsFilter
projectId: string
}
) {
Expand All @@ -80,6 +96,11 @@ export class WagmiAdapter extends AdapterBlueprint {
}) as [CaipNetwork, ...CaipNetwork[]]
})

this.pendingTransactionsFilter = {
...DEFAULT_PENDING_TRANSACTIONS_FILTER,
...(configParams.pendingTransactionsFilter ?? {})
}

this.namespace = CommonConstantsUtil.CHAIN.EVM

this.createConfig({
Expand Down Expand Up @@ -166,23 +187,40 @@ export class WagmiAdapter extends AdapterBlueprint {
})
}

private setupWatchers() {
watchPendingTransactions(this.wagmiConfig, {
pollingInterval: 15_000,
private setupWatchPendingTransactions() {
if (!this.pendingTransactionsFilter.enable || this.unwatchPendingTransactions) {
return
}

this.unwatchPendingTransactions = watchPendingTransactions(this.wagmiConfig, {
pollingInterval: this.pendingTransactionsFilter.pollingInterval,
/* Magic RPC does not support the pending transactions. We handle transaction for the AuthConnector cases in AppKit client to handle all clients at once. Adding the onError handler to avoid the error to throw. */
// eslint-disable-next-line @typescript-eslint/no-empty-function
onError: () => {},
onTransactions: () => {
this.emit('pendingTransactions')
LimitterUtil.increase('pendingTransactions')
}
})

const unsubscribe = LimitterUtil.subscribeKey('pendingTransactions', val => {
if (val >= CommonConstantsUtil.LIMITS.PENDING_TRANSACTIONS) {
this.unwatchPendingTransactions?.()
unsubscribe()
}
})
}

private setupWatchers() {
watchAccount(this.wagmiConfig, {
onChange: accountData => {
if (accountData.status === 'disconnected') {
this.emit('disconnect')
}
if (accountData.status === 'connected') {
if (accountData.address) {
this.setupWatchPendingTransactions()

this.emit('accountChanged', {
address: accountData.address,
chainId: accountData.chainId
Expand Down
29 changes: 29 additions & 0 deletions packages/adapters/wagmi/src/tests/LimitUtil.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { LimitterUtil } from '../utils/LimitterUtil'

// -- Tests --------------------------------------------------------------------
describe('LimitterUtil', () => {
it('should have valid default state', () => {
expect(LimitterUtil.state).toEqual({
pendingTransactions: 0
})
})

it('should increase state limit', () => {
LimitterUtil.increase('pendingTransactions')
expect(LimitterUtil.state.pendingTransactions).toBe(1)
})

it('should decrease state limit', () => {
expect(LimitterUtil.state.pendingTransactions).toBe(1)
LimitterUtil.decrease('pendingTransactions')
expect(LimitterUtil.state.pendingTransactions).toBe(0)
})

it('should reset state limit', () => {
LimitterUtil.increase('pendingTransactions')
expect(LimitterUtil.state.pendingTransactions).toEqual(1)
LimitterUtil.reset('pendingTransactions')
expect(LimitterUtil.state.pendingTransactions).toEqual(0)
})
})
55 changes: 48 additions & 7 deletions packages/adapters/wagmi/src/tests/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getEnsAddress as wagmiGetEnsAddress,
writeContract as wagmiWriteContract,
waitForTransactionReceipt,
watchAccount,
getAccount,
watchPendingTransactions,
http
Expand All @@ -22,6 +23,8 @@ import { mainnet } from '@wagmi/core/chains'
import { CaipNetworksUtil } from '@reown/appkit-utils'
import type UniversalProvider from '@walletconnect/universal-provider'
import { mockAppKit } from './mocks/AppKit'
import { ConstantsUtil } from '@reown/appkit-common'
import { LimitterUtil } from '../utils/LimitterUtil'

vi.mock('@wagmi/core', async () => {
const actual = await vi.importActual('@wagmi/core')
Expand All @@ -47,9 +50,7 @@ vi.mock('@wagmi/core', async () => {
reconnect: vi.fn(),
watchAccount: vi.fn(),
watchConnections: vi.fn(),
watchPendingTransactions: vi.fn((_: any, callbacks: any) => {
return callbacks
})
watchPendingTransactions: vi.fn().mockReturnValue(vi.fn())
}
})

Expand Down Expand Up @@ -544,14 +545,54 @@ describe('WagmiAdapter', () => {
})

describe('WagmiAdapter - watchPendingTransactions', () => {
it('should emit pendingTransactions when transactions are pending', () => {
it('should emit pendingTransactions when transactions are pending', async () => {
const adapter = new WagmiAdapter({
networks: mockNetworks,
projectId: mockProjectId,
pendingTransactionsFilter: {
enable: true,
pollingInterval: 5000
}
})

const emitSpy = vi.spyOn(adapter, 'emit' as any)

const watchPendingTransactionsCallback =
vi.mocked(watchPendingTransactions).mock?.calls?.[0]?.[1]
watchPendingTransactionsCallback?.onTransactions(['0xtx1', '0xtx2'])
vi.mocked(watchPendingTransactions).mockImplementation((_, { onTransactions }) => {
onTransactions(['0xtx1', '0xtx2'])
return vi.fn()
})

adapter['setupWatchPendingTransactions']()

expect(emitSpy).toHaveBeenCalledWith('pendingTransactions')
})

it('should limit the amount of pendingTransactions calls', async () => {
const unsubscribe = vi.fn()

vi.mocked(watchAccount).mockImplementation((_, { onChange }) => {
onChange({ address: '0x123', status: 'connected' } as any, {} as any)
return vi.fn()
})

vi.spyOn(wagmiCore, 'watchPendingTransactions').mockReturnValue(unsubscribe)

new WagmiAdapter({
networks: mockNetworks,
projectId: mockProjectId,
pendingTransactionsFilter: {
enable: true,
pollingInterval: 500
}
})

// Set state to maximum limit so we know once we reach the limit it'll unsubscribe the watchPendingTransactions
LimitterUtil.state.pendingTransactions = ConstantsUtil.LIMITS.PENDING_TRANSACTIONS

// Wait for valtio to check for updated state and unsubscribe watchPendingTransactions
await new Promise(resolve => setTimeout(resolve, 100))

expect(unsubscribe).toHaveBeenCalled()
})
})
})
35 changes: 35 additions & 0 deletions packages/adapters/wagmi/src/utils/LimitterUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { subscribeKey as subKey } from 'valtio/vanilla/utils'
import { proxy } from 'valtio/vanilla'

// -- Types --------------------------------------------- //
export interface LimitteStoreUtilState {
pendingTransactions: number
}

type StateKey = keyof LimitteStoreUtilState

// -- State --------------------------------------------- //
const state = proxy<LimitteStoreUtilState>({
pendingTransactions: 0
})

// -- Controller ---------------------------------------- //
export const LimitterUtil = {
state,

subscribeKey<K extends StateKey>(key: K, callback: (value: LimitteStoreUtilState[K]) => void) {
return subKey(state, key, callback)
},

increase(value: StateKey) {
state[value] += 1
},

decrease(value: StateKey) {
state[value] -= 1
},

reset(value: StateKey) {
state[value] = 0
}
}
2 changes: 1 addition & 1 deletion packages/appkit/exports/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const PACKAGE_VERSION = '1.6.0'
export const PACKAGE_VERSION = '1.6.1'
3 changes: 3 additions & 0 deletions packages/common/src/utils/ConstantsUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const ConstantsUtil = {
EIP6963: 'eip6963',
AUTH: 'ID_AUTH'
},
LIMITS: {
PENDING_TRANSACTIONS: 99
},
CHAIN: {
EVM: 'eip155',
SOLANA: 'solana',
Expand Down

0 comments on commit 15bfe49

Please sign in to comment.