feat: Smart Account relay connection + paymaster base (#351)
* feat: added Goerli smart accounts * chore: remove viem version modifier. Split useSmartAccount usage into multiliune * feat: fix error handling * feat: added smart wallets to proposal modal * feat: added paymasters to be able to freely transact on testnet with Pimlico sponsorship. Serialize SA addresses in relay response * feat: adapted SmartWalletLib interface to be able to use same methods as EIP155Lib. Hooked up all operations on EIP155RequestHandler to smart accounts. * chore: remove logs * feat: added spinner to modal footer. Use it when handling eip155 requests * feat: added sponsorship toggle in settings * feat: upgraded to permissionless@0.0.16 * fix: conflicts after merge * chore: unify loaders * chore: kristoph refactor + multi chain support * fix: chain Id issue. remove unused logs
This commit is contained in:
parent
46bfda7ecf
commit
9439c9af54
@ -0,0 +1,31 @@
|
||||
import useSmartAccount from '@/hooks/useSmartAccount'
|
||||
import { Hex } from 'viem'
|
||||
import ChainAddressMini from './ChainAddressMini'
|
||||
import { createOrRestoreEIP155Wallet, eip155Wallets } from '@/utils/EIP155WalletUtil'
|
||||
import { Spinner } from '@nextui-org/react'
|
||||
import { Chain, allowedChains } from '@/utils/SmartAccountUtils'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import SettingsStore from '@/store/SettingsStore'
|
||||
|
||||
interface Props {
|
||||
namespace: string
|
||||
}
|
||||
|
||||
const getKey = (namespace?: string) => {
|
||||
switch (namespace) {
|
||||
case 'eip155':
|
||||
createOrRestoreEIP155Wallet()
|
||||
const key = Object.values(eip155Wallets)[0]?.getPrivateKey() as Hex
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
export default function ChainSmartAddressMini({ namespace }: Props) {
|
||||
const { activeChainId } = useSnapshot(SettingsStore.state)
|
||||
const { address } = useSmartAccount(getKey(namespace) as `0x${string}`, allowedChains.find((c) => c.id.toString() === activeChainId) as Chain)
|
||||
|
||||
if (!address) return <Spinner />
|
||||
return (
|
||||
<ChainAddressMini address={address}/>
|
||||
)
|
||||
}
|
@ -11,7 +11,6 @@ interface Props {
|
||||
onReject: () => void
|
||||
infoBoxCondition?: boolean
|
||||
infoBoxText?: string
|
||||
disabledApprove?: boolean
|
||||
approveLoader?: LoaderProps
|
||||
rejectLoader?: LoaderProps
|
||||
}
|
||||
@ -23,7 +22,6 @@ export default function ModalFooter({
|
||||
rejectLoader,
|
||||
infoBoxCondition,
|
||||
infoBoxText,
|
||||
disabledApprove
|
||||
}: Props) {
|
||||
const { currentRequestVerifyContext } = useSnapshot(SettingsStore.state)
|
||||
const validation = currentRequestVerifyContext?.verified.validation
|
||||
@ -46,13 +44,14 @@ export default function ModalFooter({
|
||||
<span>{infoBoxText || ''}</span>
|
||||
</Row>
|
||||
)}
|
||||
<Row justify="space-between">
|
||||
<Row justify="space-between" align='center'>
|
||||
<Button
|
||||
auto
|
||||
flat
|
||||
style={{ color: 'white', backgroundColor: 'grey' }}
|
||||
onPress={onReject}
|
||||
data-testid="session-reject-button"
|
||||
disabled={rejectLoader?.active}
|
||||
>
|
||||
{rejectLoader && rejectLoader.active ? (
|
||||
<Loading size="md" type="points" color={rejectLoader.color || 'white'} />
|
||||
@ -64,7 +63,7 @@ export default function ModalFooter({
|
||||
auto
|
||||
flat
|
||||
color={approveButtonColor}
|
||||
disabled={disabledApprove}
|
||||
disabled={approveLoader?.active}
|
||||
onPress={onApprove}
|
||||
data-testid="session-approve-button"
|
||||
>
|
||||
|
@ -5,9 +5,10 @@ import { updateSignClientChainId } from '@/utils/WalletConnectUtil'
|
||||
import { Avatar, Button, Text, Tooltip, Loading } from '@nextui-org/react'
|
||||
import { eip155Wallets } from '@/utils/EIP155WalletUtil'
|
||||
import Image from 'next/image'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import useSmartAccount from '@/hooks/useSmartAccount'
|
||||
import { Chain, FAUCET_URLS, allowedChains } from '@/utils/SmartAccountUtils'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
@ -28,13 +29,14 @@ export default function SmartAccountCard({
|
||||
}: Props) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const { activeChainId } = useSnapshot(SettingsStore.state)
|
||||
const chain = allowedChains.find((c) => c.id.toString() === chainId.split(':')[1]) as Chain
|
||||
const {
|
||||
deploy,
|
||||
isDeployed,
|
||||
address: smartAccountAddress,
|
||||
loading,
|
||||
sendTestTransaction,
|
||||
} = useSmartAccount(eip155Wallets[address].getPrivateKey() as `0x${string}`)
|
||||
} = useSmartAccount(eip155Wallets[address].getPrivateKey() as `0x${string}`, chain)
|
||||
|
||||
function onCopy() {
|
||||
navigator?.clipboard?.writeText(address)
|
||||
@ -57,8 +59,6 @@ export default function SmartAccountCard({
|
||||
}
|
||||
}
|
||||
|
||||
const getFaucetUrl = () => `https://${name?.toLowerCase()?.replace('ethereum', '')?.trim()}faucet.com`
|
||||
|
||||
return (
|
||||
<ChainCard rgb={rgb} flexDirection="row" alignItems="center" flexWrap="wrap">
|
||||
<Avatar src={logo} />
|
||||
@ -124,7 +124,7 @@ export default function SmartAccountCard({
|
||||
disabled={!isActiveChain || loading}
|
||||
size="sm"
|
||||
css={{ marginTop: 20, width: '100%' }}
|
||||
onClick={() => window.open(getFaucetUrl(), '_blank')}
|
||||
onClick={() => window.open(FAUCET_URLS[chain?.name], '_blank')}
|
||||
>
|
||||
{name} Faucet
|
||||
</Button>
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
export type TEIP155Chain = keyof typeof EIP155_CHAINS
|
||||
|
||||
export type EIP155TestChain = {
|
||||
export type EIP155Chain = {
|
||||
chainId: number
|
||||
name: string
|
||||
logo: string
|
||||
@ -21,7 +21,7 @@ export type EIP155TestChain = {
|
||||
/**
|
||||
* Chains
|
||||
*/
|
||||
export const EIP155_MAINNET_CHAINS = {
|
||||
export const EIP155_MAINNET_CHAINS: Record<string, EIP155Chain> = {
|
||||
'eip155:1': {
|
||||
chainId: 1,
|
||||
name: 'Ethereum',
|
||||
@ -64,7 +64,7 @@ export const EIP155_MAINNET_CHAINS = {
|
||||
}
|
||||
}
|
||||
|
||||
export const EIP155_TEST_CHAINS: Record<string,EIP155TestChain> = {
|
||||
export const EIP155_TEST_CHAINS: Record<string,EIP155Chain> = {
|
||||
'eip155:5': {
|
||||
chainId: 5,
|
||||
name: 'Ethereum Goerli',
|
||||
@ -81,6 +81,7 @@ export const EIP155_TEST_CHAINS: Record<string,EIP155TestChain> = {
|
||||
rgb: '99, 125, 234',
|
||||
rpc: 'https://rpc.sepolia.org',
|
||||
namespace: 'eip155',
|
||||
smartAccountEnabled: true,
|
||||
},
|
||||
'eip155:43113': {
|
||||
chainId: 43113,
|
||||
@ -96,7 +97,8 @@ export const EIP155_TEST_CHAINS: Record<string,EIP155TestChain> = {
|
||||
logo: '/chain-logos/eip155-137.png',
|
||||
rgb: '130, 71, 229',
|
||||
rpc: 'https://matic-mumbai.chainstacklabs.com',
|
||||
namespace: 'eip155'
|
||||
namespace: 'eip155',
|
||||
smartAccountEnabled: true,
|
||||
},
|
||||
'eip155:420': {
|
||||
chainId: 420,
|
||||
|
@ -49,7 +49,7 @@ export default function useInitialization() {
|
||||
// restart transport if relayer region changes
|
||||
const onRelayerRegionChange = useCallback(() => {
|
||||
try {
|
||||
web3wallet.core.relayer.restartTransport(relayerRegionURL)
|
||||
web3wallet?.core?.relayer.restartTransport(relayerRegionURL)
|
||||
prevRelayerURLValue.current = relayerRegionURL
|
||||
} catch (err: unknown) {
|
||||
alert(err)
|
||||
|
@ -1,18 +1,24 @@
|
||||
import { SmartAccountLib } from "@/lib/SmartAccountLib";
|
||||
import { styledToast } from "@/utils/HelperUtil";
|
||||
import SettingsStore from "@/store/SettingsStore";
|
||||
import { Chain, VITALIK_ADDRESS } from "@/utils/SmartAccountUtils";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { Hex } from "viem";
|
||||
import { styledToast } from "@/utils/HelperUtil";
|
||||
import { TransactionExecutionError } from "viem";
|
||||
|
||||
export default function useSmartAccount(signerPrivateKey: `0x${string}`) {
|
||||
export default function useSmartAccount(signerPrivateKey: Hex, chain: Chain) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [client, setClient] = useState<SmartAccountLib>();
|
||||
const [isDeployed, setIsDeployed] = useState(false)
|
||||
const [address, setAddress] = useState<`0x${string}`>()
|
||||
const [address, setAddress] = useState<Hex>()
|
||||
const { smartAccountSponsorshipEnabled } = useSnapshot(SettingsStore.state);
|
||||
|
||||
const execute = useCallback(async (callback: () => void) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
await callback()
|
||||
const res = await callback()
|
||||
console.log('result:', res)
|
||||
setLoading(false)
|
||||
}
|
||||
catch (e) {
|
||||
@ -34,13 +40,22 @@ export default function useSmartAccount(signerPrivateKey: `0x${string}`) {
|
||||
|
||||
const sendTestTransaction = useCallback(async () => {
|
||||
if (!client) return
|
||||
execute(client?.sendTestTransaction)
|
||||
execute(() => client?.sendTransaction({
|
||||
to: VITALIK_ADDRESS,
|
||||
value: 0n,
|
||||
data: '0x',
|
||||
}))
|
||||
}, [client, execute])
|
||||
|
||||
useEffect(() => {
|
||||
const smartAccountClient = new SmartAccountLib(signerPrivateKey, 'goerli')
|
||||
if (!signerPrivateKey || !chain) return
|
||||
const smartAccountClient = new SmartAccountLib({
|
||||
chain,
|
||||
privateKey: signerPrivateKey,
|
||||
sponsored: smartAccountSponsorshipEnabled,
|
||||
})
|
||||
setClient(smartAccountClient)
|
||||
}, [signerPrivateKey])
|
||||
}, [signerPrivateKey, smartAccountSponsorshipEnabled, chain])
|
||||
|
||||
useEffect(() => {
|
||||
client?.checkIfSmartAccountDeployed()
|
||||
@ -48,7 +63,7 @@ export default function useSmartAccount(signerPrivateKey: `0x${string}`) {
|
||||
setIsDeployed(deployed)
|
||||
setAddress(client?.address)
|
||||
})
|
||||
}, [client])
|
||||
}, [client, chain])
|
||||
|
||||
|
||||
return {
|
||||
|
@ -117,7 +117,7 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
|
||||
* Set up WalletConnect event listeners
|
||||
*****************************************************************************/
|
||||
useEffect(() => {
|
||||
if (initialized) {
|
||||
if (initialized && web3wallet) {
|
||||
//sign
|
||||
web3wallet.on('session_proposal', onSessionProposal)
|
||||
web3wallet.on('session_request', onSessionRequest)
|
||||
|
@ -39,7 +39,7 @@ export default class EIP155Lib {
|
||||
return this.wallet.signMessage(message)
|
||||
}
|
||||
|
||||
_signTypedData(domain: any, types: any, data: any) {
|
||||
_signTypedData(domain: any, types: any, data: any, _primaryType?: string) {
|
||||
return this.wallet._signTypedData(domain, types, data)
|
||||
}
|
||||
|
||||
|
@ -1,134 +1,267 @@
|
||||
import { createSmartAccountClient } from 'permissionless'
|
||||
import { BundlerActions, BundlerClient, bundlerActions, createSmartAccountClient, getAccountNonce } from 'permissionless'
|
||||
import { privateKeyToSafeSmartAccount } from 'permissionless/accounts'
|
||||
import * as chains from 'viem/chains'
|
||||
import { privateKeyToAccount } from 'viem/accounts'
|
||||
import { type Chain, createWalletClient, formatEther, createPublicClient, http } from 'viem'
|
||||
import { createPimlicoBundlerClient } from 'permissionless/clients/pimlico'
|
||||
import { createWalletClient, formatEther, createPublicClient, http, Address, Hex, PublicClient, createClient, WalletClient } from 'viem'
|
||||
import { PimlicoPaymasterClient, createPimlicoPaymasterClient } from 'permissionless/clients/pimlico'
|
||||
import { UserOperation } from 'permissionless/types'
|
||||
import { providers } from 'ethers'
|
||||
import { PimlicoBundlerActions, pimlicoBundlerActions } from 'permissionless/actions/pimlico'
|
||||
import { Chain, ENTRYPOINT_ADDRESSES, PAYMASTER_ADDRESSES, USDC_ADDRESSES, VITALIK_ADDRESS, approveUSDCSpendCallData, bundlerUrl, paymasterUrl, publicRPCUrl } from '@/utils/SmartAccountUtils'
|
||||
|
||||
export type SmartAccountEnabledChains = 'sepolia' | 'goerli'
|
||||
type SmartAccountLibOptions = {
|
||||
privateKey: `0x${string}`
|
||||
chain: Chain
|
||||
sponsored?: boolean
|
||||
};
|
||||
|
||||
// -- Helpers -----------------------------------------------------------------
|
||||
const bundlerApiKey = process.env.NEXT_PUBLIC_PIMLICO_KEY
|
||||
const pimlicoApiKey = process.env.NEXT_PUBLIC_PIMLICO_KEY
|
||||
const projectId = process.env.NEXT_PUBLIC_PROJECT_ID
|
||||
|
||||
// -- Sdk ---------------------------------------------------------------------
|
||||
export class SmartAccountLib {
|
||||
public chain: Chain
|
||||
private bundlerApiKey: string
|
||||
#signerPrivateKey: `0x${string}`;
|
||||
public isDeployed: boolean = false;
|
||||
public address?: `0x${string}`;
|
||||
public sponsored: boolean = true;
|
||||
|
||||
public constructor(privateKey: `0x${string}`, chain: SmartAccountEnabledChains = 'goerli') {
|
||||
if (!bundlerApiKey) {
|
||||
throw new Error('Missing required data in SmartAccountSdk')
|
||||
private publicClient: PublicClient
|
||||
private paymasterClient: PimlicoPaymasterClient
|
||||
private bundlerClient: BundlerClient & BundlerActions & PimlicoBundlerActions
|
||||
private signerClient: WalletClient
|
||||
|
||||
#signerPrivateKey: `0x${string}`;
|
||||
|
||||
|
||||
public constructor({ privateKey, chain, sponsored = true }: SmartAccountLibOptions) {
|
||||
if (!pimlicoApiKey) {
|
||||
throw new Error('A Pimlico API Key is required')
|
||||
}
|
||||
this.bundlerApiKey = bundlerApiKey
|
||||
this.chain = chains[chain] as Chain
|
||||
|
||||
this.chain = chain
|
||||
this.sponsored = sponsored
|
||||
this.#signerPrivateKey = privateKey
|
||||
}
|
||||
this.publicClient = createPublicClient({
|
||||
transport: http(publicRPCUrl({ chain: this.chain }))
|
||||
})
|
||||
|
||||
// -- Public ------------------------------------------------------------------
|
||||
this.paymasterClient = createPimlicoPaymasterClient({
|
||||
transport: http(paymasterUrl({ chain: this.chain }))
|
||||
})
|
||||
|
||||
this.bundlerClient = createClient({
|
||||
transport: http(bundlerUrl({ chain: this.chain })),
|
||||
chain: this.chain
|
||||
})
|
||||
.extend(bundlerActions)
|
||||
.extend(pimlicoBundlerActions)
|
||||
|
||||
this.signerClient = createWalletClient({
|
||||
account: privateKeyToAccount(this.#signerPrivateKey),
|
||||
chain: this.chain,
|
||||
transport: http(publicRPCUrl({ chain: this.chain }))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -- Private -----------------------------------------------------------------
|
||||
private getWalletConnectTransport = () => http(
|
||||
`https://rpc.walletconnect.com/v1/?chainId=EIP155:${this.chain.id}&projectId=${projectId}`,
|
||||
{ retryDelay: 1000 }
|
||||
);
|
||||
|
||||
private getBundlerTransport = () => http(
|
||||
`https://api.pimlico.io/v1/${this.chain.name.toLowerCase()}/rpc?apikey=${this.bundlerApiKey}`,
|
||||
{ retryDelay: 1000 }
|
||||
);
|
||||
|
||||
|
||||
private getBundlerClient = () => createPimlicoBundlerClient({
|
||||
private getSmartAccountClient = async (
|
||||
sponsorUserOperation?: (args: {
|
||||
userOperation: UserOperation
|
||||
entryPoint: Address
|
||||
}) => Promise<UserOperation>
|
||||
) => {
|
||||
const account = await this.getAccount()
|
||||
return createSmartAccountClient({
|
||||
account,
|
||||
chain: this.chain,
|
||||
transport: this.getBundlerTransport()
|
||||
})
|
||||
|
||||
private getPublicClient = () => createPublicClient({
|
||||
chain: this.chain,
|
||||
transport: this.getWalletConnectTransport()
|
||||
})
|
||||
|
||||
private getSignerClient = () => {
|
||||
const signerAccount = privateKeyToAccount(this.#signerPrivateKey)
|
||||
return createWalletClient({
|
||||
account: signerAccount,
|
||||
chain: this.chain,
|
||||
transport: this.getWalletConnectTransport()
|
||||
})
|
||||
transport: http(bundlerUrl({ chain: this.chain })),
|
||||
sponsorUserOperation: sponsorUserOperation
|
||||
? sponsorUserOperation
|
||||
: this.sponsored ? this.paymasterClient.sponsorUserOperation : undefined
|
||||
}).extend(pimlicoBundlerActions)
|
||||
}
|
||||
|
||||
private getSmartAccountClient = async () => {
|
||||
const smartAccount = await privateKeyToSafeSmartAccount(this.getPublicClient(), {
|
||||
privateKey: this.#signerPrivateKey,
|
||||
safeVersion: '1.4.1',
|
||||
entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
|
||||
})
|
||||
|
||||
return createSmartAccountClient({
|
||||
account: smartAccount,
|
||||
chain: this.chain,
|
||||
transport: this.getBundlerTransport()
|
||||
public getNonce = async () => {
|
||||
const smartAccountClient = await this.getSmartAccountClient()
|
||||
return getAccountNonce(this.publicClient, {
|
||||
sender: smartAccountClient.account.address as Hex,
|
||||
entryPoint: ENTRYPOINT_ADDRESSES[this.chain.name]
|
||||
})
|
||||
}
|
||||
|
||||
private prefundSmartAccount = async (address: `0x${string}`) => {
|
||||
const signerAccountViemClient = this.getSignerClient();
|
||||
const publicClient = this.getPublicClient();
|
||||
const bundlerClient = this.getBundlerClient();
|
||||
const smartAccountBalance = await publicClient.getBalance({ address })
|
||||
if (this.sponsored) {
|
||||
return
|
||||
}
|
||||
|
||||
const smartAccountBalance = await this.publicClient.getBalance({ address })
|
||||
|
||||
console.log(`Smart Account Balance: ${formatEther(smartAccountBalance)} ETH`)
|
||||
if (smartAccountBalance < 1n) {
|
||||
console.log(`Smart Account has no balance. Starting prefund`)
|
||||
const { fast: fastPrefund } = await bundlerClient.getUserOperationGasPrice()
|
||||
const prefundHash = await signerAccountViemClient.sendTransaction({
|
||||
const { fast: fastPrefund } = await this.bundlerClient.getUserOperationGasPrice()
|
||||
const prefundHash = await this.signerClient.sendTransaction({
|
||||
to: address,
|
||||
chain: this.chain,
|
||||
account: this.signerClient.account!,
|
||||
value: 10000000000000000n,
|
||||
maxFeePerGas: fastPrefund.maxFeePerGas,
|
||||
maxPriorityFeePerGas: fastPrefund.maxPriorityFeePerGas
|
||||
})
|
||||
|
||||
await publicClient.waitForTransactionReceipt({ hash: prefundHash })
|
||||
await this.publicClient.waitForTransactionReceipt({ hash: prefundHash })
|
||||
console.log(`Prefunding Success`)
|
||||
|
||||
const newSmartAccountBalance = await publicClient.getBalance({ address })
|
||||
const newSmartAccountBalance = await this.publicClient.getBalance({ address })
|
||||
console.log(
|
||||
`Smart Account Balance: ${formatEther(newSmartAccountBalance)} ETH`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// By default first transaction will deploy the smart contract if it hasn't been deployed yet
|
||||
public sendTestTransaction = async () => {
|
||||
const publicClient = this.getPublicClient();
|
||||
const bundlerClient = this.getBundlerClient();
|
||||
const smartAccountClient = await this.getSmartAccountClient();
|
||||
const { fast: testGas, } = await bundlerClient.getUserOperationGasPrice()
|
||||
|
||||
const testHash = await smartAccountClient.sendTransaction({
|
||||
to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' as `0x${string}`,
|
||||
value: 0n,
|
||||
maxFeePerGas: testGas.maxFeePerGas,
|
||||
maxPriorityFeePerGas: testGas.maxPriorityFeePerGas,
|
||||
})
|
||||
|
||||
console.log(`Sending Test Transaction With Hash: ${testHash}`)
|
||||
|
||||
await publicClient.waitForTransactionReceipt({ hash: testHash })
|
||||
console.log(`Test Transaction Success`)
|
||||
private getSmartAccountUSDCBalance = async () => {
|
||||
const params = {
|
||||
abi: [
|
||||
{
|
||||
inputs: [{ name: "_owner", type: "address" }],
|
||||
name: "balanceOf",
|
||||
outputs: [{ name: "balance", type: "uint256" }],
|
||||
type: "function",
|
||||
}
|
||||
],
|
||||
address: USDC_ADDRESSES[this.chain.name] as Hex,
|
||||
functionName: "balanceOf",
|
||||
args: [this.address!]
|
||||
}
|
||||
const usdcBalance = await this.publicClient.readContract(params) as bigint
|
||||
return usdcBalance
|
||||
}
|
||||
|
||||
private sponsorUserOperation = async ({ userOperation }: { userOperation: UserOperation }) => {
|
||||
const userOperationWithPaymasterAndData = {
|
||||
...userOperation,
|
||||
paymasterAndData: PAYMASTER_ADDRESSES[this.chain.name]
|
||||
}
|
||||
|
||||
console.log('Estimating gas limits...', userOperationWithPaymasterAndData)
|
||||
|
||||
const gasLimits = await this.bundlerClient.estimateUserOperationGas({
|
||||
userOperation: userOperationWithPaymasterAndData,
|
||||
entryPoint: ENTRYPOINT_ADDRESSES[this.chain.name]
|
||||
})
|
||||
|
||||
return {
|
||||
...userOperationWithPaymasterAndData,
|
||||
callGasLimit: gasLimits.callGasLimit,
|
||||
verificationGasLimit: gasLimits.verificationGasLimit,
|
||||
preVerificationGas: gasLimits.preVerificationGas
|
||||
}
|
||||
}
|
||||
|
||||
// -- Public ------------------------------------------------------------------
|
||||
public getAccount = async () =>
|
||||
privateKeyToSafeSmartAccount(this.publicClient, {
|
||||
privateKey: this.#signerPrivateKey,
|
||||
safeVersion: '1.4.1', // simple version
|
||||
entryPoint: ENTRYPOINT_ADDRESSES[this.chain.name], // global entrypoint
|
||||
setupTransactions: [
|
||||
{
|
||||
to: USDC_ADDRESSES[this.chain.name],
|
||||
value: 0n,
|
||||
data: approveUSDCSpendCallData({
|
||||
to: PAYMASTER_ADDRESSES[this.chain.name],
|
||||
amount: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
|
||||
})
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
static isSmartAccount = async (address: Address, chain: Chain) => {
|
||||
const client = createPublicClient({
|
||||
chain,
|
||||
transport: http(
|
||||
`https://rpc.walletconnect.com/v1/?chainId=EIP155:${chains.goerli.id}&projectId=${projectId}`,
|
||||
{ retryDelay: 1000 }
|
||||
)
|
||||
})
|
||||
const bytecode = await client.getBytecode({ address })
|
||||
return Boolean(bytecode)
|
||||
}
|
||||
|
||||
public sendTransaction = async ({
|
||||
to,
|
||||
value,
|
||||
data
|
||||
}: { to: Address; value: bigint; data: Hex }) => {
|
||||
console.log(`Sending Transaction to ${to} with value ${value.toString()} and data ${data}`)
|
||||
const smartAccountClient = await this.getSmartAccountClient()
|
||||
const gasPrices = await smartAccountClient.getUserOperationGasPrice()
|
||||
return smartAccountClient.sendTransaction({
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
maxFeePerGas: gasPrices.fast.maxFeePerGas,
|
||||
maxPriorityFeePerGas: gasPrices.fast.maxPriorityFeePerGas
|
||||
})
|
||||
}
|
||||
|
||||
public signMessage = async (message: string) => {
|
||||
const client = await this.getSmartAccountClient()
|
||||
return client.signMessage({ message })
|
||||
}
|
||||
public _signTypedData = async (domain: any, types: any, data: any, primaryType: any) => {
|
||||
const client = await this.getSmartAccountClient()
|
||||
return client.signTypedData({ account: client.account, domain, types, primaryType, message: data })
|
||||
}
|
||||
|
||||
public connect = async (_provider: providers.JsonRpcProvider) => {
|
||||
return this.getSmartAccountClient()
|
||||
}
|
||||
|
||||
public signTransaction = async (transaction: any) => {
|
||||
const smartAccountClient = await this.getSmartAccountClient()
|
||||
return smartAccountClient.account.signTransaction(transaction)
|
||||
}
|
||||
|
||||
public sendUSDCSponsoredTransaction = async ({
|
||||
to,
|
||||
value,
|
||||
data
|
||||
}: { to: Address; value: bigint; data: Hex }) => {
|
||||
// 1. Check USDC Balance on smart account
|
||||
const usdcBalance = await this.getSmartAccountUSDCBalance()
|
||||
|
||||
if (usdcBalance < 1_000_000n) {
|
||||
throw new Error(
|
||||
`insufficient USDC balance for counterfactual wallet address ${this.address}: ${
|
||||
Number(usdcBalance) / 1000000
|
||||
} USDC, required at least 1 USDC`
|
||||
)
|
||||
}
|
||||
|
||||
const smartAccountClient = await this.getSmartAccountClient(this.sponsorUserOperation)
|
||||
const gasPrices = await smartAccountClient.getUserOperationGasPrice()
|
||||
|
||||
return smartAccountClient.sendTransaction({
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
maxFeePerGas: gasPrices.fast.maxFeePerGas,
|
||||
maxPriorityFeePerGas: gasPrices.fast.maxPriorityFeePerGas
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public checkIfSmartAccountDeployed = async () => {
|
||||
console.log('checking if deployed')
|
||||
const smartAccountClient = await this.getSmartAccountClient();
|
||||
const publicClient = this.getPublicClient();
|
||||
const bytecode = await publicClient.getBytecode({ address: smartAccountClient.account.address })
|
||||
console.log('Checking if deployed', smartAccountClient.account.address, this.chain.name)
|
||||
|
||||
const bytecode = await this.publicClient.getBytecode({ address: smartAccountClient.account.address })
|
||||
this.isDeployed = Boolean(bytecode)
|
||||
|
||||
console.log(`Smart Account Deployed: ${this.isDeployed}`)
|
||||
if (this.isDeployed) {
|
||||
this.address = smartAccountClient.account.address
|
||||
@ -145,7 +278,11 @@ export class SmartAccountLib {
|
||||
await this.prefundSmartAccount(smartAccountClient.account.address)
|
||||
|
||||
// Step 4: Create account by sending test tx
|
||||
await this.sendTestTransaction()
|
||||
await this.sendTransaction({
|
||||
to: VITALIK_ADDRESS,
|
||||
value: 0n,
|
||||
data: '0x'
|
||||
})
|
||||
await this.checkIfSmartAccountDeployed()
|
||||
console.log(`Account Created`)
|
||||
}
|
||||
|
@ -20,11 +20,11 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
useWalletConnectEventsManager(initialized)
|
||||
useEffect(() => {
|
||||
if (!initialized) return
|
||||
web3wallet.core.relayer.on(RELAYER_EVENTS.connect, () => {
|
||||
web3wallet?.core.relayer.on(RELAYER_EVENTS.connect, () => {
|
||||
styledToast('Network connection is restored!', 'success')
|
||||
})
|
||||
|
||||
web3wallet.core.relayer.on(RELAYER_EVENTS.disconnect, () => {
|
||||
web3wallet?.core.relayer.on(RELAYER_EVENTS.disconnect, () => {
|
||||
styledToast('Network connection lost.', 'error')
|
||||
})
|
||||
}, [initialized])
|
||||
|
@ -16,6 +16,7 @@ import { tezosWallets } from '@/utils/TezosWalletUtil'
|
||||
export default function SettingsPage() {
|
||||
const {
|
||||
testNets,
|
||||
smartAccountSponsorshipEnabled,
|
||||
eip155Address,
|
||||
cosmosAddress,
|
||||
solanaAddress,
|
||||
@ -53,6 +54,20 @@ export default function SettingsPage() {
|
||||
|
||||
<Divider y={2} />
|
||||
|
||||
<Text h4 css={{ marginBottom: '$5' }}>
|
||||
Smart Account Sponsorship (Pimlico)
|
||||
</Text>
|
||||
<Row justify="space-between" align="center">
|
||||
<Switch
|
||||
checked={smartAccountSponsorshipEnabled}
|
||||
onChange={SettingsStore.toggleSmartAccountSponsorship}
|
||||
data-testid="settings-toggle-smart-account-sponsorship"
|
||||
/>
|
||||
<Text>{smartAccountSponsorshipEnabled ? 'Enabled' : 'Disabled'}</Text>
|
||||
</Row>
|
||||
|
||||
<Divider y={2} />
|
||||
|
||||
<Row justify="space-between" align="center">
|
||||
<Text h4 css={{ marginBottom: '$5' }}>
|
||||
Relayer Region
|
||||
|
@ -20,6 +20,7 @@ interface State {
|
||||
activeChainId: string
|
||||
currentRequestVerifyContext?: Verify.Context
|
||||
sessions: SessionTypes.Struct[]
|
||||
smartAccountSponsorshipEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,7 +40,8 @@ const state = proxy<State>({
|
||||
tezosAddress: '',
|
||||
kadenaAddress: '',
|
||||
relayerRegionURL: '',
|
||||
sessions: []
|
||||
sessions: [],
|
||||
smartAccountSponsorshipEnabled: false,
|
||||
})
|
||||
|
||||
/**
|
||||
@ -103,10 +105,17 @@ const SettingsStore = {
|
||||
toggleTestNets() {
|
||||
state.testNets = !state.testNets
|
||||
if (state.testNets) {
|
||||
state.smartAccountSponsorshipEnabled = true
|
||||
localStorage.setItem('TEST_NETS', 'YES')
|
||||
} else {
|
||||
state.smartAccountSponsorshipEnabled = false
|
||||
localStorage.removeItem('TEST_NETS')
|
||||
}
|
||||
},
|
||||
|
||||
toggleSmartAccountSponsorship() {
|
||||
if (!state.testNets) return
|
||||
state.smartAccountSponsorshipEnabled = !state.smartAccountSponsorshipEnabled
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { EIP155_CHAINS, EIP155_SIGNING_METHODS, TEIP155Chain } from '@/data/EIP155Data'
|
||||
import EIP155Lib from '@/lib/EIP155Lib'
|
||||
import { SmartAccountLib } from '@/lib/SmartAccountLib'
|
||||
import { eip155Addresses, eip155Wallets } from '@/utils/EIP155WalletUtil'
|
||||
import {
|
||||
getSignParamsMessage,
|
||||
@ -9,11 +11,41 @@ import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
|
||||
import { SignClientTypes } from '@walletconnect/types'
|
||||
import { getSdkError } from '@walletconnect/utils'
|
||||
import { providers } from 'ethers'
|
||||
import { Hex } from 'viem'
|
||||
import { allowedChains } from './SmartAccountUtils'
|
||||
type RequestEventArgs = Omit<SignClientTypes.EventArguments['session_request'], 'verifyContext'>
|
||||
|
||||
|
||||
const getWallet = async (params: any) => {
|
||||
const requestParams: Array<any> = params?.request?.params || []
|
||||
const eoaWallet = eip155Wallets[getWalletAddressFromParams(eip155Addresses, params)]
|
||||
if (eoaWallet) {
|
||||
return eoaWallet
|
||||
}
|
||||
|
||||
const deployedSmartAccounts = await Promise.all(Object.values(eip155Wallets).map(async (wallet) => {
|
||||
const smartAccount = new SmartAccountLib({
|
||||
privateKey: wallet.getPrivateKey() as Hex,
|
||||
chain: allowedChains[0], // TODO: FIX FOR MULTI NETWORK
|
||||
sponsored: true, // TODO: Sponsor for now but should be dynamic according to SettingsStore
|
||||
})
|
||||
const isDeployed = await smartAccount.checkIfSmartAccountDeployed()
|
||||
if (isDeployed) {
|
||||
return smartAccount
|
||||
}
|
||||
return null
|
||||
}));
|
||||
const validSmartAccounts = deployedSmartAccounts.filter(Boolean) as Array<SmartAccountLib>
|
||||
const smartAccountAddress = getWalletAddressFromParams(validSmartAccounts.map(acc => acc.address!), params)
|
||||
|
||||
return validSmartAccounts.find((smartAccount) => smartAccount?.address === smartAccountAddress) as SmartAccountLib
|
||||
}
|
||||
|
||||
|
||||
export async function approveEIP155Request(requestEvent: RequestEventArgs) {
|
||||
const { params, id } = requestEvent
|
||||
const { chainId, request } = params
|
||||
const wallet = eip155Wallets[getWalletAddressFromParams(eip155Addresses, params)]
|
||||
const wallet = await getWallet(params)
|
||||
|
||||
switch (request.method) {
|
||||
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
||||
@ -32,10 +64,10 @@ export async function approveEIP155Request(requestEvent: RequestEventArgs) {
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3:
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4:
|
||||
try {
|
||||
const { domain, types, message: data } = getSignTypedDataParamsData(request.params)
|
||||
const { domain, types, message: data, primaryType } = getSignTypedDataParamsData(request.params)
|
||||
// https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471
|
||||
delete types.EIP712Domain
|
||||
const signedData = await wallet._signTypedData(domain, types, data)
|
||||
const signedData = await wallet._signTypedData(domain, types, data, primaryType)
|
||||
return formatJsonRpcResult(id, signedData)
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
@ -47,9 +79,10 @@ export async function approveEIP155Request(requestEvent: RequestEventArgs) {
|
||||
try {
|
||||
const provider = new providers.JsonRpcProvider(EIP155_CHAINS[chainId as TEIP155Chain].rpc)
|
||||
const sendTransaction = request.params[0]
|
||||
const connectedWallet = wallet.connect(provider)
|
||||
const { hash } = await connectedWallet.sendTransaction(sendTransaction)
|
||||
return formatJsonRpcResult(id, hash)
|
||||
const connectedWallet = await wallet.connect(provider)
|
||||
const hash = await connectedWallet.sendTransaction(sendTransaction)
|
||||
const receipt = typeof hash === 'string' ? hash : hash?.hash // TODO improve interface
|
||||
return formatJsonRpcResult(id, receipt)
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
alert(error.message)
|
||||
|
140
advanced/wallets/react-wallet-v2/src/utils/SmartAccountUtils.ts
Normal file
140
advanced/wallets/react-wallet-v2/src/utils/SmartAccountUtils.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { Hex, createPublicClient, encodeFunctionData, http } from "viem"
|
||||
import { goerli, polygonMumbai, sepolia } from 'viem/chains'
|
||||
|
||||
const apiKey = process.env.NEXT_PUBLIC_PIMLICO_KEY
|
||||
|
||||
// Types
|
||||
export const allowedChains = [sepolia, polygonMumbai, goerli] as const
|
||||
export type Chain = (typeof allowedChains)[number]
|
||||
export type UrlConfig = {
|
||||
chain: Chain
|
||||
}
|
||||
|
||||
// Entrypoints [I think this is constant but JIC]
|
||||
export const ENTRYPOINT_ADDRESSES: Record<Chain['name'], Hex> = {
|
||||
Sepolia: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
|
||||
'Polygon Mumbai': '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
|
||||
'Goerli': '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
|
||||
}
|
||||
|
||||
// Paymasters
|
||||
// https://docs.pimlico.io/paymaster/erc20-paymaster/contract-addresses
|
||||
export const PAYMASTER_ADDRESSES: Record<Chain['name'], Hex> = {
|
||||
Sepolia: '0x0000000000325602a77416A16136FDafd04b299f',
|
||||
'Polygon Mumbai': '0x000000000009B901DeC1aaB9389285965F49D387',
|
||||
Goerli: '0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009'
|
||||
}
|
||||
|
||||
// USDC
|
||||
export const USDC_ADDRESSES: Record<Chain['name'], Hex> = {
|
||||
Sepolia: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
|
||||
'Polygon Mumbai': '0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97',
|
||||
Goerli: '0x07865c6e87b9f70255377e024ace6630c1eaa37f'
|
||||
}
|
||||
|
||||
// RPC URLs
|
||||
export const RPC_URLS: Record<Chain['name'], string> = {
|
||||
Sepolia: 'https://rpc.ankr.com/eth_sepolia',
|
||||
'Polygon Mumbai': 'https://mumbai.rpc.thirdweb.com',
|
||||
Goerli: 'https://ethereum-goerli.publicnode.com'
|
||||
}
|
||||
|
||||
// Pimlico RPC names
|
||||
export const PIMLICO_NETWORK_NAMES: Record<Chain['name'], string> = {
|
||||
Sepolia: 'sepolia',
|
||||
'Polygon Mumbai': 'mumbai',
|
||||
Goerli: 'goerli'
|
||||
}
|
||||
|
||||
export const FAUCET_URLS: Record<Chain['name'], string> = {
|
||||
Sepolia: 'https://sepoliafaucet.com',
|
||||
'Polygon Mumbai': 'https://faucet.polygon.technology',
|
||||
Goerli: 'https://goerlifaucet.com'
|
||||
}
|
||||
|
||||
export const USDC_FAUCET_URL = 'https://faucet.circle.com/'
|
||||
|
||||
export const VITALIK_ADDRESS = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' as Hex
|
||||
|
||||
export const publicRPCUrl = ({ chain }: UrlConfig) => RPC_URLS[chain.name]
|
||||
export const paymasterUrl = ({ chain }: UrlConfig) =>
|
||||
`https://api.pimlico.io/v2/${PIMLICO_NETWORK_NAMES[chain.name]}/rpc?apikey=${apiKey}`
|
||||
export const bundlerUrl = ({ chain }: UrlConfig) =>
|
||||
`https://api.pimlico.io/v1/${PIMLICO_NETWORK_NAMES[chain.name]}/rpc?apikey=${apiKey}`
|
||||
|
||||
|
||||
const publicClient = ({ chain }: UrlConfig) => createPublicClient({
|
||||
transport: http(publicRPCUrl({ chain })),
|
||||
})
|
||||
|
||||
export const approvePaymasterUSDCSpend = (chain: Chain) => {
|
||||
// Approve paymaster to spend USDC on our behalf
|
||||
const approveData = approveUSDCSpendCallData({
|
||||
to: PAYMASTER_ADDRESSES[chain.name],
|
||||
amount: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
|
||||
})
|
||||
|
||||
// GENERATE THE CALLDATA FOR USEROP TO SEND TO THE SMART ACCOUNT
|
||||
const dest = USDC_ADDRESSES[chain.name] // Execute tx in USDC contract
|
||||
const value = 0n
|
||||
const data = approveData // Execute approve call
|
||||
|
||||
return generateUserOperationExecuteCallData({ dest, value, data })
|
||||
}
|
||||
|
||||
export const approveUSDCSpendCallData = ({ to, amount }: { to: Hex, amount: bigint }) => {
|
||||
return encodeFunctionData({
|
||||
abi: [
|
||||
{
|
||||
inputs: [
|
||||
{ name: "_spender", type: "address" },
|
||||
{ name: "_value", type: "uint256" }
|
||||
],
|
||||
name: "approve",
|
||||
outputs: [{ name: "", type: "bool" }],
|
||||
payable: false,
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
}
|
||||
],
|
||||
args: [to, amount]
|
||||
})
|
||||
}
|
||||
|
||||
// Wraps the call data in the execute function in order to send via UserOperation
|
||||
export const generateUserOperationExecuteCallData = ({ dest, data, value }: { dest: Hex, data: Hex, value: bigint }) => {
|
||||
return encodeFunctionData({
|
||||
abi: [
|
||||
{
|
||||
inputs: [
|
||||
{ name: "dest", type: "address" },
|
||||
{ name: "value", type: "uint256" },
|
||||
{ name: "func", type: "bytes" }
|
||||
],
|
||||
name: "execute",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
}
|
||||
],
|
||||
args: [dest, value, data]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const getUSDCBalance = async ({ address, chain }: { address: Hex, chain: Chain }) => {
|
||||
return publicClient({ chain }).readContract({
|
||||
abi: [
|
||||
{
|
||||
inputs: [{ name: "_owner", type: "address" }],
|
||||
name: "balanceOf",
|
||||
outputs: [{ name: "balance", type: "uint256" }],
|
||||
type: "function",
|
||||
stateMutability: "view"
|
||||
}
|
||||
],
|
||||
address: USDC_ADDRESSES[chain.name],
|
||||
functionName: "balanceOf",
|
||||
args: [address]
|
||||
})
|
||||
}
|
@ -40,11 +40,11 @@ export async function updateSignClientChainId(chainId: string, address: string)
|
||||
[namespace]: {
|
||||
...session.namespaces[namespace],
|
||||
chains: [
|
||||
...new Set([chainId].concat(Array.from(session.namespaces[namespace].chains || [])))
|
||||
...new Set([chainId].concat(Array.from(session?.namespaces?.[namespace]?.chains || [])))
|
||||
],
|
||||
accounts: [
|
||||
...new Set(
|
||||
[`${chainId}:${address}`].concat(Array.from(session.namespaces[namespace].accounts))
|
||||
[`${chainId}:${address}`].concat(Array.from(session?.namespaces?.[namespace]?.accounts || []))
|
||||
)
|
||||
]
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ interface IProps {
|
||||
intention?: string
|
||||
infoBoxCondition?: boolean
|
||||
infoBoxText?: string
|
||||
disabledApprove?: boolean
|
||||
approveLoader?: LoaderProps
|
||||
rejectLoader?: LoaderProps
|
||||
}
|
||||
@ -32,7 +31,6 @@ export default function RequestModal({
|
||||
intention,
|
||||
infoBoxCondition,
|
||||
infoBoxText,
|
||||
disabledApprove
|
||||
}: IProps) {
|
||||
const { currentRequestVerifyContext } = useSnapshot(SettingsStore.state)
|
||||
const isScam = currentRequestVerifyContext?.verified.isScam
|
||||
@ -65,14 +63,12 @@ export default function RequestModal({
|
||||
rejectLoader={rejectLoader}
|
||||
infoBoxCondition={infoBoxCondition}
|
||||
infoBoxText={infoBoxText}
|
||||
disabledApprove={disabledApprove}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}, [
|
||||
approveLoader,
|
||||
children,
|
||||
disabledApprove,
|
||||
infoBoxCondition,
|
||||
infoBoxText,
|
||||
intention,
|
||||
|
@ -8,7 +8,7 @@ import CloseIcon from '@mui/icons-material/Close'
|
||||
|
||||
import ModalStore from '@/store/ModalStore'
|
||||
import { cosmosAddresses } from '@/utils/CosmosWalletUtil'
|
||||
import { eip155Addresses } from '@/utils/EIP155WalletUtil'
|
||||
import { createOrRestoreEIP155Wallet, eip155Addresses, eip155Wallets } from '@/utils/EIP155WalletUtil'
|
||||
import { polkadotAddresses } from '@/utils/PolkadotWalletUtil'
|
||||
import { multiversxAddresses } from '@/utils/MultiversxWalletUtil'
|
||||
import { tronAddresses } from '@/utils/TronWalletUtil'
|
||||
@ -18,7 +18,7 @@ import { nearAddresses } from '@/utils/NearWalletUtil'
|
||||
import { kadenaAddresses } from '@/utils/KadenaWalletUtil'
|
||||
import { styledToast } from '@/utils/HelperUtil'
|
||||
import { web3wallet } from '@/utils/WalletConnectUtil'
|
||||
import { EIP155_CHAINS, EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
|
||||
import { EIP155Chain, EIP155_CHAINS, EIP155_SIGNING_METHODS } from '@/data/EIP155Data'
|
||||
import { COSMOS_MAINNET_CHAINS, COSMOS_SIGNING_METHODS } from '@/data/COSMOSData'
|
||||
import { KADENA_CHAINS, KADENA_SIGNING_METHODS } from '@/data/KadenaData'
|
||||
import { MULTIVERSX_CHAINS, MULTIVERSX_SIGNING_METHODS } from '@/data/MultiversxData'
|
||||
@ -31,8 +31,12 @@ import ChainDataMini from '@/components/ChainDataMini'
|
||||
import ChainAddressMini from '@/components/ChainAddressMini'
|
||||
import { getChainData } from '@/data/chainsUtil'
|
||||
import RequestModal from './RequestModal'
|
||||
import { SmartAccountLib } from '@/lib/SmartAccountLib'
|
||||
import { Hex } from 'viem'
|
||||
import ChainSmartAddressMini from '@/components/ChainSmartAddressMini'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import SettingsStore from '@/store/SettingsStore'
|
||||
import { allowedChains } from '@/utils/SmartAccountUtils'
|
||||
|
||||
const StyledText = styled(Text, {
|
||||
fontWeight: 400
|
||||
@ -43,6 +47,7 @@ const StyledSpan = styled('span', {
|
||||
} as any)
|
||||
|
||||
export default function SessionProposalModal() {
|
||||
const { smartAccountSponsorshipEnabled } = useSnapshot(SettingsStore.state)
|
||||
// Get proposal data and wallet address from store
|
||||
const data = useSnapshot(ModalStore.state)
|
||||
const proposal = data?.data?.proposal as SignClientTypes.EventArguments['session_proposal']
|
||||
@ -174,6 +179,11 @@ export default function SessionProposalModal() {
|
||||
[requestedChains]
|
||||
)
|
||||
|
||||
const smartAccountChains = useMemo(
|
||||
() => supportedChains.filter(chain =>(chain as any)?.smartAccountEnabled),
|
||||
[supportedChains]
|
||||
)
|
||||
|
||||
// get required chains that are not supported by the wallet
|
||||
const notSupportedChains = useMemo(() => {
|
||||
if (!proposal) return []
|
||||
@ -225,6 +235,21 @@ export default function SessionProposalModal() {
|
||||
supportedNamespaces
|
||||
})
|
||||
|
||||
// TODO: improve for multi network
|
||||
console.log('Checking if SA is deployed', eip155Wallets[eip155Addresses[0]])
|
||||
const chainId = namespaces['eip155'].chains?.[0]
|
||||
const smartAccountClient = new SmartAccountLib({
|
||||
privateKey: eip155Wallets[eip155Addresses[0]].getPrivateKey() as Hex,
|
||||
chain: allowedChains.find(chain => chain.id.toString() === chainId)!,
|
||||
sponsored: smartAccountSponsorshipEnabled,
|
||||
})
|
||||
const isDeployed = await smartAccountClient.checkIfSmartAccountDeployed()
|
||||
console.log('isDeployed', isDeployed)
|
||||
|
||||
if (isDeployed) {
|
||||
namespaces.eip155.accounts = [...namespaces.eip155.accounts, `eip155:5:${smartAccountClient.address}`]
|
||||
}
|
||||
|
||||
console.log('approving namespaces:', namespaces)
|
||||
|
||||
try {
|
||||
@ -241,7 +266,7 @@ export default function SessionProposalModal() {
|
||||
}
|
||||
setIsLoadingApprove(false)
|
||||
ModalStore.close()
|
||||
}, [proposal, supportedNamespaces])
|
||||
}, [proposal, supportedNamespaces, smartAccountSponsorshipEnabled])
|
||||
|
||||
// Hanlde reject action
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
@ -273,7 +298,6 @@ export default function SessionProposalModal() {
|
||||
rejectLoader={{ active: isLoadingReject }}
|
||||
infoBoxCondition={notSupportedChains.length > 0}
|
||||
infoBoxText={`The following required chains are not supported by your wallet - ${notSupportedChains.toString()}`}
|
||||
disabledApprove={notSupportedChains.length > 0}
|
||||
>
|
||||
<Row>
|
||||
<Col>
|
||||
@ -309,6 +333,16 @@ export default function SessionProposalModal() {
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
|
||||
<Row style={{ color: 'GrayText' }}>Smart Accounts</Row>
|
||||
{smartAccountChains.length &&
|
||||
smartAccountChains.map((chain, i) => {
|
||||
return (
|
||||
<Row key={i}>
|
||||
<ChainSmartAddressMini namespace={chain?.namespace!} />
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Row style={{ color: 'GrayText' }} justify="flex-end">
|
||||
|
@ -75,6 +75,8 @@ export default function SessionSignTypedDataModal() {
|
||||
metadata={requestSession.peer.metadata}
|
||||
onApprove={onApprove}
|
||||
onReject={onReject}
|
||||
approveLoader={{ active: isLoadingApprove }}
|
||||
rejectLoader={{ active: isLoadingReject }}
|
||||
>
|
||||
<RequesDetailsCard chains={[chainId ?? '']} protocol={requestSession.relay.protocol} />
|
||||
<Divider y={1} />
|
||||
|
Loading…
Reference in New Issue
Block a user