Skip to content

Commit 5ea574e

Browse files
committed
feat: add util for querying token balance
1 parent 1cd9516 commit 5ea574e

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

packages/rln/src/contract/token.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { type Address, formatUnits, PublicClient, WalletClient } from "viem";
2+
3+
/**
4+
* Minimal ERC20 ABI containing only the functions we need
5+
*/
6+
export const erc20Abi = [
7+
{
8+
type: "function",
9+
inputs: [{ name: "account", internalType: "address", type: "address" }],
10+
name: "balanceOf",
11+
outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
12+
stateMutability: "view"
13+
},
14+
{
15+
type: "function",
16+
inputs: [],
17+
name: "decimals",
18+
outputs: [{ name: "", internalType: "uint8", type: "uint8" }],
19+
stateMutability: "view"
20+
},
21+
{
22+
type: "function",
23+
inputs: [],
24+
name: "symbol",
25+
outputs: [{ name: "", internalType: "string", type: "string" }],
26+
stateMutability: "view"
27+
}
28+
] as const;
29+
30+
export interface TokenBalance {
31+
/** Raw balance as bigint */
32+
raw: bigint;
33+
/** Formatted balance as string (e.g., "100.5") */
34+
formatted: string;
35+
/** Token decimals */
36+
decimals: number;
37+
/** Token symbol (if available) */
38+
symbol?: string;
39+
/** User's wallet address */
40+
userAddress: Address;
41+
}
42+
43+
/**
44+
* Gets the token balance for the connected wallet
45+
* @param walletClient The viem wallet client with a connected account
46+
* @param publicClient The viem public client for reading contract state
47+
* @param tokenAddress The ERC20 token contract address
48+
* @returns Promise<TokenBalance> The token balance information
49+
* @throws Error if no account is connected to the wallet client
50+
*
51+
* @example
52+
* ```typescript
53+
* const { walletClient, publicClient } = await createViemClientsFromWindow();
54+
* const balance = await getTokenBalance(walletClient, publicClient, "0x...");
55+
* console.log(`Balance: ${balance.formatted} ${balance.symbol}`);
56+
* ```
57+
*/
58+
export async function getTokenBalance(
59+
walletClient: WalletClient,
60+
publicClient: PublicClient,
61+
tokenAddress: Address
62+
): Promise<TokenBalance> {
63+
if (!walletClient.account) {
64+
throw new Error("No account connected to wallet client");
65+
}
66+
67+
const userAddress = walletClient.account.address;
68+
69+
// Read balance, decimals, and symbol in parallel
70+
const [balance, decimals, symbol] = await Promise.all([
71+
publicClient.readContract({
72+
address: tokenAddress,
73+
abi: erc20Abi,
74+
functionName: "balanceOf",
75+
args: [userAddress]
76+
}),
77+
publicClient.readContract({
78+
address: tokenAddress,
79+
abi: erc20Abi,
80+
functionName: "decimals"
81+
}),
82+
publicClient
83+
.readContract({
84+
address: tokenAddress,
85+
abi: erc20Abi,
86+
functionName: "symbol"
87+
})
88+
.catch(() => undefined) // Symbol is optional, some tokens don't have it
89+
]);
90+
91+
return {
92+
raw: balance,
93+
formatted: formatUnits(balance, decimals),
94+
decimals,
95+
symbol,
96+
userAddress
97+
};
98+
}

packages/rln/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export {
2424
membershipUpgradeableAbi
2525
} from "./contract/wagmi/generated.js";
2626

27+
// Export token utilities
28+
export { getTokenBalance, erc20Abi } from "./contract/token.js";
29+
export type { TokenBalance } from "./contract/token.js";
30+
2731
export type {
2832
DecryptedCredentials,
2933
EncryptedCredentials,

0 commit comments

Comments
 (0)