From f93c9c4a86d850fd65de0c9861159ac5fd8e2c55 Mon Sep 17 00:00:00 2001
From: Fielding Johnston
Date: Sat, 25 Apr 2026 14:31:33 -0500
Subject: [PATCH 1/2] feat(chains): add Ethereum, Arbitrum, Optimism, Polygon,
Avalanche, BNB
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Expands the registry from Base + Base Sepolia to all 7 target EVM
mainnet chains plus Base Sepolia. Sablier Lockup v2.0 addresses
sourced directly from sablier-labs/sdk's deployment broadcasts;
verified against the two known-good values (Base mainnet + Base
Sepolia) which match exactly. Native USDC addresses sourced from
Circle's official contract addresses page.
Sharp edges captured:
1. BNB Chain "USDC" is Binance-Peg, NOT Circle-native. The token at
0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d is custodied by
Binance and uses 18 decimals (every other chain's USDC is 6).
The chain entry carries a usdcNote field disclosing this; /create
and /vaults render the note when chainId === 56 so users see the
distinction before they lock. Test enforces that BNB stays at 18
decimals and every other chain stays at 6.
2. Treasury is the same EOA on every mainnet chain. EVM addresses
derive from public keys, so the same private key gives the same
address everywhere. The Base treasury has an EIP-7702 delegation,
but that's per-chain and doesn't change token-receipt behavior.
Test asserts treasury parity across mainnet chains.
3. Sablier streamStartBlock is set to each chain's exact v2.0
deployment block (pulled from Sablier's deployment broadcasts).
Tightest safe value for the on-chain getLogs fallback when the
indexer is unavailable.
wagmi.ts auto-picks up the new entries via the chainIdToWagmiChain
map — no further config change needed. Wallet picker now shows all
7 mainnet chains; testnet deployment still shows only Base Sepolia.
Refs: RG-644a80 (data wiring portion; smoke testing remains)
Co-Authored-By: Claude Opus 4.7 (1M context)
---
packages/app/src/app/create/page.tsx | 6 ++
packages/app/src/app/vaults/page.tsx | 5 ++
packages/app/src/config/chains.test.ts | 35 +++++++-
packages/app/src/config/chains.ts | 108 ++++++++++++++++++++++++-
packages/app/src/config/wagmi.ts | 6 ++
5 files changed, 156 insertions(+), 4 deletions(-)
diff --git a/packages/app/src/app/create/page.tsx b/packages/app/src/app/create/page.tsx
index 3561ba1..c260099 100644
--- a/packages/app/src/app/create/page.tsx
+++ b/packages/app/src/app/create/page.tsx
@@ -122,6 +122,7 @@ function CreateLockInner() {
usdcDecimals,
treasury,
explorerUrl,
+ usdcNote,
} = useMemo(
() =>
getChainConfig(
@@ -708,6 +709,11 @@ function CreateLockInner() {
{formatUnits(totalAmount, usdcDecimals)} USDC (incl. {brokerFeePct} fee).
)}
+ {usdcNote && (
+
+ {usdcNote}
+
+ )}
{IS_TESTNET && isConnected && (
diff --git a/packages/app/src/app/vaults/page.tsx b/packages/app/src/app/vaults/page.tsx
index 33ba86f..3e9b776 100644
--- a/packages/app/src/app/vaults/page.tsx
+++ b/packages/app/src/app/vaults/page.tsx
@@ -709,6 +709,11 @@ function VaultDashboard() {
return (
+ {chainConfig.usdcNote && (
+
+ {chainConfig.usdcNote}
+
+ )}
{totals && (
diff --git a/packages/app/src/config/chains.test.ts b/packages/app/src/config/chains.test.ts
index 23563ae..414ddd3 100644
--- a/packages/app/src/config/chains.test.ts
+++ b/packages/app/src/config/chains.test.ts
@@ -25,7 +25,7 @@ describe("chain registry", () => {
});
it("throws on unsupported chainId", () => {
- expect(() => getChainConfig(1)).toThrow(/Unsupported chainId: 1/);
+ expect(() => getChainConfig(999_999)).toThrow(/Unsupported chainId: 999999/);
});
it("DEFAULT_CHAIN_ID resolves to a registered chain", () => {
@@ -55,7 +55,8 @@ describe("chain registry", () => {
it("isSupportedChain recognizes registered chains", () => {
expect(isSupportedChain(8453)).toBe(true);
expect(isSupportedChain(84532)).toBe(true);
- expect(isSupportedChain(1)).toBe(false);
+ expect(isSupportedChain(1)).toBe(true);
+ expect(isSupportedChain(999_999)).toBe(false);
expect(isSupportedChain(undefined)).toBe(false);
});
@@ -71,4 +72,34 @@ describe("chain registry", () => {
expect(isSupportedDeploymentChain(999_999, true)).toBe(false);
expect(isSupportedDeploymentChain(undefined, false)).toBe(false);
});
+
+ it("BNB Chain uses 18-decimal Binance-Peg USDC", () => {
+ const bsc = getChainConfig(56);
+ expect(bsc.usdcDecimals, "BNB Chain USDC is 18 decimals, not 6").toBe(18);
+ expect(bsc.usdc).toBe("0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d");
+ expect(bsc.usdcNote, "BNB Chain entry must disclose Binance-Peg variant").toMatch(
+ /Binance-Peg/i
+ );
+ });
+
+ it("every other registered chain uses 6-decimal USDC", () => {
+ for (const chain of Object.values(CHAINS)) {
+ if (chain.chainId === 56) continue; // BNB is the documented exception
+ expect(chain.usdcDecimals, `${chain.name} expected 6 decimals`).toBe(6);
+ }
+ });
+
+ it("all mainnet chains share the same treasury EOA", () => {
+ const mainnetChains = Object.values(CHAINS).filter((c) => !c.isTestnet);
+ const treasuries = new Set(mainnetChains.map((c) => c.treasury.toLowerCase()));
+ expect(treasuries.size, "treasury must be the same address on every mainnet chain").toBe(1);
+ });
+
+ it("all 7 target EVM mainnet chains are present", () => {
+ // Base, Ethereum, Arbitrum, Optimism, Polygon, Avalanche, BNB Chain
+ const expected = [1, 10, 56, 137, 8453, 42161, 43114];
+ for (const id of expected) {
+ expect(isSupportedChain(id), `chainId ${id} must be in the registry`).toBe(true);
+ }
+ });
});
diff --git a/packages/app/src/config/chains.ts b/packages/app/src/config/chains.ts
index 1753947..c1ecf62 100644
--- a/packages/app/src/config/chains.ts
+++ b/packages/app/src/config/chains.ts
@@ -12,10 +12,33 @@ export type ChainConfig = {
streamStartBlock: bigint;
logChunkSize: bigint;
isTestnet: boolean;
+ // Optional disclosure shown in /create and /vaults when this chain is
+ // active. Used today to flag that BNB Chain "USDC" is Binance-Peg, not
+ // Circle-native — a real distinction users should know about.
+ usdcNote?: string;
};
export const ZERO_ADDRESS: Address = "0x0000000000000000000000000000000000000000";
+// Treasury — same EOA on every EVM chain (EVM addresses derive from public
+// keys, so a single private key gives the same address everywhere). EIP-7702
+// delegations are per-chain and don't change the underlying account.
+const TREASURY_EOA: Address = "0x847F640bE052b0700C31F72Dce622F4C6286934E";
+
+const ETHEREUM_DEFAULT: ChainConfig = {
+ chainId: 1,
+ name: "Ethereum",
+ shortName: "Ethereum",
+ sablierLockup: "0x7C01AA3783577E15fD7e272443D44B92d5b21056",
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ usdcDecimals: 6,
+ treasury: TREASURY_EOA,
+ explorerUrl: "https://etherscan.io",
+ streamStartBlock: BigInt(21_717_452), // Sablier Lockup v2.0 deployment block
+ logChunkSize: BigInt(10_000),
+ isTestnet: false,
+};
+
const BASE_DEFAULT: ChainConfig = {
chainId: 8453,
name: "Base",
@@ -23,18 +46,93 @@ const BASE_DEFAULT: ChainConfig = {
sablierLockup: "0xb5D78DD3276325f5FAF3106Cc4Acc56E28e0Fe3B",
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
usdcDecimals: 6,
- treasury: "0x847F640bE052b0700C31F72Dce622F4C6286934E",
+ treasury: TREASURY_EOA,
explorerUrl: "https://basescan.org",
streamStartBlock: BigInt(22_000_000),
logChunkSize: BigInt(50_000),
isTestnet: false,
};
+const ARBITRUM_DEFAULT: ChainConfig = {
+ chainId: 42161,
+ name: "Arbitrum One",
+ shortName: "Arbitrum",
+ sablierLockup: "0x467D5Bf8Cfa1a5f99328fBdCb9C751c78934b725",
+ usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // native USDC, not USDC.e
+ usdcDecimals: 6,
+ treasury: TREASURY_EOA,
+ explorerUrl: "https://arbiscan.io",
+ streamStartBlock: BigInt(299_856_278),
+ logChunkSize: BigInt(10_000),
+ isTestnet: false,
+};
+
+const OPTIMISM_DEFAULT: ChainConfig = {
+ chainId: 10,
+ name: "Optimism",
+ shortName: "Optimism",
+ sablierLockup: "0x822e9c4852E978104d82F0f785bFA663c2b700c1",
+ usdc: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // native USDC
+ usdcDecimals: 6,
+ treasury: TREASURY_EOA,
+ explorerUrl: "https://optimistic.etherscan.io",
+ streamStartBlock: BigInt(131_196_856),
+ logChunkSize: BigInt(10_000),
+ isTestnet: false,
+};
+
+const POLYGON_DEFAULT: ChainConfig = {
+ chainId: 137,
+ name: "Polygon",
+ shortName: "Polygon",
+ sablierLockup: "0xE0BFe071Da104e571298f8b6e0fcE44C512C1Ff4",
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // native USDC, not USDC.e
+ usdcDecimals: 6,
+ treasury: TREASURY_EOA,
+ explorerUrl: "https://polygonscan.com",
+ streamStartBlock: BigInt(67_212_728),
+ logChunkSize: BigInt(10_000),
+ isTestnet: false,
+};
+
+const AVALANCHE_DEFAULT: ChainConfig = {
+ chainId: 43114,
+ name: "Avalanche",
+ shortName: "Avalanche",
+ sablierLockup: "0x3C81BBBe72EF8eF3fb1D19B0bd6310Ad0dd27E82",
+ usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
+ usdcDecimals: 6,
+ treasury: TREASURY_EOA,
+ explorerUrl: "https://snowtrace.io",
+ streamStartBlock: BigInt(56_433_739),
+ logChunkSize: BigInt(10_000),
+ isTestnet: false,
+};
+
+// BNB Smart Chain — note that "USDC" here is Binance-Peg (18 decimals, not 6).
+// Circle does not issue native USDC on BNB Chain. The token is custodied by
+// Binance and bridged. We support it for utility (casino degens cash out here)
+// but disclose the distinction in the UI via usdcNote.
+const BSC_DEFAULT: ChainConfig = {
+ chainId: 56,
+ name: "BNB Chain",
+ shortName: "BNB",
+ sablierLockup: "0x6E0baD2c077d699841F1929b45bfb93FAfBEd395",
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", // Binance-Peg USDC
+ usdcDecimals: 18, // not 6 — sharp edge
+ treasury: TREASURY_EOA,
+ explorerUrl: "https://bscscan.com",
+ streamStartBlock: BigInt(46_137_048),
+ logChunkSize: BigInt(10_000),
+ isTestnet: false,
+ usdcNote: "On BNB Chain, USDC is Binance-Peg (custodied by Binance, 18 decimals).",
+};
+
const BASE_SEPOLIA_DEFAULT: ChainConfig = {
chainId: 84532,
name: "Base Sepolia",
shortName: "Base Sepolia",
- sablierLockup: "0xa4777ca525d43a7af55d45b11b430606d7416f8d",
+ sablierLockup: "0xa4777CA525d43a7aF55D45b11b430606d7416f8d",
usdc: "0x54C0f145D70ca4792e695697B6498552F1EC0009",
usdcDecimals: 6,
treasury: ZERO_ADDRESS,
@@ -62,7 +160,13 @@ function withEnvOverrides(cfg: ChainConfig): ChainConfig {
}
export const CHAINS: Record = {
+ [ETHEREUM_DEFAULT.chainId]: withEnvOverrides(ETHEREUM_DEFAULT),
[BASE_DEFAULT.chainId]: withEnvOverrides(BASE_DEFAULT),
+ [ARBITRUM_DEFAULT.chainId]: withEnvOverrides(ARBITRUM_DEFAULT),
+ [OPTIMISM_DEFAULT.chainId]: withEnvOverrides(OPTIMISM_DEFAULT),
+ [POLYGON_DEFAULT.chainId]: withEnvOverrides(POLYGON_DEFAULT),
+ [AVALANCHE_DEFAULT.chainId]: withEnvOverrides(AVALANCHE_DEFAULT),
+ [BSC_DEFAULT.chainId]: withEnvOverrides(BSC_DEFAULT),
[BASE_SEPOLIA_DEFAULT.chainId]: withEnvOverrides(BASE_SEPOLIA_DEFAULT),
};
diff --git a/packages/app/src/config/wagmi.ts b/packages/app/src/config/wagmi.ts
index f5297e7..4292796 100644
--- a/packages/app/src/config/wagmi.ts
+++ b/packages/app/src/config/wagmi.ts
@@ -6,7 +6,13 @@ import { CHAINS } from "./chains";
// Map a registry chainId to its wagmi chain definition. Add an entry here
// when adding a chain to the registry so wagmi can pick it up.
const chainIdToWagmiChain: Record = {
+ [wagmiChains.mainnet.id]: wagmiChains.mainnet,
[wagmiChains.base.id]: wagmiChains.base,
+ [wagmiChains.arbitrum.id]: wagmiChains.arbitrum,
+ [wagmiChains.optimism.id]: wagmiChains.optimism,
+ [wagmiChains.polygon.id]: wagmiChains.polygon,
+ [wagmiChains.avalanche.id]: wagmiChains.avalanche,
+ [wagmiChains.bsc.id]: wagmiChains.bsc,
[wagmiChains.baseSepolia.id]: wagmiChains.baseSepolia,
};
From d98faf936dd0716383b0cd66e57750e3feecd8e3 Mon Sep 17 00:00:00 2001
From: Fielding Johnston
Date: Sat, 25 Apr 2026 16:57:28 -0500
Subject: [PATCH 2/2] fix(copy): chain-aware explorer name everywhere; drop
hardcoded BaseScan
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Addresses review feedback on #13: enabling non-Base chains made every
hardcoded "BaseScan" reference wrong. The URLs were already chain-
derived, but the labels weren't, so a user on Arbitrum saw "View on
BaseScan" linking to Arbiscan.
Adds explorerName to ChainConfig (Etherscan, BaseScan, Arbiscan,
Optimistic Etherscan, PolygonScan, Snowtrace, BscScan, Base Sepolia
BaseScan) and threads it through:
- /create toast: "Lock created!" → "View on {explorerName}"
- /create approve confirming link: "View on {explorerName}"
- /create lock confirming link: "View on {explorerName}"
- /create SuccessView: "View transaction on {explorerName}"
(now takes explorerName as a prop alongside explorerUrl)
- /vaults claim toast: "View on {explorerName}"
- landing footer link: "{explorerName}" instead of literal "BaseScan"
Static landing copy that doesn't sit next to a connected chain
(TRUST_POINTS, FAQ, footer disclaimer) is generalized to "the chain's
block explorer" since it's marketing copy, not a per-chain label.
The hardcoded "on Base" in the footer disclaimer also goes away —
RipGuard now runs on 7 chains.
Lock success effect deps now include explorerName so the toast picks
up the active chain's value when the user changes networks.
Refs: RG-644a80 (review feedback)
Co-Authored-By: Claude Opus 4.7 (1M context)
---
packages/app/src/app/create/page.tsx | 13 +++++++++----
packages/app/src/app/page.tsx | 14 +++++++-------
packages/app/src/app/vaults/page.tsx | 6 +++---
packages/app/src/config/chains.ts | 12 ++++++++++++
4 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/packages/app/src/app/create/page.tsx b/packages/app/src/app/create/page.tsx
index c260099..70b0f7f 100644
--- a/packages/app/src/app/create/page.tsx
+++ b/packages/app/src/app/create/page.tsx
@@ -122,6 +122,7 @@ function CreateLockInner() {
usdcDecimals,
treasury,
explorerUrl,
+ explorerName,
usdcNote,
} = useMemo(
() =>
@@ -475,7 +476,7 @@ function CreateLockInner() {
lockInFlightRef.current = false;
setStep("success");
toast("Lock created!", "success", {
- label: "View on BaseScan",
+ label: `View on ${explorerName}`,
href: `${explorerUrl}/tx/${lockTxHash}`,
});
trackLockCreated({
@@ -512,6 +513,7 @@ function CreateLockInner() {
chainId,
sablierLockup,
explorerUrl,
+ explorerName,
usdcDecimals,
]);
@@ -617,6 +619,7 @@ function CreateLockInner() {
usdcDecimals={usdcDecimals}
sablierAddress={sablierLockup}
explorerUrl={explorerUrl}
+ explorerName={explorerName}
onCreateAnother={resetForm}
/>
) : (
@@ -984,7 +987,7 @@ function CreateLockInner() {
rel="noopener noreferrer"
className="inline-flex items-center min-h-[2.75rem] px-2 text-sm text-faint hover:text-cyan underline transition-colors"
>
- View on BaseScan
+ View on {explorerName}
)}
@@ -1029,7 +1032,7 @@ function CreateLockInner() {
rel="noopener noreferrer"
className="inline-flex items-center min-h-[2.75rem] px-2 text-sm text-faint hover:text-cyan underline transition-colors"
>
- View on BaseScan
+ View on {explorerName}
)}
@@ -1395,6 +1398,7 @@ function SuccessView({
usdcDecimals,
sablierAddress,
explorerUrl,
+ explorerName,
onCreateAnother,
}: {
txHash: `0x${string}`;
@@ -1404,6 +1408,7 @@ function SuccessView({
usdcDecimals: number;
sablierAddress: Address;
explorerUrl: string;
+ explorerName: string;
onCreateAnother: () => void;
}) {
const now = Math.floor(Date.now() / 1000);
@@ -1498,7 +1503,7 @@ function SuccessView({
rel="noopener noreferrer"
className="block text-xs text-faint hover:text-cyan underline transition-colors text-center"
>
- View transaction on BaseScan
+ View transaction on {explorerName}
diff --git a/packages/app/src/app/page.tsx b/packages/app/src/app/page.tsx
index 066b9b5..3372749 100644
--- a/packages/app/src/app/page.tsx
+++ b/packages/app/src/app/page.tsx
@@ -78,7 +78,7 @@ const TRUST_POINTS = [
{
eyebrow: "04",
title: "Site goes down. You're fine.",
- body: "Your lock lives in Sablier, not on our servers. Interact directly via BaseScan anytime. RipGuard disappearing tomorrow doesn't touch your funds.",
+ body: "Your lock lives in Sablier, not on our servers. Interact directly via your chain's block explorer anytime. RipGuard disappearing tomorrow doesn't touch your funds.",
},
];
@@ -122,7 +122,7 @@ export default function Home() {
const chainId = useChainId();
// Unconnected users + users on unsupported chains see the default chain's
// contract link so the landing always has somewhere real to point at.
- const { sablierLockup, explorerUrl } = getChainConfig(
+ const { sablierLockup, explorerUrl, explorerName } = getChainConfig(
isSupportedDeploymentChain(chainId, IS_TESTNET) ? chainId : DEFAULT_CHAIN_ID,
);
const sablierExplorerUrl = `${explorerUrl}/address/${sablierLockup}`;
@@ -457,7 +457,7 @@ export default function Home() {
>
Sablier Lockup contract
{" "}
- on BaseScan. Verified, immutable, no proxy.
+ on the chain's block explorer. Verified, immutable, no proxy.
@@ -622,7 +622,7 @@ export default function Home() {
All smart contracts carry risk. Start with $5. Verify the
- Sablier contract yourself on BaseScan.
+ Sablier contract yourself on the chain's block explorer.
@@ -640,7 +640,7 @@ export default function Home() {
Your lock lives in Sablier, not on our servers. If this site
goes down tomorrow, your vault keeps counting down and you can
- claim directly through BaseScan or the Sablier app.
+ claim directly through the chain's block explorer or the Sablier app.
RipGuard is the UI. Sablier is the bank.
@@ -745,13 +745,13 @@ export default function Home() {
rel="noopener noreferrer"
className="inline-flex items-center min-h-[2.75rem] px-3 hover:text-cyan transition-colors"
>
- BaseScan
+ {explorerName}
RipGuard does not custody funds. All locks are created directly in
- Sablier's audited protocol on Base. Non-custodial. Immutable. Not
+ Sablier's audited protocol. Non-custodial. Immutable. Not
financial advice. DYOR. 0.5% broker fee collected via Sablier's
native mechanism.
diff --git a/packages/app/src/app/vaults/page.tsx b/packages/app/src/app/vaults/page.tsx
index 3e9b776..24bb133 100644
--- a/packages/app/src/app/vaults/page.tsx
+++ b/packages/app/src/app/vaults/page.tsx
@@ -428,7 +428,7 @@ function VaultDashboard() {
const chainConfig = getChainConfig(
isSupportedDeploymentChain(chainId, IS_TESTNET) ? chainId : DEFAULT_CHAIN_ID,
);
- const { sablierLockup, usdcDecimals, explorerUrl } = chainConfig;
+ const { sablierLockup, usdcDecimals, explorerUrl, explorerName } = chainConfig;
const publicClient = usePublicClient();
const { toast } = useToast();
@@ -589,11 +589,11 @@ function VaultDashboard() {
setClaimingId(null);
refetchStreams();
toast("Claim successful!", "success", {
- label: "View on BaseScan",
+ label: `View on ${explorerName}`,
href: `${explorerUrl}/tx/${withdrawTxHash}`,
});
}
- }, [isWithdrawConfirmed, withdrawTxHash, claimingId, refetchStreams, toast, explorerUrl]);
+ }, [isWithdrawConfirmed, withdrawTxHash, claimingId, refetchStreams, toast, explorerUrl, explorerName]);
// Toast + reset claiming state if tx rejected or failed
useEffect(() => {
diff --git a/packages/app/src/config/chains.ts b/packages/app/src/config/chains.ts
index c1ecf62..aff8833 100644
--- a/packages/app/src/config/chains.ts
+++ b/packages/app/src/config/chains.ts
@@ -9,6 +9,10 @@ export type ChainConfig = {
usdcDecimals: number;
treasury: Address;
explorerUrl: string;
+ // Brand name of the explorer at explorerUrl, used as link/button copy.
+ // "View on BaseScan" reads stronger than a generic "View on the explorer"
+ // and is correct per chain (Etherscan, Arbiscan, etc.).
+ explorerName: string;
streamStartBlock: bigint;
logChunkSize: bigint;
isTestnet: boolean;
@@ -34,6 +38,7 @@ const ETHEREUM_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: TREASURY_EOA,
explorerUrl: "https://etherscan.io",
+ explorerName: "Etherscan",
streamStartBlock: BigInt(21_717_452), // Sablier Lockup v2.0 deployment block
logChunkSize: BigInt(10_000),
isTestnet: false,
@@ -48,6 +53,7 @@ const BASE_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: TREASURY_EOA,
explorerUrl: "https://basescan.org",
+ explorerName: "BaseScan",
streamStartBlock: BigInt(22_000_000),
logChunkSize: BigInt(50_000),
isTestnet: false,
@@ -62,6 +68,7 @@ const ARBITRUM_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: TREASURY_EOA,
explorerUrl: "https://arbiscan.io",
+ explorerName: "Arbiscan",
streamStartBlock: BigInt(299_856_278),
logChunkSize: BigInt(10_000),
isTestnet: false,
@@ -76,6 +83,7 @@ const OPTIMISM_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: TREASURY_EOA,
explorerUrl: "https://optimistic.etherscan.io",
+ explorerName: "Optimistic Etherscan",
streamStartBlock: BigInt(131_196_856),
logChunkSize: BigInt(10_000),
isTestnet: false,
@@ -90,6 +98,7 @@ const POLYGON_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: TREASURY_EOA,
explorerUrl: "https://polygonscan.com",
+ explorerName: "PolygonScan",
streamStartBlock: BigInt(67_212_728),
logChunkSize: BigInt(10_000),
isTestnet: false,
@@ -104,6 +113,7 @@ const AVALANCHE_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: TREASURY_EOA,
explorerUrl: "https://snowtrace.io",
+ explorerName: "Snowtrace",
streamStartBlock: BigInt(56_433_739),
logChunkSize: BigInt(10_000),
isTestnet: false,
@@ -122,6 +132,7 @@ const BSC_DEFAULT: ChainConfig = {
usdcDecimals: 18, // not 6 — sharp edge
treasury: TREASURY_EOA,
explorerUrl: "https://bscscan.com",
+ explorerName: "BscScan",
streamStartBlock: BigInt(46_137_048),
logChunkSize: BigInt(10_000),
isTestnet: false,
@@ -137,6 +148,7 @@ const BASE_SEPOLIA_DEFAULT: ChainConfig = {
usdcDecimals: 6,
treasury: ZERO_ADDRESS,
explorerUrl: "https://sepolia.basescan.org",
+ explorerName: "Base Sepolia BaseScan",
streamStartBlock: BigInt(38_540_000),
logChunkSize: BigInt(10_000),
isTestnet: true,