From 2fc41a279d819400bf642dbae6febfeefffacc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96hrlund?= Date: Tue, 4 Jul 2023 10:05:00 +0200 Subject: [PATCH 1/7] add 2 chains per env: mainChain for contracts & secondaryChain for tbls --- garage/src/App.tsx | 4 ++-- garage/src/env.ts | 20 ++++++++++++++----- garage/src/hooks/useNFTs.ts | 6 +++--- garage/src/hooks/useTablelandConnection.ts | 2 +- .../pages/Dashboard/modules/RigsInventory.tsx | 4 ++-- garage/src/pages/RigDetails/index.tsx | 4 ++-- garage/src/utils/queries.ts | 2 +- 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/garage/src/App.tsx b/garage/src/App.tsx index dff593ff..07faa7e5 100644 --- a/garage/src/App.tsx +++ b/garage/src/App.tsx @@ -23,10 +23,10 @@ import { RigAttributeStatsContextProvider } from "./components/RigAttributeStats import { NFTsContextProvider } from "./components/NFTsContext"; import { ActingAsAddressContextProvider } from "./components/ActingAsAddressContext"; import { routes } from "./routes"; -import { chain } from "./env"; +import { mainChain, secondaryChain } from "./env"; const { chains, publicClient } = configureChains( - [chain], + [mainChain, secondaryChain], [ alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_ID }), publicProvider(), diff --git a/garage/src/env.ts b/garage/src/env.ts index 0844e5cc..25f38854 100644 --- a/garage/src/env.ts +++ b/garage/src/env.ts @@ -24,21 +24,31 @@ const parseEnv = (env?: string): DeploymentEnvironment => { const environment = parseEnv(import.meta.env.VITE_APP_ENV); -const chainEnvMapping = { +const mainChainEnvMapping = { [DeploymentEnvironment.DEVELOPMENT]: chains.polygonMumbai, [DeploymentEnvironment.STAGING]: chains.polygonMumbai, [DeploymentEnvironment.PRODUCTION]: chains.mainnet, }; -export const chain = chainEnvMapping[environment]; +const secondaryChainEnvMapping = { + [DeploymentEnvironment.DEVELOPMENT]: chains.polygonMumbai, + [DeploymentEnvironment.STAGING]: chains.polygonMumbai, + [DeploymentEnvironment.PRODUCTION]: chains.filecoin, +}; + +// Main chain used by the rigs contract +export const mainChain = mainChainEnvMapping[environment]; + +// Chain used by secondary tables, like ft rewards and the voting mechanism +export const secondaryChain = secondaryChainEnvMapping[environment]; const blockExplorerChainMapping = { [DeploymentEnvironment.DEVELOPMENT]: - chainEnvMapping[DeploymentEnvironment.STAGING].blockExplorers.etherscan.url, + mainChainEnvMapping[DeploymentEnvironment.STAGING].blockExplorers.etherscan.url, [DeploymentEnvironment.STAGING]: - chainEnvMapping[DeploymentEnvironment.STAGING].blockExplorers.etherscan.url, + mainChainEnvMapping[DeploymentEnvironment.STAGING].blockExplorers.etherscan.url, [DeploymentEnvironment.PRODUCTION]: - chainEnvMapping[DeploymentEnvironment.PRODUCTION].blockExplorers.etherscan + mainChainEnvMapping[DeploymentEnvironment.PRODUCTION].blockExplorers.etherscan .url, }; diff --git a/garage/src/hooks/useNFTs.ts b/garage/src/hooks/useNFTs.ts index ef029c2f..a7f94514 100644 --- a/garage/src/hooks/useNFTs.ts +++ b/garage/src/hooks/useNFTs.ts @@ -10,7 +10,7 @@ import { NftFilters, GetNftsForOwnerOptions, } from "alchemy-sdk"; -import { chain } from "../env"; +import { mainChain } from "../env"; import { useQuery } from "@tanstack/react-query"; const wagmiChainToNetwork = (c: Chain): Network => { @@ -24,13 +24,13 @@ const wagmiChainToNetwork = (c: Chain): Network => { case chains.polygonMumbai: return Network.MATIC_MUMBAI; default: - throw new Error(`wagmiChainToNetwork unsupported chain, ${c}`); + throw new Error(`wagmiChainToNetwork unsupported mainChain, ${c}`); } }; const settings = { apiKey: import.meta.env.VITE_ALCHEMY_ID, - network: wagmiChainToNetwork(chain), + network: wagmiChainToNetwork(mainChain), }; export const alchemy = new Alchemy(settings); diff --git a/garage/src/hooks/useTablelandConnection.ts b/garage/src/hooks/useTablelandConnection.ts index 77bc3a84..fdae8993 100644 --- a/garage/src/hooks/useTablelandConnection.ts +++ b/garage/src/hooks/useTablelandConnection.ts @@ -1,5 +1,5 @@ import { Database, Validator, helpers } from "@tableland/sdk"; -import { chain } from "../env"; +import { mainChain as chain } from "../env"; const db = new Database({ baseUrl: helpers.getBaseUrl(chain.id) }); const validator = new Validator(db.config); diff --git a/garage/src/pages/Dashboard/modules/RigsInventory.tsx b/garage/src/pages/Dashboard/modules/RigsInventory.tsx index 4a4a4c44..ce562033 100644 --- a/garage/src/pages/Dashboard/modules/RigsInventory.tsx +++ b/garage/src/pages/Dashboard/modules/RigsInventory.tsx @@ -34,7 +34,7 @@ import { findNFT } from "../../../utils/nfts"; import { sleep } from "../../../utils/async"; import { prettyNumber } from "../../../utils/fmt"; import { firstSetValue, copySet, toggleInSet } from "../../../utils/set"; -import { chain } from "../../../env"; +import { mainChain } from "../../../env"; interface RigListItemProps { rig: RigWithPilots; @@ -201,7 +201,7 @@ export const RigsInventory = (props: React.ComponentProps) => { validator .pollForReceiptByTransactionHash( { - chainId: chain.id, + chainId: mainChain.id, transactionHash: pendingTx, }, { interval: 2000, signal } diff --git a/garage/src/pages/RigDetails/index.tsx b/garage/src/pages/RigDetails/index.tsx index 34e0ef48..21600d86 100644 --- a/garage/src/pages/RigDetails/index.tsx +++ b/garage/src/pages/RigDetails/index.tsx @@ -34,7 +34,7 @@ import { findNFT } from "../../utils/nfts"; import { prettyNumber, truncateWalletAddress } from "../../utils/fmt"; import { sleep } from "../../utils/async"; import { isValidAddress, as0xString } from "../../utils/types"; -import { chain, openseaBaseUrl, deployment } from "../../env"; +import { mainChain, openseaBaseUrl, deployment } from "../../env"; import { RigWithPilots } from "../../types"; import { abi } from "../../abis/TablelandRigs"; import { ReactComponent as OpenseaMark } from "../../assets/opensea-mark.svg"; @@ -223,7 +223,7 @@ export const RigDetails = () => { validator .pollForReceiptByTransactionHash( { - chainId: chain.id, + chainId: mainChain.id, transactionHash: pendingTx, }, { interval: 2000, signal } diff --git a/garage/src/utils/queries.ts b/garage/src/utils/queries.ts index 0610564c..f1c0c252 100644 --- a/garage/src/utils/queries.ts +++ b/garage/src/utils/queries.ts @@ -1,4 +1,4 @@ -import { chain, deployment } from "../env"; +import { mainChain as chain, deployment } from "../env"; const { attributesTable, lookupsTable, pilotSessionsTable, ftRewardsTable } = deployment; From 29a975935a6e83924fd59f9aa31460463085b3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96hrlund?= Date: Tue, 4 Jul 2023 10:08:32 +0200 Subject: [PATCH 2/7] update all contract reads and writes to specify chain id --- garage/src/components/FlyParkModals.tsx | 5 ++++- garage/src/components/TransferRigModal.tsx | 3 ++- garage/src/hooks/useOwnedRigs.ts | 3 ++- garage/src/pages/PilotDetails/index.tsx | 3 ++- garage/src/pages/RigDetails/index.tsx | 3 +++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/garage/src/components/FlyParkModals.tsx b/garage/src/components/FlyParkModals.tsx index 066d1ec0..19d7d526 100644 --- a/garage/src/components/FlyParkModals.tsx +++ b/garage/src/components/FlyParkModals.tsx @@ -54,7 +54,7 @@ import { useActivePilotSessions } from "../hooks/useActivePilotSessions"; import { Rig, WalletAddress } from "../types"; import { TransactionStateAlert } from "./TransactionStateAlert"; import { RigDisplay } from "./RigDisplay"; -import { deployment } from "../env"; +import { mainChain, deployment } from "../env"; import { abi } from "../abis/TablelandRigs"; import { copySet, toggleInSet } from "../utils/set"; import { pluralize } from "../utils/fmt"; @@ -77,6 +77,7 @@ export const TrainRigsModal = ({ onTransactionSubmitted, }: ModalProps) => { const { config } = usePrepareContractWrite({ + chainId: mainChain.id, address: as0xString(contractAddress), abi, functionName: "trainRig", @@ -152,6 +153,7 @@ export const ParkRigsModal = ({ onTransactionSubmitted, }: ModalProps) => { const { config } = usePrepareContractWrite({ + chainId: mainChain.id, address: as0xString(contractAddress), abi, functionName: "parkRig", @@ -256,6 +258,7 @@ const PilotTransactionStep = ({ }: PilotTransactionProps) => { // TODO support calling pilotRig(uint256, address, uint256) for a single rig? const { config } = usePrepareContractWrite({ + chainId: mainChain.id, address: as0xString(contractAddress), abi, functionName: "pilotRig", diff --git a/garage/src/components/TransferRigModal.tsx b/garage/src/components/TransferRigModal.tsx index d8f56f15..802a3974 100644 --- a/garage/src/components/TransferRigModal.tsx +++ b/garage/src/components/TransferRigModal.tsx @@ -25,7 +25,7 @@ import { Rig } from "../types"; import { isValidAddress, as0xString } from "../utils/types"; import { TransactionStateAlert } from "./TransactionStateAlert"; import { RigDisplay } from "./RigDisplay"; -import { deployment } from "../env"; +import { mainChain, deployment } from "../env"; import { abi } from "../abis/TablelandRigs"; const { contractAddress } = deployment; @@ -53,6 +53,7 @@ export const TransferRigModal = ({ }, [toAddress]); const { config } = usePrepareContractWrite({ + chainId: mainChain.id, address: as0xString(contractAddress), abi, functionName: rig.currentPilot diff --git a/garage/src/hooks/useOwnedRigs.ts b/garage/src/hooks/useOwnedRigs.ts index fb0b2b4d..bcb4ca02 100644 --- a/garage/src/hooks/useOwnedRigs.ts +++ b/garage/src/hooks/useOwnedRigs.ts @@ -4,7 +4,7 @@ import { useContractRead } from "wagmi"; import { useTablelandConnection } from "./useTablelandConnection"; import { selectRigs } from "../utils/queries"; import { isValidAddress, as0xString } from "../utils/types"; -import { deployment } from "../env"; +import { mainChain, deployment } from "../env"; import { abi } from "../abis/TablelandRigs"; const { contractAddress } = deployment; @@ -13,6 +13,7 @@ export const useOwnedRigs = (address?: string) => { const { db } = useTablelandConnection(); const { data } = useContractRead({ + chainId: mainChain.id, address: as0xString(contractAddress), abi, functionName: "tokensOfOwner", diff --git a/garage/src/pages/PilotDetails/index.tsx b/garage/src/pages/PilotDetails/index.tsx index 0eaa8f84..281f9c7c 100644 --- a/garage/src/pages/PilotDetails/index.tsx +++ b/garage/src/pages/PilotDetails/index.tsx @@ -28,7 +28,7 @@ import { useNFTs, NFT } from "../../hooks/useNFTs"; import { useRigImageUrls } from "../../hooks/useRigImageUrls"; import { TOPBAR_HEIGHT } from "../../Topbar"; import { prettyNumber, truncateWalletAddress } from "../../utils/fmt"; -import { openseaBaseUrl } from "../../env"; +import { mainChain, openseaBaseUrl } from "../../env"; import { PilotSessionWithRigId } from "../../types"; import { ReactComponent as OpenseaMark } from "../../assets/opensea-mark.svg"; import { selectPilotSessionsForPilot } from "../../utils/queries"; @@ -202,6 +202,7 @@ export const PilotDetails = () => { const pilot = nfts?.length ? nfts[0] : null; const { data: owner } = useContractRead({ + chainId: mainChain.id, address: as0xString(collection), abi, functionName: "ownerOf", diff --git a/garage/src/pages/RigDetails/index.tsx b/garage/src/pages/RigDetails/index.tsx index 21600d86..6a1aa916 100644 --- a/garage/src/pages/RigDetails/index.tsx +++ b/garage/src/pages/RigDetails/index.tsx @@ -164,18 +164,21 @@ export const RigDetails = () => { allowFailure: false, contracts: [ { + chainId: mainChain.id, address: as0xString(contractAddress)!, abi, functionName: "ownerOf", args: [BigInt(id ?? "")], }, { + chainId: mainChain.id, address: as0xString(contractAddress)!, abi, functionName: "tokenURI", args: [BigInt(id ?? "")], }, { + chainId: mainChain.id, address: as0xString(contractAddress)!, abi, functionName: "pilotInfo", From 1dd21743efa3e8ec38f1cb334c76700327209378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96hrlund?= Date: Tue, 4 Jul 2023 10:10:58 +0200 Subject: [PATCH 3/7] update ChainAwareButton to support specifying an expected chain --- garage/src/components/ChainAwareButton.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/garage/src/components/ChainAwareButton.tsx b/garage/src/components/ChainAwareButton.tsx index 28148944..ce106d51 100644 --- a/garage/src/components/ChainAwareButton.tsx +++ b/garage/src/components/ChainAwareButton.tsx @@ -1,14 +1,19 @@ import React from "react"; import { Button } from "@chakra-ui/react"; -import { useNetwork } from "wagmi"; +import { Chain, useNetwork } from "wagmi"; import { useChainModal } from "@rainbow-me/rainbowkit"; -import { chain as expectedChain } from "../env"; +import { mainChain } from "../env"; export const ChainAwareButton = ( - props: React.ComponentProps + props: { expectedChain?: Chain } & React.ComponentProps ) => { const { chain } = useNetwork(); - const { children: _children, onClick: _onClick, ...rest } = props; + const { + expectedChain = mainChain, + children: _children, + onClick: _onClick, + ...rest + } = props; const { openChainModal } = useChainModal(); let children = _children; From 4bd1ea20c5e1ab3fd34cd91036b6e132b03b5d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96hrlund?= Date: Tue, 4 Jul 2023 10:50:40 +0200 Subject: [PATCH 4/7] update all contract interaction buttons to be chain aware --- garage/src/components/FlyParkModals.tsx | 11 +++++++---- garage/src/components/TransferRigModal.tsx | 6 ++++-- garage/src/pages/Admin/index.tsx | 8 +++++--- garage/src/pages/RigDetails/modules/Pilots.tsx | 5 ++++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/garage/src/components/FlyParkModals.tsx b/garage/src/components/FlyParkModals.tsx index 19d7d526..016b8113 100644 --- a/garage/src/components/FlyParkModals.tsx +++ b/garage/src/components/FlyParkModals.tsx @@ -52,6 +52,7 @@ import { import debounce from "lodash/debounce"; import { useActivePilotSessions } from "../hooks/useActivePilotSessions"; import { Rig, WalletAddress } from "../types"; +import { ChainAwareButton } from "./ChainAwareButton"; import { TransactionStateAlert } from "./TransactionStateAlert"; import { RigDisplay } from "./RigDisplay"; import { mainChain, deployment } from "../env"; @@ -126,13 +127,14 @@ export const TrainRigsModal = ({ - + + + + ); diff --git a/garage/src/pages/RigDetails/modules/Pilots.tsx b/garage/src/pages/RigDetails/modules/Pilots.tsx index b1b6b201..3bd6d790 100644 --- a/garage/src/pages/RigDetails/modules/Pilots.tsx +++ b/garage/src/pages/RigDetails/modules/Pilots.tsx @@ -27,7 +27,7 @@ import { useBlockNumber } from "wagmi"; import { NFT } from "../../../hooks/useNFTs"; import { findNFT } from "../../../utils/nfts"; import { prettyNumber, pluralize } from "../../../utils/fmt"; -import { deployment } from "../../../env"; +import { mainChain, deployment } from "../../../env"; const getPilots = ( rig: RigWithPilots, @@ -194,6 +194,7 @@ export const Pilots = ({ {!rig.currentPilot && !rig.isTrained && chainPilotStatus !== 2 && ( Date: Tue, 4 Jul 2023 10:50:59 +0200 Subject: [PATCH 5/7] update signer used on admin page to specify chain id --- garage/src/pages/Admin/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/garage/src/pages/Admin/index.tsx b/garage/src/pages/Admin/index.tsx index 48d01bc9..719e1ff2 100644 --- a/garage/src/pages/Admin/index.tsx +++ b/garage/src/pages/Admin/index.tsx @@ -35,7 +35,7 @@ const MODULE_PROPS = { const GiveFtRewardForm = (props: React.ComponentProps) => { const toast = useToast(); - const signer = useSigner(); + const signer = useSigner({ chainId: secondaryChain.id }); const db = useMemo(() => { if (signer) return new Database({ signer }); From 353744af8ffd6e10d8d83a7428715fbc6b88f3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96hrlund?= Date: Tue, 4 Jul 2023 10:51:39 +0200 Subject: [PATCH 6/7] add hacky workaround to make delegate.cash sdk always use mainChain --- garage/src/hooks/useAccount.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/garage/src/hooks/useAccount.ts b/garage/src/hooks/useAccount.ts index bd2eb870..b8e6c393 100644 --- a/garage/src/hooks/useAccount.ts +++ b/garage/src/hooks/useAccount.ts @@ -1,11 +1,32 @@ import { useEffect, useMemo, useState } from "react"; import { DelegateCash } from "delegatecash"; +import { providers } from "ethers"; import { useAccount as useWagmiAccount } from "wagmi"; -import { deployment } from "../env"; +import { mainChain, deployment } from "../env"; import { isPresent } from "../utils/types"; import { useActingAsAddress } from "../components/ActingAsAddressContext"; -const dc = new DelegateCash(); +const { id, network } = mainChain; + +// NOTE(daniel): +// this is a hack to work around the fact that we always want to use the +// mainChain-chain to look up delegated wallets. +// +// delegate cash uses the default provider (window.ethereum) if none is provided +// and the default provider will switch network automatically when the +// connected wallet switches network. since we sometimes need the wallet +// to be connected to `mainChain` and sometimes to `secondaryChain` +// we expect the rest of the app to work regardless of which chain +// the user is connected to +// +// we also need to overwrite the `getSigner` method since the dc sdk +// always calls getSigner regardless of if it is supported or not +const provider = new providers.AlchemyProvider( + { chainId: id, name: network }, + import.meta.env.VITE_ALCHEMY_ID +); +provider.getSigner = () => null as any; +const dc = new DelegateCash(provider); type Flatten = Type extends Array ? Item : Type; From 7b105685cce4b0da68bfea7143c710db11f20f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96hrlund?= Date: Tue, 4 Jul 2023 11:36:45 +0200 Subject: [PATCH 7/7] fix Admin page ft reward submit action error handling we need to wrap the .run()-call in a try/catch so that we can catch exceptions thrown if the user rejects the transaction --- garage/src/pages/Admin/index.tsx | 41 +++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/garage/src/pages/Admin/index.tsx b/garage/src/pages/Admin/index.tsx index 719e1ff2..8bd69ab7 100644 --- a/garage/src/pages/Admin/index.tsx +++ b/garage/src/pages/Admin/index.tsx @@ -84,28 +84,31 @@ const GiveFtRewardForm = (props: React.ComponentProps) => { setIsQuerying(true); - const { meta: insert } = await db - .prepare( - `INSERT INTO ${ftRewardsTable} (block_num, recipient, reason, amount) VALUES (BLOCK_NUM(), ?1, ?2, ?3)` - ) - .bind(form.recipient, form.reason, form.amount) - .run(); - - insert.txn - ?.wait() - .then((_) => { + try { + const { meta: insert } = await db + .prepare( + `INSERT INTO ${ftRewardsTable} (block_num, recipient, reason, amount) VALUES (BLOCK_NUM(), ?1, ?2, ?3)` + ) + .bind(form.recipient, form.reason, form.amount) + .run(); + + insert.txn?.wait().then((_) => { setIsQuerying(false); toast({ title: "Success", status: "success", duration: 7_500 }); - }) - .catch((e) => { - setIsQuerying(false); - toast({ - title: "Reward failed", - description: e.toString(), - status: "error", - duration: 7_500, - }); }); + } catch (e) { + if (e instanceof Error) { + if (!/user rejected transaction/.test(e.message)) { + toast({ + title: "Reward failed", + description: e.message, + status: "error", + duration: 7_500, + }); + } + } + setIsQuerying(false); + } }, [db, form]); return (