From ba8af0600bcf6835e528d62bc74a7b0f596d9009 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Fri, 13 Dec 2024 18:02:32 +0700 Subject: [PATCH 01/13] bootstrap Base support (boilerplate) --- packages/app/.env.example | 2 + .../app/components/strategies/constants.tsx | 50 ++++++++++++++++++- .../app/components/token-picker/constants.ts | 6 +++ packages/app/constants/urls.ts | 1 + packages/app/contexts/TokenListContext.tsx | 6 +++ packages/app/models/cow-order/cow-order.ts | 2 + packages/app/models/token/base.ts | 19 +++++++ packages/app/models/token/index.ts | 1 + packages/app/providers/wagmi-config.ts | 5 +- .../assets/blockchains/base/tokenlist.json | 26 ++++++++++ packages/app/utils/constants.ts | 5 ++ packages/app/utils/transaction.ts | 1 + packages/sdk/src/constants.ts | 1 + packages/sdk/src/vaults/constants.ts | 10 ++++ packages/subgraph/bin/config.ts | 6 +++ 15 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 packages/app/models/token/base.ts create mode 100644 packages/app/public/assets/blockchains/base/tokenlist.json diff --git a/packages/app/.env.example b/packages/app/.env.example index b1a71694..d308d920 100644 --- a/packages/app/.env.example +++ b/packages/app/.env.example @@ -5,5 +5,7 @@ NEXT_PUBLIC_FATHOM_SITE_ID= RPC_GNOSIS= RPC_MAINNET= RPC_ARBITRUM= +RPC_BASE= + NEXT_STACKLY_SUBGRAPH_API_KEY= \ No newline at end of file diff --git a/packages/app/components/strategies/constants.tsx b/packages/app/components/strategies/constants.tsx index d60b1067..4029dde2 100644 --- a/packages/app/components/strategies/constants.tsx +++ b/packages/app/components/strategies/constants.tsx @@ -1,5 +1,10 @@ import { FREQUENCY_OPTIONS } from "@/models/stack"; -import { gnosisTokens, mainnetTokens, arbitrumTokens } from "@/models/token"; +import { + gnosisTokens, + mainnetTokens, + arbitrumTokens, + baseTokens, +} from "@/models/token"; import { Strategy } from "@/contexts"; import { ChainId } from "@stackly/sdk"; @@ -149,4 +154,47 @@ export const STRATEGY_CATEGORIES: { [chainId: number]: ChainStrategies } = { ], }, }, + [ChainId.BASE]: { + popular: { + label: "Popular Strategies", + strategies: [ + { + id: 1, + buyToken: baseTokens.WETH, + daysAmount: 30, + frequency: FREQUENCY_OPTIONS.day, + sellAmountPerTimeframe: 50, + sellToken: baseTokens.USDC, + totalSellAmount: "1500", + }, + { + id: 2, + buyToken: baseTokens.WETH, + daysAmount: 7, + frequency: FREQUENCY_OPTIONS.day, + sellAmountPerTimeframe: 20, + sellToken: baseTokens.USDC, + totalSellAmount: "140", + }, + { + id: 3, + buyToken: baseTokens.WETH, + daysAmount: 10, + frequency: FREQUENCY_OPTIONS.hour, + sellAmountPerTimeframe: 5, + sellToken: baseTokens.USDC, + totalSellAmount: "1200", + }, + { + id: 4, + buyToken: baseTokens.WETH, + daysAmount: 56, + frequency: FREQUENCY_OPTIONS.week, + sellAmountPerTimeframe: 100, + sellToken: baseTokens.USDC, + totalSellAmount: "800", + }, + ], + }, + }, }; diff --git a/packages/app/components/token-picker/constants.ts b/packages/app/components/token-picker/constants.ts index da9345db..ae215195 100644 --- a/packages/app/components/token-picker/constants.ts +++ b/packages/app/components/token-picker/constants.ts @@ -2,6 +2,7 @@ import { arbitrumTokens, gnosisTokens, mainnetTokens, + baseTokens, TokenFromTokenlist, } from "@/models/token"; import { ChainId } from "@stackly/sdk"; @@ -28,4 +29,9 @@ export const TOKEN_PICKER_COMMON_TOKENS: { gnosisTokens.WETH, gnosisTokens.WXDAI, ], + [ChainId.BASE]: [ + baseTokens.USDC, + baseTokens.WETH, + // Add any other common Base tokens you want to display in the token picker + ], }; diff --git a/packages/app/constants/urls.ts b/packages/app/constants/urls.ts index a041404e..895d3f2a 100644 --- a/packages/app/constants/urls.ts +++ b/packages/app/constants/urls.ts @@ -6,6 +6,7 @@ export const RPC_LIST: { [chainId: number]: string } = { [ChainId.GNOSIS]: process.env.RPC_GNOSIS ?? "https://rpc.gnosis.gateway.fm/", [ChainId.ARBITRUM]: process.env.RPC_ARBITRUM ?? "https://arbitrum-one-rpc.publicnode.com/", + [ChainId.BASE]: process.env.RPC_BASE ?? "https://mainnet.base.org", }; // App URLs diff --git a/packages/app/contexts/TokenListContext.tsx b/packages/app/contexts/TokenListContext.tsx index 2f79f75f..6621653d 100644 --- a/packages/app/contexts/TokenListContext.tsx +++ b/packages/app/contexts/TokenListContext.tsx @@ -23,6 +23,7 @@ import { TokenFromTokenlist } from "@/models"; import defaultGnosisTokenlist from "public/assets/blockchains/gnosis/tokenlist.json"; import defaultEthereumTokenlist from "public/assets/blockchains/ethereum/tokenlist.json"; import defaultArbitrumTokenList from "public/assets/blockchains/arbitrum/tokenlist.json"; +import defaultBaseTokenList from "public/assets/blockchains/base/tokenlist.json"; import { useNetworkContext } from "./NetworkContext"; @@ -37,6 +38,7 @@ const DEFAULT_TOKEN_LIST_BY_CHAIN: { [ChainId.ETHEREUM]: defaultEthereumTokenlist, [ChainId.GNOSIS]: defaultGnosisTokenlist, [ChainId.ARBITRUM]: defaultArbitrumTokenList, + [ChainId.BASE]: defaultBaseTokenList, }; const TOKEN_LISTS_BY_CHAIN_URL: { [chainId: number]: string[] } = { @@ -53,6 +55,10 @@ const TOKEN_LISTS_BY_CHAIN_URL: { [chainId: number]: string[] } = { "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/ArbitrumOneUniswapTokensList.json", "https://tokens.coingecko.com/arbitrum-one/all.json", ], + [ChainId.BASE]: [ + "https://raw.githubusercontent.com/ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json", + "https://tokens.coingecko.com/base/all.json", + ], }; const TokenListContext = createContext<{ diff --git a/packages/app/models/cow-order/cow-order.ts b/packages/app/models/cow-order/cow-order.ts index b6fc0d4d..fa5ccc18 100644 --- a/packages/app/models/cow-order/cow-order.ts +++ b/packages/app/models/cow-order/cow-order.ts @@ -7,6 +7,7 @@ export const COW_API_URL: Readonly> = { [ChainId.ETHEREUM]: `${COW_API_BASE_URL}/mainnet/${API_VERSION_PATH}`, [ChainId.GNOSIS]: `${COW_API_BASE_URL}/xdai/${API_VERSION_PATH}`, [ChainId.ARBITRUM]: `${COW_API_BASE_URL}/arbitrum_one/${API_VERSION_PATH}`, + [ChainId.BASE]: `${COW_API_BASE_URL}/base/${API_VERSION_PATH}`, }; const COW_EXPLORER_BASE_URL = "https://explorer.cow.fi"; @@ -16,6 +17,7 @@ export const COW_API_EXPLORER_URL: Readonly> = { [ChainId.ETHEREUM]: `${COW_EXPLORER_BASE_URL}/${ORDERS_PATH}/`, [ChainId.GNOSIS]: `${COW_EXPLORER_BASE_URL}/gc/${ORDERS_PATH}/`, [ChainId.ARBITRUM]: `${COW_EXPLORER_BASE_URL}/arb1/${ORDERS_PATH}/`, + [ChainId.BASE]: `${COW_EXPLORER_BASE_URL}/base/${ORDERS_PATH}/`, }; export const cowExplorerUrl = (chainId: ChainId, uid: string) => diff --git a/packages/app/models/token/base.ts b/packages/app/models/token/base.ts new file mode 100644 index 00000000..587dd106 --- /dev/null +++ b/packages/app/models/token/base.ts @@ -0,0 +1,19 @@ +export const baseTokens = { + USDC: { + address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + name: "USD Coin", + symbol: "USDC", + decimals: 6, + logoURI: "/assets/images/tokens/usdc.png", + chainId: 8453, + }, + WETH: { + address: "0x4200000000000000000000000000000000000006", + name: "Wrapped Ether on Base", + symbol: "WETH", + decimals: 18, + logoURI: "/assets/images/tokens/weth.png", + chainId: 8453, + }, + // Add other relevant Base tokens +}; diff --git a/packages/app/models/token/index.ts b/packages/app/models/token/index.ts index d1c603a7..c9405331 100644 --- a/packages/app/models/token/index.ts +++ b/packages/app/models/token/index.ts @@ -1,4 +1,5 @@ export * from "./gnosis"; export * from "./arbitrum"; export * from "./mainnet"; +export * from "./base"; export * from "./types"; diff --git a/packages/app/providers/wagmi-config.ts b/packages/app/providers/wagmi-config.ts index 9f03dd63..18520622 100644 --- a/packages/app/providers/wagmi-config.ts +++ b/packages/app/providers/wagmi-config.ts @@ -1,19 +1,20 @@ import { ChainId } from "@stackly/sdk"; import { createConfig, fallback, http } from "wagmi"; import { getDefaultConfig } from "connectkit"; -import { gnosis, mainnet, arbitrum } from "wagmi/chains"; +import { gnosis, mainnet, arbitrum, base } from "wagmi/chains"; import { safe } from "wagmi/connectors"; import { RPC_LIST } from "@/constants"; const defaultConfig = getDefaultConfig({ - chains: [gnosis, mainnet, arbitrum], + chains: [gnosis, mainnet, arbitrum, base], walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || "", transports: { [mainnet.id]: fallback([http(RPC_LIST[ChainId.ETHEREUM]), http()]), [gnosis.id]: fallback([http(RPC_LIST[ChainId.GNOSIS]), http()]), [arbitrum.id]: fallback([http(RPC_LIST[ChainId.ARBITRUM]), http()]), + [base.id]: fallback([http(RPC_LIST[ChainId.BASE]), http()]), }, appName: "Stackly", appDescription: "Empower your portfolio with DCA.", diff --git a/packages/app/public/assets/blockchains/base/tokenlist.json b/packages/app/public/assets/blockchains/base/tokenlist.json new file mode 100644 index 00000000..a27f259b --- /dev/null +++ b/packages/app/public/assets/blockchains/base/tokenlist.json @@ -0,0 +1,26 @@ +[ + { + "address": "0x4200000000000000000000000000000000000006", + "name": "Wrapped Ether", + "symbol": "WETH", + "decimals": 18, + "logoURI": "/assets/images/tokens/weth.png", + "chainId": 8453 + }, + { + "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "name": "USD Coin", + "symbol": "USDC", + "decimals": 6, + "logoURI": "/assets/images/tokens/usdc.png", + "chainId": 8453 + }, + { + "address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "name": "Coinbase Wrapped BTC", + "symbol": "cbBTC", + "decimals": 8, + "logoURI": "/assets/images/tokens/cbbtc.png", // todo add proper image + "chainId": 8453 + } +] diff --git a/packages/app/utils/constants.ts b/packages/app/utils/constants.ts index 67cc569c..34b7d87a 100644 --- a/packages/app/utils/constants.ts +++ b/packages/app/utils/constants.ts @@ -3,6 +3,7 @@ import { gnosisTokens, mainnetTokens, TokenFromTokenlist, + baseTokens, } from "@/models/token"; import { ChainId } from "@stackly/sdk"; @@ -24,4 +25,8 @@ export const DEFAULT_TOKENS_BY_CHAIN: { [chainId: number]: DefaultTokens } = { from: gnosisTokens.WXDAI, to: gnosisTokens.WETH, }, + [ChainId.BASE]: { + from: baseTokens.USDC, + to: baseTokens.WETH, + }, }; diff --git a/packages/app/utils/transaction.ts b/packages/app/utils/transaction.ts index a8814b8f..d83f2cbd 100644 --- a/packages/app/utils/transaction.ts +++ b/packages/app/utils/transaction.ts @@ -4,6 +4,7 @@ const EXPLORER_URL_BY_CHAIN = { [ChainId.ETHEREUM]: "https://etherscan.io", [ChainId.GNOSIS]: "https://gnosisscan.io", [ChainId.ARBITRUM]: "https://arbiscan.io", + [ChainId.BASE]: "https://basescan.org", }; export const getExplorerLink = ( diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index f630e44b..0bb23689 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -2,6 +2,7 @@ export enum ChainId { ETHEREUM = 1, GNOSIS = 100, ARBITRUM = 42161, + BASE = 8453, } /** diff --git a/packages/sdk/src/vaults/constants.ts b/packages/sdk/src/vaults/constants.ts index b3c10d70..7d0aabd3 100644 --- a/packages/sdk/src/vaults/constants.ts +++ b/packages/sdk/src/vaults/constants.ts @@ -10,6 +10,8 @@ export const GNOSIS_ORDER_FACTORY_ADDRESS = export const ARBITRUM_ORDER_FACTORY_ADDRESS = "0xf4cd605e5fef8618ac450e84b7e912c870927922"; +export const BASE_ORDER_FACTORY_ADDRESS = "0xYourBaseFactoryAddress"; // Replace with actual address + const validateVaultInfo = ( chainId: ChainId, map: Record | Readonly>, @@ -30,6 +32,7 @@ export const ORDER_FACTORY_ADDRESS_LIST: Record = { [ChainId.ETHEREUM]: MAINNET_ORDER_FACTORY_ADDRESS, [ChainId.GNOSIS]: GNOSIS_ORDER_FACTORY_ADDRESS, [ChainId.ARBITRUM]: ARBITRUM_ORDER_FACTORY_ADDRESS, + [ChainId.BASE]: BASE_ORDER_FACTORY_ADDRESS, }; /** @@ -39,6 +42,7 @@ export const DCAORDER_SINGLETON_ADDRESS_LIST: Record = { [ChainId.ETHEREUM]: "0xc97ecbdba20c672c61e27bd657d4dfbd2328f6fa", [ChainId.GNOSIS]: "0xFc41E4DCBab781092a32E8487cFB7444F9e0e403", [ChainId.ARBITRUM]: "0x810f9f1384421b6d185f46253e36f3a558e57369", + [ChainId.BASE]: "0xYourBaseSingletonAddress", // Replace with actual address }; /** @@ -54,6 +58,7 @@ export const COW_SETTLEMENT_ADDRESS_LIST: Record = { [ChainId.ETHEREUM]: COW_SETTLEMENT_ADDRESS, [ChainId.GNOSIS]: COW_SETTLEMENT_ADDRESS, [ChainId.ARBITRUM]: COW_SETTLEMENT_ADDRESS, + [ChainId.BASE]: COW_SETTLEMENT_ADDRESS, }; const API_BASE_URL = "https://gateway-arbitrum.network.thegraph.com/api"; @@ -72,10 +77,15 @@ const ARBITRUM_SUBGRAPH_ENDPOINT_URL = process.env.ARBITRUM_SUBGRAPH_API_URL ?? `${API_BASE_URL}/${SUBGRAPH_API_KEY}/subgraphs/id/FNmemHB6tUh7eHmJnBFKYFf27U5GUAzXnatry4ZbrF7f`; +const BASE_SUBGRAPH_ENDPOINT_URL = + process.env.BASE_SUBGRAPH_API_URL ?? + `${API_BASE_URL}/${SUBGRAPH_API_KEY}/subgraphs/id/TODO`; // Replace with actual subgraph ID + export const SUBGRAPH_ENDPOINT_LIST: Readonly> = { [ChainId.ETHEREUM]: ETHEREUM_SUBGRAPH_ENDPOINT_URL, [ChainId.GNOSIS]: GNOSIS_SUBGRAPH_ENDPOINT_URL, [ChainId.ARBITRUM]: ARBITRUM_SUBGRAPH_ENDPOINT_URL, + [ChainId.BASE]: BASE_SUBGRAPH_ENDPOINT_URL, }; /** diff --git a/packages/subgraph/bin/config.ts b/packages/subgraph/bin/config.ts index 8c317e04..d9e5b049 100644 --- a/packages/subgraph/bin/config.ts +++ b/packages/subgraph/bin/config.ts @@ -25,4 +25,10 @@ export const config: Record< startBlock: 218655333, }, }, + base: { + orderFactory: { + address: "0xYourBaseFactoryAddress", + startBlock: 1234567, + }, + }, }; From c768bf052184f933adf5c702bd3b4671840f88dd Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Fri, 13 Dec 2024 18:02:37 +0700 Subject: [PATCH 02/13] add Base network to landing page --- .../components/sections/FAQ/constants.ts | 2 +- .../sections/HeroBanner/HeroBanner.tsx | 64 ++++++++++--------- ...{arbitrum-avatar.svg => arbitrum-logo.svg} | 0 .../public/assets/images/base-logo.svg | 4 ++ ...{ethereum-avatar.svg => ethereum-logo.svg} | 0 .../{gnosis-avatar.svg => gnosis-logo.svg} | 0 6 files changed, 40 insertions(+), 30 deletions(-) rename packages/landing/public/assets/images/{arbitrum-avatar.svg => arbitrum-logo.svg} (100%) create mode 100644 packages/landing/public/assets/images/base-logo.svg rename packages/landing/public/assets/images/{ethereum-avatar.svg => ethereum-logo.svg} (100%) rename packages/landing/public/assets/images/{gnosis-avatar.svg => gnosis-logo.svg} (100%) diff --git a/packages/landing/components/sections/FAQ/constants.ts b/packages/landing/components/sections/FAQ/constants.ts index c0c5ee9f..07133f96 100644 --- a/packages/landing/components/sections/FAQ/constants.ts +++ b/packages/landing/components/sections/FAQ/constants.ts @@ -70,7 +70,7 @@ export const FAQ_QUESTIONS_AND_ANSWERS: FaqQa[] = [ { question: "Which networks is Stackly available?", answers: [ - `Currently Stackly supports Arbitrum, Gnosis and Ethereum mainnet networks.`, + `Currently Stackly supports Arbitrum, Base, Gnosis and Ethereum mainnet networks.`, ], trackEventName: EVENTS.SECTIONS.FAQ.NETWORKS_AVAILABLE, }, diff --git a/packages/landing/components/sections/HeroBanner/HeroBanner.tsx b/packages/landing/components/sections/HeroBanner/HeroBanner.tsx index 1b10136a..900dbd2a 100644 --- a/packages/landing/components/sections/HeroBanner/HeroBanner.tsx +++ b/packages/landing/components/sections/HeroBanner/HeroBanner.tsx @@ -15,39 +15,45 @@ import { import { EVENTS } from "@/analytics"; import { STACKLY_APP_URL } from "@/constants"; +export const SUPPORTED_NETWORKS = [ + { + name: "Ethereum", + image: "/assets/images/ethereum-logo.svg", + }, + { + name: "Gnosis", + image: "/assets/images/gnosis-logo.svg", + }, + { + name: "Arbitrum", + image: "/assets/images/arbitrum-logo.svg", + }, + { + name: "Base", + image: "/assets/images/base-logo.svg", + }, +]; + export const HeroBanner = () => { return (
-
+

Live on:

- ethereum logo - gnosis logo - arbitrum logo + {SUPPORTED_NETWORKS.map((network) => ( + {network.name} + ))}
-
+
Empower your portfolio Say goodbye to market timing and hello to effortless recurrent swaps. @@ -65,15 +71,15 @@ export const HeroBanner = () => { > Start stacking now -
+
amount widget