Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const sig = Protocols.aaveV3.eventTopics.Borrow;

| Protocol | Contracts | Chains |
|---|---|---|
| **AAVE V3** | Pool, Oracle, WETH Gateway + Pool methods/events ABI + topics | Mainnet, Sepolia, Base, Base Sepolia |
| **AAVE V3** | Pool, Oracle, WETH Gateway + Pool methods/events ABI + topics | Mainnet, Sepolia, Base, Base Sepolia, **BNB** (no WETH Gateway on BNB) |
| **Aerodrome** | Router | Base |
| **Chainlink** | ETH/USD + BTC/USD feeds + AggregatorV3 ABI | Mainnet, Sepolia |
| **Chainlink** | ETH/USD + BTC/USD + BNB/USD feeds + AggregatorV3 ABI | Mainnet, Sepolia, **BNB** (BNB/USD is BNB-only) |
| **Compound V3** | USDC Comet market | Mainnet, Base |
| **Ethena** | USDe, sUSDe vault + custom (cooldown-aware) ABI | Mainnet |
| **Frax Ether** | frxETH, sfrxETH vault + standard ERC-4626 ABI | Mainnet |
Expand All @@ -34,9 +34,9 @@ const sig = Protocols.aaveV3.eventTopics.Borrow;
| **Rocket Pool** | rETH + L1 burn/rate/value ABI | Mainnet, Base (bridged) |
| **Sky (sDAI)** | sDAI vault + standard ERC-4626 ABI | Mainnet |
| **Spark** | SparkLend Pool (AAVE V3 fork — reuse AAVE Pool ABI) | Mainnet |
| **Superfluid** | CFAv1Forwarder + setFlowrate/createFlow ABI | Mainnet, Base |
| **Uniswap V3** | SwapRouter02, QuoterV2, Permit2, Factory, NFT Position Manager, Universal Router + ABIs | Mainnet, Sepolia, Base, Base Sepolia |
| **Wrapped Ether** | WETH per chain + WETH9 ABI | Mainnet, Sepolia, Base, Base Sepolia |
| **Superfluid** | CFAv1Forwarder + setFlowrate/createFlow ABI | Mainnet, Base, **BNB** |
| **Uniswap V3** | SwapRouter02, QuoterV2, Permit2, Factory, NFT Position Manager, Universal Router + ABIs | Mainnet, Sepolia, Base, Base Sepolia, **BNB** |
| **Wrapped Ether** | Canonical wrapper of native gas + WETH9 ABI (WBNB on BNB) | Mainnet, Sepolia, Base, Base Sepolia, **BNB** |
| **ERC-20** | Standard `approve` ABI fragment | n/a |

Shared ABIs (consumed by multiple protocol modules):
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@avaprotocol/protocols",
"version": "0.1.0",
"version": "0.2.0",
"description": "Multi-chain catalog of DeFi protocol contract addresses, ABI fragments, and event topic hashes — covers AAVE V3, Uniswap V3, Chainlink, Lido, Morpho, and more.",
"keywords": [
"defi",
Expand Down
1 change: 1 addition & 0 deletions src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const Chains = Object.freeze({
Holesky: 17_000 as const,
BaseMainnet: 8453 as const,
BaseSepolia: 84_532 as const,
BnbMainnet: 56 as const,
});

export type ChainId = (typeof Chains)[keyof typeof Chains] | number;
6 changes: 6 additions & 0 deletions src/protocols/aave-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const pool: AddressByChain = {
[Chains.Sepolia]: "0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951",
[Chains.BaseMainnet]: "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
[Chains.BaseSepolia]: "0x8bAB6d1b75f19e9eD9fCe8b9BD338844fF79aE27",
[Chains.BnbMainnet]: "0x6807dc923806fE8Fd134338EABCA509979a7e0cB",
};

/**
Expand All @@ -36,12 +37,17 @@ const oracle: AddressByChain = {
[Chains.Sepolia]: "0x2da88497588bf89281816106C7259e31AF45a663",
[Chains.BaseMainnet]: "0x2Cc0Fc26eD4563A5ce5e8bdcfe1A2878676Ae156",
[Chains.BaseSepolia]: "0x943b0dE18d4abf4eF02A85912F8fc07684C141dF",
[Chains.BnbMainnet]: "0x39bc1bfDa2130d6Bb6DBEfd366939b4c7aa7C697",
};

/**
* WETH gateway — wraps native ETH supplies so Pool can hold WETH as
* the reserve. Templates that supply native ETH (rather than an
* already-wrapped token) go through here instead of Pool directly.
*
* Only present on chains whose native gas token is ETH. Absent on
* BNB Chain (native is BNB; the equivalent there would be a "WBNB
* gateway" but AAVE V3 doesn't deploy one).
*/
const wethGateway: AddressByChain = {
[Chains.EthereumMainnet]: "0xd01607c3C5eCABa394D8be377a08590149325722",
Expand Down
12 changes: 12 additions & 0 deletions src/protocols/chainlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,27 @@ import { type AddressByChain } from "./types";
const ethUsdFeed: AddressByChain = {
[Chains.EthereumMainnet]: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
[Chains.Sepolia]: "0x694AA1769357215DE4FAC081bf1f309aDC325306",
[Chains.BnbMainnet]: "0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e",
};

const btcUsdFeed: AddressByChain = {
[Chains.EthereumMainnet]: "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c",
[Chains.BnbMainnet]: "0x264990fbd0A4796A3E3d8E37C4d5F87a3aCa5Ebf",
};

/**
* BNB/USD feed — only meaningful on BNB Chain (where BNB is the
* native gas token). The catalog ships this so templates on BNB can
* read native-asset prices the same way ETH-based chains read ETH/USD.
*/
const bnbUsdFeed: AddressByChain = {
[Chains.BnbMainnet]: "0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE",
};

export const chainlink = Object.freeze({
ethUsdFeed,
btcUsdFeed,
bnbUsdFeed,
/** Shared AggregatorV3 ABI — works for any Chainlink feed. */
aggregatorV3Abi,
});
1 change: 1 addition & 0 deletions src/protocols/superfluid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type AbiFragment, type AddressByChain } from "./types";
const cfaForwarder: AddressByChain = {
[Chains.EthereumMainnet]: "0xcfA132E353cB4E398080B9700609bb008eceB125",
[Chains.BaseMainnet]: "0xcfA132E353cB4E398080B9700609bb008eceB125",
[Chains.BnbMainnet]: "0xcfA132E353cB4E398080B9700609bb008eceB125",
};

/** CFAv1Forwarder minimal write surface — `setFlowrate` + `createFlow`. */
Expand Down
24 changes: 20 additions & 4 deletions src/protocols/uniswap-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const swapRouter02: AddressByChain = {
[Chains.Sepolia]: "0x3bFA4769FB09eefC5a80d6E87c3B9C650f7Ae48E",
[Chains.BaseMainnet]: "0x2626664c2603336E57B271c5C0b26F421741e481",
[Chains.BaseSepolia]: "0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4",
[Chains.BnbMainnet]: "0xB971eF87ede563556b2ED4b1C0b0019111Dd85d2",
};

/** QuoterV2 — off-chain quote helper for swap previews. */
Expand All @@ -29,6 +30,7 @@ const quoterV2: AddressByChain = {
[Chains.Sepolia]: "0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3",
[Chains.BaseMainnet]: "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a",
[Chains.BaseSepolia]: "0xC5290058841028F1614F3A6F0F5816cAd0df5E27",
[Chains.BnbMainnet]: "0x78D78E420Da98ad378D7799bE8f4AF69033EB077",
};

/**
Expand All @@ -40,6 +42,7 @@ const permit2: AddressByChain = {
[Chains.Sepolia]: "0x000000000022d473030F116dDEE9F6B43aC78BA3",
[Chains.BaseMainnet]: "0x000000000022d473030F116dDEE9F6B43aC78BA3",
[Chains.BaseSepolia]: "0x000000000022d473030F116dDEE9F6B43aC78BA3",
[Chains.BnbMainnet]: "0x000000000022d473030F116dDEE9F6B43aC78BA3",
};

/** Uniswap V3 Factory — derives the deterministic pool address per token-pair+fee. */
Expand All @@ -48,6 +51,7 @@ const factory: AddressByChain = {
[Chains.Sepolia]: "0x0227628f3F023bb0B980b67D528571c95c6DaC1c",
[Chains.BaseMainnet]: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD",
[Chains.BaseSepolia]: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
[Chains.BnbMainnet]: "0xdB1d10011AD0Ff90774D0C6Bb92e5C5c8b4461F7",
};

/** NonfungiblePositionManager — LP NFT mint/burn/collect. */
Expand All @@ -56,6 +60,7 @@ const nonfungiblePositionManager: AddressByChain = {
[Chains.Sepolia]: "0x1238536071E1c677A632429e3655c799b22cDA52",
[Chains.BaseMainnet]: "0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1",
[Chains.BaseSepolia]: "0x27F971cb582BF9E50F397e4d29a5C7A34f11faA2",
[Chains.BnbMainnet]: "0x7b8A01B39D58278b5DE7e48c8449c9f4F5170613",
};

/** UniversalRouter — Uniswap's multi-step routing entrypoint (Permit2-aware). */
Expand All @@ -64,6 +69,7 @@ const universalRouter: AddressByChain = {
[Chains.Sepolia]: "0x3A9D48AB9751398BbFa63ad67599Bb04e4BdF98b",
[Chains.BaseMainnet]: "0x6ff5693b99212da76ad316178a184ab56d299b43",
[Chains.BaseSepolia]: "0x492e6456d9528771018deb9e87ef7750ef184104",
[Chains.BnbMainnet]: "0x4Dae2f939ACf50408e13d58534Ff8c2776d45265",
};

/**
Expand Down Expand Up @@ -175,23 +181,33 @@ const factoryAbi: readonly AbiFragment[] = Object.freeze([
]);

/**
* Reference token addresses on the testnet markets these templates
* routinely target. Mainnet tokens vary per template — pass them
* inline rather than relying on a global registry the catalog
* doesn't yet maintain.
* Reference token addresses on the markets these templates routinely
* target. Mainnet tokens vary per template — pass them inline rather
* than relying on a global registry the catalog doesn't yet maintain.
*
* On chains whose native gas token is ETH, the `WETH` entry is the
* canonical WETH9-style contract. On BNB Chain, the native is BNB
* not ETH, so `WETH[BnbMainnet]` is **WBNB** (the canonical wrapper
* of the native token) — that's what Uniswap V3 pools on BNB actually
* quote against. Templates that specifically need the bridged-from-
* Ethereum WETH on BNB (`0x2170Ed0880ac9A755fd29B2688956BD959F933F8`)
* should pass it inline; the catalog leans on the "wrapper of native"
* semantic for consistency with `Protocols.wrapped.weth`.
*/
const tokens = Object.freeze({
WETH: {
[Chains.Sepolia]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
[Chains.EthereumMainnet]: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
[Chains.BaseMainnet]: "0x4200000000000000000000000000000000000006",
[Chains.BaseSepolia]: "0x4200000000000000000000000000000000000006",
[Chains.BnbMainnet]: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
} satisfies AddressByChain,
USDC: {
[Chains.Sepolia]: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
[Chains.EthereumMainnet]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
[Chains.BaseMainnet]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
[Chains.BaseSepolia]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
[Chains.BnbMainnet]: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
} satisfies AddressByChain,
});

Expand Down
19 changes: 15 additions & 4 deletions src/protocols/wrapped.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
// Wrapped Ether (WETH) — the canonical native-ETH wrapping contract.
// Mainnet WETH is a one-off; Base (and Base Sepolia) use the OP-stack
// predeploy at 0x4200…0006. Sepolia is the Uniswap-deployed test WETH
// used by AAVE Sepolia and Uniswap Sepolia.
// Wrapped native — the canonical wrapper of the chain's native gas
// token. On chains whose native is ETH (Mainnet, Sepolia, Base, Base
// Sepolia) this is the WETH9 (or OP-stack predeploy at 0x4200…0006)
// contract. On chains with a different native gas token, the field
// maps to the equivalent canonical wrapper — e.g. **WBNB** on BNB
// Chain. The field name stays `weth` so chain-agnostic consumers can
// write `Protocols.wrapped.weth[chainId]` without branching by chain.
//
// The bridged-from-Ethereum WETH on BNB
// (0x2170Ed0880ac9A755fd29B2688956BD959F933F8) is a different
// semantic — Binance-Peg ETH ERC-20, not the wrapped native — and is
// intentionally NOT mapped here. Templates that need it pass the
// address inline.

import { Chains } from "../chains";
import { type AbiFragment, type AddressByChain } from "./types";
Expand All @@ -11,6 +20,8 @@ const weth: AddressByChain = {
[Chains.Sepolia]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
[Chains.BaseMainnet]: "0x4200000000000000000000000000000000000006",
[Chains.BaseSepolia]: "0x4200000000000000000000000000000000000006",
// BNB Chain's native is BNB, not ETH — this entry is WBNB.
[Chains.BnbMainnet]: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
};

/**
Expand Down
16 changes: 12 additions & 4 deletions tests/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ describe("Event topic shape", () => {
});

describe("AAVE V3 catalog", () => {
it("has Pool addresses on mainnet, sepolia, base, base-sepolia", () => {
it("has Pool addresses on every covered chain", () => {
expect(Protocols.aaveV3.pool[Chains.EthereumMainnet]).toMatch(ADDRESS_RE);
expect(Protocols.aaveV3.pool[Chains.Sepolia]).toMatch(ADDRESS_RE);
expect(Protocols.aaveV3.pool[Chains.BaseMainnet]).toMatch(ADDRESS_RE);
expect(Protocols.aaveV3.pool[Chains.BaseSepolia]).toMatch(ADDRESS_RE);
expect(Protocols.aaveV3.pool[Chains.BnbMainnet]).toMatch(ADDRESS_RE);
});

it("ships the Pool method ABI with getUserAccountData + supply", () => {
Expand All @@ -117,6 +118,7 @@ describe("Uniswap V3 catalog", () => {
expect(Protocols.uniswapV3.swapRouter02[Chains.Sepolia]).toMatch(ADDRESS_RE);
expect(Protocols.uniswapV3.swapRouter02[Chains.BaseMainnet]).toMatch(ADDRESS_RE);
expect(Protocols.uniswapV3.swapRouter02[Chains.BaseSepolia]).toMatch(ADDRESS_RE);
expect(Protocols.uniswapV3.swapRouter02[Chains.BnbMainnet]).toMatch(ADDRESS_RE);
});

it("ships exactInputSingle in the SwapRouter02 ABI", () => {
Expand All @@ -132,6 +134,7 @@ describe("Uniswap V3 catalog", () => {
Chains.Sepolia,
Chains.BaseMainnet,
Chains.BaseSepolia,
Chains.BnbMainnet,
]) {
expect(Protocols.uniswapV3.permit2[chainId]?.toLowerCase()).toBe(expected.toLowerCase());
}
Expand Down Expand Up @@ -165,12 +168,17 @@ describe("Shared ABIs", () => {
});

describe("Chain coverage", () => {
it("AAVE V3 covers the same chain set on Pool/Oracle/WETH Gateway", () => {
it("AAVE V3 Pool + Oracle cover the same chains; WETH Gateway is a subset", () => {
const poolChains = Object.keys(Protocols.aaveV3.pool).sort();
const oracleChains = Object.keys(Protocols.aaveV3.oracle).sort();
const gatewayChains = Object.keys(Protocols.aaveV3.wethGateway).sort();
expect(oracleChains).toEqual(poolChains);
expect(gatewayChains).toEqual(poolChains);
// WETH Gateway is only deployed on chains whose native gas token
// is ETH. Chains without it (e.g. BNB Chain) still have Pool +
// Oracle. So the invariant is "gateway ⊆ pool", not equality.
const gatewayChains = Object.keys(Protocols.aaveV3.wethGateway);
for (const cid of gatewayChains) {
expect(poolChains).toContain(cid);
}
});

it("Uniswap V3 covers the same chain set across its contracts", () => {
Expand Down
Loading