-
Notifications
You must be signed in to change notification settings - Fork 587
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
feat: realtime balance fetcher for recent transactions #1717
Changes from all commits
e5af20e
ea902f4
990f4ef
546c866
13d7a17
02f01f2
a2e143d
52809fb
91196b5
8dbf3c2
1c31c7b
52d1e3f
38b0140
9894b5d
3d21571
6e6ac2e
1144242
771a269
c66b807
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--- | ||
"@rainbow-me/rainbowkit": patch | ||
--- | ||
|
||
Added real-time balance fetching based on the [Recent Transaction](https://www.rainbowkit.com/docs/recent-transactions) API. As a transaction is confirmed on-chain, the user's gas balance will be updated to reflect the transaction. | ||
|
||
```tsx | ||
import { useAddRecentTransaction } from '@rainbow-me/rainbowkit'; | ||
|
||
export default () => { | ||
const addRecentTransaction = useAddRecentTransaction(); | ||
|
||
return ( | ||
<button | ||
onClick={() => { | ||
addRecentTransaction({ | ||
hash: '0x...', | ||
description: '...', | ||
}); | ||
}} | ||
> | ||
Add recent transaction | ||
</button> | ||
); | ||
}; | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Address } from 'viem'; | ||
import { useBalance } from 'wagmi'; | ||
import { useMainnetEnsAvatar } from './useMainnetEnsAvatar'; | ||
import { useMainnetEnsName } from './useMainnetEnsName'; | ||
|
||
interface UseProfileParameters { | ||
address?: Address; | ||
includeBalance?: boolean; | ||
} | ||
|
||
export function useProfile({ address, includeBalance }: UseProfileParameters) { | ||
const ensName = useMainnetEnsName(address); | ||
const ensAvatar = useMainnetEnsAvatar(ensName); | ||
const { data: balance } = useBalance({ | ||
address: includeBalance ? address : undefined, | ||
}); | ||
|
||
return { ensName, ensAvatar, balance }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
import React, { createContext, useContext, useEffect, useState } from 'react'; | ||
import { PublicClient } from 'viem'; | ||
import { useAccount, usePublicClient } from 'wagmi'; | ||
import React, { | ||
createContext, | ||
useCallback, | ||
useContext, | ||
useEffect, | ||
useState, | ||
} from 'react'; | ||
import { PublicClient, TransactionReceipt } from 'viem'; | ||
import { useAccount, useBalance, usePublicClient } from 'wagmi'; | ||
import { useChainId } from '../hooks/useChainId'; | ||
import { TransactionStore, createTransactionStore } from './transactionStore'; | ||
|
||
|
@@ -20,13 +26,26 @@ export function TransactionStoreProvider({ | |
const provider = usePublicClient() as PublicClient; | ||
const { address } = useAccount(); | ||
const chainId = useChainId(); | ||
const { refetch } = useBalance({ | ||
address, | ||
query: { | ||
enabled: false, | ||
}, | ||
}); | ||
|
||
// Use existing store if it exists, or lazily create one | ||
const [store] = useState( | ||
() => | ||
storeSingleton ?? (storeSingleton = createTransactionStore({ provider })), | ||
); | ||
|
||
const onTransactionStatus = useCallback( | ||
(txStatus: TransactionReceipt['status']) => { | ||
if (txStatus === 'success') refetch(); | ||
}, | ||
[refetch], | ||
); | ||
|
||
// Keep store provider up to date with any wagmi changes | ||
useEffect(() => { | ||
store.setProvider(provider); | ||
|
@@ -39,6 +58,12 @@ export function TransactionStoreProvider({ | |
} | ||
}, [store, address, chainId]); | ||
|
||
useEffect(() => { | ||
if (store && address && chainId) { | ||
return store.onTransactionStatus(onTransactionStatus); | ||
} | ||
}, [store, address, chainId, onTransactionStatus]); | ||
Comment on lines
+61
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could pass this logic in here which would be less work, but that would cause a lot of re-renders + make unnecessary RPC calls since it'll happen pretty often due to how our transaction store provider works. We'll call refetch whenever is necessary to update our balance (e.g when a tx has successfully passed). |
||
|
||
return ( | ||
<TransactionStoreContext.Provider value={store}> | ||
{children} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import type { Address, PublicClient } from 'viem'; | ||
import type { Address, PublicClient, TransactionReceipt } from 'viem'; | ||
|
||
const storageKey = 'rk-transactions'; | ||
|
||
|
@@ -67,6 +67,9 @@ export function createTransactionStore({ | |
|
||
let provider = initialProvider; | ||
const listeners: Set<() => void> = new Set(); | ||
const transactionListeners: Set< | ||
(txStatus: TransactionReceipt['status']) => void | ||
> = new Set(); | ||
const transactionRequestCache: Map<string, Promise<void>> = new Map(); | ||
|
||
function setProvider(newProvider: PublicClient): void { | ||
|
@@ -153,6 +156,8 @@ export function createTransactionStore({ | |
// @ts-ignore - types changed with [email protected] | ||
status === 0 || status === 'reverted' ? 'failed' : 'confirmed', | ||
); | ||
|
||
notifyTransactionListeners(status); | ||
}) | ||
.catch(() => { | ||
// If a transaction is not found or cancelled | ||
|
@@ -207,6 +212,14 @@ export function createTransactionStore({ | |
} | ||
} | ||
|
||
function notifyTransactionListeners( | ||
txStatus: TransactionReceipt['status'], | ||
): void { | ||
for (const transactionListener of transactionListeners) { | ||
transactionListener(txStatus); | ||
} | ||
} | ||
|
||
function onChange(fn: () => void): () => void { | ||
listeners.add(fn); | ||
|
||
|
@@ -215,10 +228,21 @@ export function createTransactionStore({ | |
}; | ||
} | ||
|
||
function onTransactionStatus( | ||
fn: (txStatus: TransactionReceipt['status']) => void, | ||
): () => void { | ||
transactionListeners.add(fn); | ||
|
||
return () => { | ||
transactionListeners.delete(fn); | ||
}; | ||
} | ||
|
||
return { | ||
addTransaction, | ||
clearTransactions, | ||
getTransactions, | ||
onTransactionStatus, | ||
onChange, | ||
setProvider, | ||
waitForPendingTransactions, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@magiziz Should we move this logic to
useProfile
so that we're caching it and reduce the number of balance fetches? Currently I believe this hook would be called even if showBalance is disabled, and would cause duplicate balance fetch calls when rendering Profile -> Transaction UI