| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- /**
- * Chain helpers for mavi — balance, send, optional test fund.
- * RPC comes from the network switcher (local private or public anvil).
- */
- import {
- createPublicClient,
- createWalletClient,
- formatEther,
- http,
- parseEther,
- type Hex,
- } from "viem";
- import { privateKeyToAccount } from "viem/accounts";
- import { getActiveNetwork, type MaviNetwork } from "./network";
- /** Anvil account #0 — local dev only, never production */
- const ANVIL_0_KEY =
- "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as Hex;
- function chainFromNetwork(net: MaviNetwork) {
- return {
- id: net.chainId,
- name: net.label,
- nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
- rpcUrls: {
- default: { http: [net.rpcUrl] },
- },
- } as const;
- }
- function publicClient(net = getActiveNetwork()) {
- return createPublicClient({
- chain: chainFromNetwork(net),
- transport: http(net.rpcUrl),
- });
- }
- export type ChainBalance = {
- ok: boolean;
- eth: string;
- wei: string;
- chainId?: number;
- networkId?: string;
- networkLabel?: string;
- error?: string;
- };
- export async function fetchEthBalance(
- address: `0x${string}`,
- ): Promise<ChainBalance> {
- const net = getActiveNetwork();
- try {
- const client = publicClient(net);
- const [wei, chainId] = await Promise.all([
- client.getBalance({ address }),
- client.getChainId(),
- ]);
- return {
- ok: true,
- eth: formatEther(wei),
- wei: wei.toString(),
- chainId,
- networkId: net.id,
- networkLabel: net.shortLabel,
- };
- } catch (e) {
- return {
- ok: false,
- eth: "0",
- wei: "0",
- networkId: net.id,
- networkLabel: net.shortLabel,
- error:
- e instanceof Error
- ? e.message
- : `Chain unreachable (${net.shortLabel}). Start: bun run chains:up`,
- };
- }
- }
- export async function sendEth(opts: {
- privateKey: Hex;
- to: `0x${string}`;
- amountEth: string;
- }): Promise<{ hash: `0x${string}`; networkId: string }> {
- const net = getActiveNetwork();
- const chain = chainFromNetwork(net);
- const account = privateKeyToAccount(opts.privateKey);
- const client = createWalletClient({
- account,
- chain,
- transport: http(net.rpcUrl),
- });
- const publicC = publicClient(net);
- const hash = await client.sendTransaction({
- account,
- to: opts.to,
- value: parseEther(opts.amountEth),
- chain,
- });
- await publicC.waitForTransactionReceipt({ hash, timeout: 20_000 });
- return { hash, networkId: net.id };
- }
- /**
- * Dev-only: send test ETH from anvil account #0 to a mavi address.
- */
- export async function fundFromAnvil(
- to: `0x${string}`,
- amountEth = "1",
- ): Promise<{ hash: `0x${string}`; networkId: string }> {
- const net = getActiveNetwork();
- if (!net.canFund) {
- throw new Error("Test fund is only available on local anvil networks.");
- }
- return sendEth({
- privateKey: ANVIL_0_KEY,
- to,
- amountEth,
- });
- }
- export function isValidEthAddress(value: string): value is `0x${string}` {
- return /^0x[a-fA-F0-9]{40}$/.test(value.trim());
- }
|