diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b5f2bd78..5bc45c70 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -32,7 +32,8 @@ module.exports = { "@typescript-eslint/no-unnecessary-type-assertion": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-misused-promises": "off", - "react-hooks-addons/no-unused-deps": ["warn", { effectComment: "used" }] + "react-hooks-addons/no-unused-deps": ["warn", { effectComment: "used" }], + "@typescript-eslint/no-unused-vars": ["warn", { vars: "local", args: "after-used", ignoreRestSiblings: true }] }, globals: { JSX: true, diff --git a/packages/good-design/package.json b/packages/good-design/package.json index 05fb5fd7..bb37ecc2 100644 --- a/packages/good-design/package.json +++ b/packages/good-design/package.json @@ -27,7 +27,6 @@ "@babel/preset-react": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.18.9", - "@gooddollar/web3sdk-v2": "latest", "@storybook/addon-actions": "^6.5.12", "@storybook/addon-essentials": "^6.5.12", "@storybook/addon-links": "^6.5.12", @@ -73,6 +72,7 @@ "@babel/core": "^7.18.10", "@babel/runtime": "^7.18.9", "@gooddollar/goodprotocol": "^2.0.3", + "@gooddollar/web3sdk-v2": "workspace:^", "@lingui/macro": "^3.14.0", "@lingui/react": "^3.14.0", "@magiklabs/react-sdk": "^1.0.8", diff --git a/packages/good-design/src/core/layout/ActionHeader.tsx b/packages/good-design/src/core/layout/ActionHeader.tsx index 35911f70..8cecf9a9 100644 --- a/packages/good-design/src/core/layout/ActionHeader.tsx +++ b/packages/good-design/src/core/layout/ActionHeader.tsx @@ -4,7 +4,7 @@ import { Title } from "./"; interface ActionHeaderProps { textColor: any; - /** text to complete the 'To complete this action, ...' sentence */ + /** text to complete the 'To complete this action, ...' copy */ actionText: string; } diff --git a/packages/good-design/src/core/web3/index.ts b/packages/good-design/src/core/web3/index.ts index a677f99e..6958e930 100644 --- a/packages/good-design/src/core/web3/index.ts +++ b/packages/good-design/src/core/web3/index.ts @@ -1,4 +1,3 @@ export * from "./WalletAndChainGuard"; -export * from "./SwitchChainModal"; +export * from "./modals"; export * from "./ExplorerLink"; -export * from "./KimaModal"; diff --git a/packages/good-design/src/core/web3/KimaModal.tsx b/packages/good-design/src/core/web3/modals/KimaModal.tsx similarity index 90% rename from packages/good-design/src/core/web3/KimaModal.tsx rename to packages/good-design/src/core/web3/modals/KimaModal.tsx index b844c2c6..aa87c171 100644 --- a/packages/good-design/src/core/web3/KimaModal.tsx +++ b/packages/good-design/src/core/web3/modals/KimaModal.tsx @@ -1,8 +1,8 @@ import React, { useEffect } from "react"; -import { useModal } from "../../hooks/useModal"; +import { useModal } from "../../../hooks/useModal"; import { Box, Text } from "native-base"; -import { LearnButton } from "../buttons"; -import { Title } from "../layout"; +import { LearnButton } from "../../buttons"; +import { Title } from "../../layout"; type BridgeNetworks = { origin: string; diff --git a/packages/good-design/src/core/web3/modals/SignTxModal.tsx b/packages/good-design/src/core/web3/modals/SignTxModal.tsx new file mode 100644 index 00000000..71698ba8 --- /dev/null +++ b/packages/good-design/src/core/web3/modals/SignTxModal.tsx @@ -0,0 +1,96 @@ +import React, { useEffect } from "react"; +import { Box, Heading, useColorModeValue, useToast, Text } from "native-base"; +import { useNotifications } from "@usedapp/core"; +import type { TransactionReceipt, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider"; +import { useModal } from "../../../hooks/useModal"; +import { ActionHeader } from "../../layout"; +import { LearnButton } from "../../buttons"; + +// usedapp type definitions without walletConnected +type NotificationPayload = { submittedAt: number } & ( + | { type: "transactionPendingSignature"; transactionName?: string; transactionRequest?: TransactionRequest } + | { type: "transactionStarted"; transaction: TransactionResponse; transactionName?: string } + | { + type: "transactionSucceed"; + transaction: TransactionResponse; + receipt: TransactionReceipt; + transactionName?: string; + originalTransaction?: TransactionResponse; + } + | { + type: "transactionFailed"; + transaction: TransactionResponse; + receipt: TransactionReceipt; + transactionName?: string; + originalTransaction?: TransactionResponse; + } +); + +export type Notification = { id: string } & NotificationPayload; + +//todo: add proper (customizable) styles +const SimpleTxToast = ({ title, desc }: { title: string; desc: string }) => ( + + {title} + txName: {desc} + +); + +export type SignTxProps = { + children?: any; +} & ({ withToast: boolean; onSubmit?: never } | { withToast?: never; onSubmit: () => void }); + +/** + * A modal to wrap your component or page with and show a modal re-active to a + * pending signature for a usedapp useContractFunction call + * it assumes you have already wrapped your app with the Web3Provider out of the @gooddollar/sdk-v2 package + * @param children + * @param withToast - if true, will show a simple toast + * @param onSubmitted - if withToast false, provide a callback to handle what happens after tx is submitted + * @returns JSX.Element + */ +export const SignTxModal = ({ children, withToast, onSubmit }: SignTxProps) => { + const doWithToast = withToast; + const { notifications } = useNotifications(); + const toast = useToast(); + const textColor = useColorModeValue("goodGrey.500", "white"); + + const { Modal, showModal, hideModal } = useModal(); + + useEffect(() => { + const localNotif = notifications as Notification[]; + if (localNotif[0]?.type.includes("transaction")) { + const { type, transactionName = "" } = localNotif[0]; + switch (type) { + case "transactionPendingSignature": + showModal(); + break; + case "transactionStarted": + hideModal(); + if (doWithToast) { + toast.show({ + render: () => , + placement: "top-right" + }); + } else { + onSubmit?.(); + } + break; + default: + hideModal(); + break; + } + } + }, [notifications]); + + return ( + + } + body={} + closeText="x" + /> + {children} + + ); +}; diff --git a/packages/good-design/src/core/web3/SwitchChainModal.tsx b/packages/good-design/src/core/web3/modals/SwitchChainModal.tsx similarity index 84% rename from packages/good-design/src/core/web3/SwitchChainModal.tsx rename to packages/good-design/src/core/web3/modals/SwitchChainModal.tsx index 3c7914a8..8041c6ea 100644 --- a/packages/good-design/src/core/web3/SwitchChainModal.tsx +++ b/packages/good-design/src/core/web3/modals/SwitchChainModal.tsx @@ -3,9 +3,9 @@ import React, { useEffect, useState } from "react"; import { useConfig } from "@usedapp/core"; import { useSwitchNetwork } from "@gooddollar/web3sdk-v2"; import { find } from "lodash"; -import { useModal } from "../../hooks/useModal"; -import { ActionHeader } from "../layout"; -import { LearnButton } from "../buttons"; +import { useModal } from "../../../hooks/useModal"; +import { ActionHeader } from "../../layout"; +import { LearnButton } from "../../buttons"; export interface SwitchChainProps { children?: any; @@ -14,7 +14,6 @@ export interface SwitchChainProps { /** * A modal to wrap your component or page with and show a modal re-active to switchChain requests * it assumes you have already wrapped your app with the Web3Provider out of the @gooddollar/sdk-v2 package - * @param {boolean} switching indicating if there is a pending switch request triggered * @param children * @returns JSX.Element */ @@ -37,7 +36,6 @@ export const SwitchChainModal = ({ children }: SwitchChainProps) => { } }); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [setOnSwitchNetwork]); const networkName = find(config.networks, _ => _.chainId === requestedChain)?.chainName; diff --git a/packages/good-design/src/core/web3/modals/index.ts b/packages/good-design/src/core/web3/modals/index.ts new file mode 100644 index 00000000..04793e33 --- /dev/null +++ b/packages/good-design/src/core/web3/modals/index.ts @@ -0,0 +1,3 @@ +export * from "./SwitchChainModal"; +export * from "./SignTxModal"; +export * from "./KimaModal"; diff --git a/packages/good-design/src/stories/W3Wrapper.tsx b/packages/good-design/src/stories/W3Wrapper.tsx index 23524828..49d3815f 100644 --- a/packages/good-design/src/stories/W3Wrapper.tsx +++ b/packages/good-design/src/stories/W3Wrapper.tsx @@ -19,6 +19,9 @@ const config: Config = { readOnlyUrls: { 122: "https://rpc.fuse.io", 42220: "https://forno.celo.org" + }, + notifications: { + expirationPeriod: 0 } }; @@ -30,7 +33,7 @@ export const W3Wrapper = ({ children, withMetaMask, env = "fuse" }: PageProps) = if (!withMetaMask) { const rpc = new ethers.providers.JsonRpcProvider("https://rpc.fuse.io"); - + rpc.getSigner = () => w as any; setProvider(rpc); } diff --git a/packages/good-design/src/stories/apps/bridge/MicroBridgeController.stories.tsx b/packages/good-design/src/stories/apps/bridge/MicroBridgeController.stories.tsx index 378a4448..98a93da6 100644 --- a/packages/good-design/src/stories/apps/bridge/MicroBridgeController.stories.tsx +++ b/packages/good-design/src/stories/apps/bridge/MicroBridgeController.stories.tsx @@ -2,13 +2,13 @@ import { WalletAndChainGuard } from "../../../core/web3/WalletAndChainGuard"; import React from "react"; import { MicroBridgeController } from "../../../apps/bridge/MicroBridgeController"; import { W3Wrapper } from "../../W3Wrapper"; -import { SwitchChainModal } from "../../../core/web3/SwitchChainModal"; +import { SwitchChainModal } from "../../../core/web3/modals/SwitchChainModal"; export default { title: "Apps/MicroBridgeController", component: MicroBridgeController, decorators: [ - (Story:any) => ( + (Story: any) => ( diff --git a/packages/good-design/src/stories/core/web3/SignTxModal.stories.tsx b/packages/good-design/src/stories/core/web3/SignTxModal.stories.tsx new file mode 100644 index 00000000..21e92176 --- /dev/null +++ b/packages/good-design/src/stories/core/web3/SignTxModal.stories.tsx @@ -0,0 +1,114 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { SignTxModal } from "../../../core"; +import { W3Wrapper } from "../../W3Wrapper"; +import BaseButton from "../../../core/buttons/BaseButton"; +import { Box, HStack, Center, Heading } from "native-base"; +import { useNotifications, useEthers } from "@usedapp/core"; +import { ethers } from "ethers"; +import { JsonRpcSigner } from "@ethersproject/providers"; +import useTestContractFunction from "../../hooks/useTestContractFunction"; + +const SignTxModalExample = () => { + const { notifications } = useNotifications(); + const [signer, setSigner] = useState(undefined); + const { send, transferState } = useTestContractFunction({ signer }); + const { activateBrowserWallet, library, account } = useEthers(); + const [connected, setConnected] = useState(false); + + const connect = useCallback(async () => { + await (activateBrowserWallet as any)(); + setConnected(true); + }, []); + + useEffect(() => { + if (library && !signer && account) { + const signer = (library as ethers.providers.JsonRpcProvider)?.getSigner(); + setSigner(signer); + } + }, [library, connected]); + + useEffect(() => { + console.log("notifications -->", { notifications, transferState }); + }, [notifications, transferState]); + + return ( + + + {!connected ? ( + + ) : ( + <> + + + )} + + + {notifications.map(notification => { + // this first if check is needed because of a weird type inference + if (notification.type === "walletConnected") return; + else if (notification.type.includes("transaction")) + return ( + + Notifications + +
+ id: {notification.id}{" "} +
+
+ type: {notification.type}{" "} +
+
+ transactionName: {notification.transactionName}{" "} +
+
+ submittedAt: {notification.submittedAt}{" "} +
+
+
+ ); + })} +
+
+ ); +}; + +export default { + title: "Core/Web3/SignTxModal", + component: SignTxModalExample, + decorators: [ + (Story: any) => ( + + + + ) + ], + argTypes: {} +}; + +export const SignTxModalStory = { + args: {} +}; diff --git a/packages/good-design/src/stories/hooks/useTestContractFunction.tsx b/packages/good-design/src/stories/hooks/useTestContractFunction.tsx new file mode 100644 index 00000000..584acba7 --- /dev/null +++ b/packages/good-design/src/stories/hooks/useTestContractFunction.tsx @@ -0,0 +1,37 @@ +import React, { useCallback, useState } from "react"; +import { useEthers } from "@usedapp/core"; +import { ethers, Contract } from "ethers"; +import { JsonRpcSigner } from "@ethersproject/providers"; +import { useContractFunctionWithDefaultGasFees } from "@gooddollar/web3sdk-v2"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +// import { +// IGoodDollar, +// } from "@gooddollar/goodprotocol/types"; +import GoodDollarABI from "@gooddollar/goodprotocol/artifacts/abis/IGoodDollar.min.json"; + +interface CustomProps { + signer?: JsonRpcSigner; +} + +const useTestContractFunction = (params: CustomProps) => { + const { signer } = params; + const { account } = useEthers(); + const gdContract = new Contract(Contracts["fuse"].GoodDollar, GoodDollarABI.abi, signer) as any; + + const { state: transferState, send: sendTransfer } = useContractFunctionWithDefaultGasFees( + gdContract, + "transferAndCall", + { + transactionName: "SelfTransfer" + } + ); + + const send = useCallback(async () => { + const callData = ethers.constants.HashZero; + await sendTransfer(account, "10000", callData); + }, [account, sendTransfer, signer]); + + return { send, transferState }; +}; + +export default useTestContractFunction; diff --git a/packages/sdk-v2/src/sdk/base/hooks/useGasFees.ts b/packages/sdk-v2/src/sdk/base/hooks/useGasFees.ts index 72339e4b..80204854 100644 --- a/packages/sdk-v2/src/sdk/base/hooks/useGasFees.ts +++ b/packages/sdk-v2/src/sdk/base/hooks/useGasFees.ts @@ -30,8 +30,9 @@ export const useGasFees = () => { export function useContractFunctionWithDefaultGasFees>( contract: T | Falsy, functionName: FN, - options?: TransactionOptions + customOptions?: TransactionOptions ) { + const options = { enablePendingSignatureNotification: true, ...customOptions }; const { send, ...rest } = useContractFunction(contract, functionName, options); const gasFees = useGasFees(); const newSend = useCallback( diff --git a/packages/sdk-v2/src/sdk/claim/react.ts b/packages/sdk-v2/src/sdk/claim/react.ts index 999a361a..90cfdb04 100644 --- a/packages/sdk-v2/src/sdk/claim/react.ts +++ b/packages/sdk-v2/src/sdk/claim/react.ts @@ -42,7 +42,7 @@ export const useClaim = (refresh: QueryParams["refresh"] = "never") => { const ubi = useGetContract("UBIScheme", true, "claim", chainId) as UBIScheme; const identity = useGetContract("Identity", true, "claim", chainId) as IIdentity; - const claimCall = useContractFunctionWithDefaultGasFees(ubi, "claim"); + const claimCall = useContractFunctionWithDefaultGasFees(ubi, "claim", { transactionName: "Claimed Daily UBI" }); const results = useCalls( [ diff --git a/packages/sdk-v2/src/sdk/microbridge/react.ts b/packages/sdk-v2/src/sdk/microbridge/react.ts index 3bc2e4de..85bb18c5 100644 --- a/packages/sdk-v2/src/sdk/microbridge/react.ts +++ b/packages/sdk-v2/src/sdk/microbridge/react.ts @@ -109,7 +109,9 @@ export const useBridge = (withRelay = false) => { const [selfRelayStatus, setSelfRelay] = useState | undefined>(); // const bridgeTo = useContractFunction(bridgeContract, "bridgeTo", {}); - const transferAndCall = useContractFunctionWithDefaultGasFees(g$Contract, "transferAndCall"); + const transferAndCall = useContractFunctionWithDefaultGasFees(g$Contract, "transferAndCall", { + transactionName: "BridgeTransfer" + }); const bridgeRequestId = (transferAndCall.state?.receipt?.logs || []) .filter(log => log.address === bridgeContract.address) .map(log => bridgeContract.interface.parseLog(log))?.[0]?.args?.id; diff --git a/yarn.lock b/yarn.lock index 178f798a..1affd717 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3255,7 +3255,7 @@ __metadata: "@babel/preset-typescript": ^7.16.0 "@babel/runtime": ^7.18.9 "@gooddollar/goodprotocol": ^2.0.3 - "@gooddollar/web3sdk-v2": latest + "@gooddollar/web3sdk-v2": "workspace:^" "@lingui/macro": ^3.14.0 "@lingui/react": ^3.14.0 "@magiklabs/react-sdk": ^1.0.8 @@ -3438,7 +3438,7 @@ __metadata: languageName: node linkType: hard -"@gooddollar/web3sdk-v2@workspace:packages/sdk-v2": +"@gooddollar/web3sdk-v2@workspace:^, @gooddollar/web3sdk-v2@workspace:packages/sdk-v2": version: 0.0.0-use.local resolution: "@gooddollar/web3sdk-v2@workspace:packages/sdk-v2" dependencies: