diff --git a/package.json b/package.json index 12a3c7b..5a97503 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", + "@gelatonetwork/relay-sdk": "^4.0.0", "@gobob/bob-sdk": "^1.0.3", "@interlay/hooks": "^0.0.7", "@interlay/system": "^0.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d59390..af29bf4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: '@ethersproject/providers': specifier: ^5.7.2 version: 5.7.2 + '@gelatonetwork/relay-sdk': + specifier: ^4.0.0 + version: 4.0.0 '@gobob/bob-sdk': specifier: ^1.0.3 version: 1.1.0 @@ -1078,6 +1081,17 @@ packages: tslib: 2.6.2 dev: false + /@gelatonetwork/relay-sdk@4.0.0: + resolution: {integrity: sha512-apjMQ/r6BPLHhRVE0NzL8PWDpbNsne98oG74kzliBDD/D9IJBVhbveJRamecdmrYmsMFxS0WfODhfDDw2yIijw==} + dependencies: + axios: 1.4.0 + ethers: 5.7.2 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + dev: false + /@gobob/bob-sdk@1.1.0: resolution: {integrity: sha512-20bg+fWb24WCFKZ1NhtF1zxcWun+htVm2ZwhmNvXs6i2yJMtSD2o56zV2D4EetFLe7a/t5n2aGRZGdH7K4aUSg==} dependencies: @@ -4966,6 +4980,10 @@ packages: tslib: 2.6.2 dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -4976,6 +4994,16 @@ packages: engines: {node: '>= 0.4'} dev: false + /axios@1.4.0: + resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + dependencies: + follow-redirects: 1.15.5 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -5335,6 +5363,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /command-line-args@5.2.1: resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} engines: {node: '>=4.0.0'} @@ -5591,6 +5626,11 @@ packages: engines: {node: '>=10'} dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -6137,12 +6177,31 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true + /follow-redirects@1.15.5: + resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /formik@2.4.5(react@18.2.0): resolution: {integrity: sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==} peerDependencies: @@ -6973,6 +7032,18 @@ packages: brorand: 1.1.0 dev: false + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -7452,6 +7523,10 @@ packages: requiresBuild: true dev: false + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /public-encrypt@4.0.3: resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} dependencies: diff --git a/src/App.tsx b/src/App.tsx index 1a7328b..07ac0bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,21 +1,23 @@ -import { Card, Flex, H1, Input, P, TokenInput } from '@interlay/ui'; +import { Card, Flex, H1, Input, TokenInput } from '@interlay/ui'; import { Layout } from './components'; +import { GelatoRelay } from '@gelatonetwork/relay-sdk'; import { useForm } from '@interlay/hooks'; import { mergeProps } from '@react-aria/utils'; -import { useMutation } from '@tanstack/react-query'; -import { Key, useEffect, useState } from 'react'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { Key, useState } from 'react'; +import { encodeFunctionData } from 'viem'; +import { useAccount } from 'wagmi'; import { StyledWrapper } from './App.style'; import { AuthCTA } from './components/AuthCTA'; +import { L2_CHAIN_ID } from './config'; import { ContractType, CurrencyTicker, Erc20CurrencyTicker } from './constants'; -import { useBalances } from './hooks/useBalances'; +import { useContract } from './hooks/useContract'; +import { useEthersSigner } from './hooks/useEthersSigner'; +import { HexString } from './types'; +import { toAtomicAmount, toBaseAmount } from './utils/currencies'; import { isFormDisabled } from './utils/validation'; import './utils/yup.custom'; -import { useAccountAbstraction, BigNumberish } from './aa'; -import { encodeFunctionData } from 'viem'; -import { HexString } from './types'; -import { toAtomicAmount } from './utils/currencies'; -import { useContract } from './hooks/useContract'; type TransferForm = { amount: string; @@ -23,59 +25,96 @@ type TransferForm = { address: string; }; +const relay = new GelatoRelay({}); + function App() { - const { client } = useAccountAbstraction(); + // const { client } = useAccountAbstraction(); + const { address } = useAccount(); const [ticker, setTicker] = useState(Erc20CurrencyTicker.WBTC); - const { balances, getBalance, refetch } = useBalances(); + const l1Signer = useEthersSigner({ chainId: L2_CHAIN_ID }); const contract = useContract(ContractType[Erc20CurrencyTicker.WBTC]); + const { data } = useQuery({ + queryKey: ['wbtc-balance', address], + enabled: !!address, + queryFn: async () => { + if (!address) return; + return contract.read.balanceOf([address!]); + } + }); + const mutation = useMutation({ mutationFn: async (form: TransferForm) => { - if (!client) { - return; - } - - let approvalUserOpNonce: BigNumberish | null = null; - // approve wbtc spending by paymaster contract - if (client.paymasterAddress && client.smartAccountAddress) { - const allowance = await contract.read.allowance([client.smartAccountAddress, client.paymasterAddress]); - - const uint256Max = BigInt(2 ** 256) - BigInt(1); - if (allowance < uint256Max) { - const approvalCallData = encodeFunctionData({ - abi: contract.abi, - functionName: 'approve', - args: [client.paymasterAddress as HexString, uint256Max] - }); - const approvalUserOp = await client.createUserOp({ - address: contract.address, - callData: approvalCallData, - value: 0 - }); - approvalUserOp.paymasterAndData = '0x'; - await client.signAndSendUserOp(approvalUserOp); - approvalUserOpNonce = await approvalUserOp.nonce; - } - } + if (!address) return; + + // let approvalUserOpNonce: BigNumberish | null = null; + // // approve wbtc spending by paymaster contract + // if (address && client.smartAccountAddress) { + // const allowance = await contract.read.allowance([address, client.paymasterAddress]); + + // const uint256Max = BigInt(2 ** 256) - BigInt(1); + // if (allowance < uint256Max) { + // const approvalCallData = encodeFunctionData({ + // abi: contract.abi, + // functionName: 'approve', + // args: [client.paymasterAddress as HexString, uint256Max] + // }); + // const approvalUserOp = await client.createUserOp({ + // address: contract.address, + // callData: approvalCallData, + // value: 0 + // }); + // approvalUserOp.paymasterAndData = '0x'; + // await client.signAndSendUserOp(approvalUserOp); + // approvalUserOpNonce = await approvalUserOp.nonce; + // } + // } + const atomicAmount = toAtomicAmount(form.amount, 'WBTC'); + + // return contract.write.transfer([form.address as HexString, atomicAmount]); // send userop + // const callData = encodeFunctionData({ + // abi: contract.abi, + // functionName: 'transfer', + // args: [form.address as HexString, atomicAmount] + // }); + const callData = encodeFunctionData({ abi: contract.abi, - functionName: 'transfer', - args: [form.address as HexString, atomicAmount] - }); - const userOp = await client.createUserOp({ - address: contract.address, - callData, - value: 0, - nonce: approvalUserOpNonce ? parseInt(approvalUserOpNonce.toString()) + 1 : undefined + functionName: 'mint', + args: [atomicAmount] }); + // const userOp = await client.createUserOp({ + // address: contract.address, + // callData, + // value: 0, + // nonce: approvalUserOpNonce ? parseInt(approvalUserOpNonce.toString()) + 1 : undefined + // }); + + const request = { + chainId: 123420111, // Goerli in this case + target: contract.address, // target contract address + data: callData!, // encoded transaction datas + user: await l1Signer?.getAddress() + }; + + const sponsorApiKey = import.meta.env.VITE_GELATO_API_KEY; + + const relayResponse = await relay.sponsoredCallERC2771( + request, + l1Signer, // new providers.Web3Provider(provider), + sponsorApiKey + ); + + const taskId = relayResponse.taskId; - const transferResult = await client?.signAndSendUserOp(userOp); - console.log(transferResult); + console.log(`https://relay.gelato.digital/tasks/status/${taskId}`); + + // const transferResult = await client?.signAndSendUserOp(userOp); + // console.log(transferResult); - refetch(); return; } }); @@ -84,8 +123,6 @@ function App() { mutation.mutate(values); }; - const balance = getBalance(ticker); - const form = useForm({ initialValues: { amount: '', @@ -96,13 +133,6 @@ function App() { hideErrors: 'untouched' }); - useEffect(() => { - if (!balances) return; - - form.validateForm(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [balances]); - const isSubmitDisabled = isFormDisabled(form); return ( @@ -112,30 +142,27 @@ function App() {

Transfer

-

- {client?.smartAccountAddress - ? `Using smart account ${client.smartAccountAddress}` - : 'Please connect with Metamask.'} -

+
{ const publicClient = usePublicClient(); - const { client } = useAccountAbstraction(); - // const { address } = useAccount(); + const { address } = useAccount(); // TODO: add transfer event listener and update balance on transfer in/out const { data, ...queryResult } = useQuery({ - queryKey: ['balances', client?.smartAccountAddress], - enabled: !!client?.smartAccountAddress && !!publicClient, + queryKey: ['balances', address], + enabled: !!address && !!publicClient, queryFn: async () => { const balancesMulticallResult = await publicClient.multicall({ contracts: Object.values(Erc20Currencies).map(({ address: erc20Address }) => ({ abi: ERC20Abi, address: erc20Address, functionName: 'balanceOf', - args: [client?.smartAccountAddress] + args: [address] })) }); diff --git a/src/hooks/useEthersSigner.ts b/src/hooks/useEthersSigner.ts new file mode 100644 index 0000000..b5807e0 --- /dev/null +++ b/src/hooks/useEthersSigner.ts @@ -0,0 +1,27 @@ +import { useMemo } from 'react'; +import { Web3Provider } from '@ethersproject/providers'; +import { WalletClient, useWalletClient } from 'wagmi'; + +function walletClientToSigner(walletClient: WalletClient) { + const { account, chain, transport } = walletClient; + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address + }; + const provider = new Web3Provider(transport, network); + const signer = provider.getSigner(account.address); + + return signer; +} + +type UseEthersSignerProps = { chainId?: number }; + +const useEthersSigner = ({ chainId }: UseEthersSignerProps = {}) => { + const { data: walletClient } = useWalletClient({ chainId }); + + return useMemo(() => (walletClient ? walletClientToSigner(walletClient) : undefined), [walletClient]); +}; + +export { walletClientToSigner, useEthersSigner }; +export type { UseEthersSignerProps }; diff --git a/src/main.tsx b/src/main.tsx index 1068a04..9added4 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,16 @@ import { InterlayUIProvider } from '@interlay/system'; import '@interlay/theme/dist/bob.css'; import { CSSReset } from '@interlay/ui'; +import '@rainbow-me/rainbowkit/styles.css'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { WagmiConfig } from 'wagmi'; import App from './App'; import { chains, config } from './connectors/wagmi-connectors'; import './index.css'; -import '@rainbow-me/rainbowkit/styles.css'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { AccountAbstractionProvider } from './aa'; import { RainbowKitProvider } from '@rainbow-me/rainbowkit'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const queryClient = new QueryClient(); @@ -19,14 +18,12 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - + + + + + +