feat: Counterfactual signing of smart accounts (#461)
This commit is contained in:
parent
475847ff12
commit
43d41d40b4
@ -1,2 +1,2 @@
|
||||
NEXT_PUBLIC_PROJECT_ID=39bc...
|
||||
NEXT_PUBLIC_RELAY_URL=wss://relay.walletconnect.com
|
||||
NEXT_PUBLIC_RELAY_URL=wss://relay.walletconnect.com
|
@ -35,6 +35,13 @@ export const EIP155ChainData: ChainsMap = {
|
||||
slip44: 60,
|
||||
testnet: true,
|
||||
},
|
||||
"11155111": {
|
||||
name: "Ethereum Sepolia",
|
||||
id: "eip155:11155111",
|
||||
rpc: ["https://gateway.tenderly.co/public/sepolia "],
|
||||
slip44: 60,
|
||||
testnet: true,
|
||||
},
|
||||
"10": {
|
||||
name: "Optimism Mainnet",
|
||||
id: "eip155:10",
|
||||
@ -138,6 +145,10 @@ export const EIP155Metadata: NamespaceMetadata = {
|
||||
logo: "/assets/" + "eip155-1.png",
|
||||
rgb: EIP155Colors.ethereum,
|
||||
},
|
||||
"11155111": {
|
||||
logo: "/assets/" + "eip155-1.png",
|
||||
rgb: EIP155Colors.ethereum,
|
||||
},
|
||||
"10": {
|
||||
name: "Optimism",
|
||||
logo: "/assets/" + "eip155-10.png",
|
||||
|
@ -24,6 +24,7 @@ export const DEFAULT_MAIN_CHAINS = [
|
||||
export const DEFAULT_TEST_CHAINS = [
|
||||
// testnets
|
||||
"eip155:5",
|
||||
"eip155:11155111",
|
||||
"eip155:280",
|
||||
"eip155:420",
|
||||
"eip155:80001",
|
||||
|
@ -35,6 +35,14 @@ export const rpcProvidersByChainId: RpcProvidersByChainId = {
|
||||
symbol: "ETH",
|
||||
},
|
||||
},
|
||||
11155111: {
|
||||
name: "Ethereum Sepolia",
|
||||
baseURL: WALLETCONNECT_RPC_BASE_URL + "&chainId=eip155:11155111",
|
||||
token: {
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
},
|
||||
},
|
||||
137: {
|
||||
name: "Polygon Mainnet",
|
||||
baseURL: WALLETCONNECT_RPC_BASE_URL + "&chainId=eip155:137",
|
||||
|
@ -16,7 +16,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function ChainAddressMini({ address }: Props) {
|
||||
if (!address) return <></>
|
||||
if (!address || address === 'N/A') return <></>
|
||||
return (
|
||||
<>
|
||||
<Row>
|
||||
|
@ -4,11 +4,15 @@ 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
|
||||
chain: {
|
||||
chainId: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
rgb: string;
|
||||
namespace: string;
|
||||
} | undefined
|
||||
}
|
||||
|
||||
const getKey = (namespace?: string) => {
|
||||
@ -20,10 +24,11 @@ const getKey = (namespace?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
export default function ChainSmartAddressMini({chain}: Props) {
|
||||
const { address } = useSmartAccount(
|
||||
getKey(chain?.namespace) as `0x${string}`,
|
||||
allowedChains.find((c) => c.id.toString() === chain?.chainId.toString()) as Chain
|
||||
)
|
||||
if (!address) return <Spinner />
|
||||
return (
|
||||
<ChainAddressMini address={address}/>
|
||||
|
@ -44,11 +44,6 @@ export default function SmartAccountCard({
|
||||
setTimeout(() => setCopied(false), 1500)
|
||||
}
|
||||
|
||||
async function onChainChanged(chainId: string, address: string) {
|
||||
SettingsStore.setActiveChainId(chainId)
|
||||
await updateSignClientChainId(chainId.toString(), address)
|
||||
}
|
||||
|
||||
async function onCreateSmartAccount() {
|
||||
try {
|
||||
if (!isDeployed) {
|
||||
@ -58,6 +53,11 @@ export default function SmartAccountCard({
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function onChainChanged(chainId: string, address: string) {
|
||||
SettingsStore.setActiveChainId(chainId)
|
||||
await updateSignClientChainId(chainId.toString(), address)
|
||||
}
|
||||
|
||||
return (
|
||||
<ChainCard rgb={rgb} flexDirection="row" alignItems="center" flexWrap="wrap">
|
||||
|
@ -79,7 +79,7 @@ export const EIP155_TEST_CHAINS: Record<string,EIP155Chain> = {
|
||||
name: 'Ethereum Sepolia',
|
||||
logo: '/chain-logos/eip155-1.png',
|
||||
rgb: '99, 125, 234',
|
||||
rpc: 'https://rpc.sepolia.org',
|
||||
rpc: 'https://gateway.tenderly.co/public/sepolia',
|
||||
namespace: 'eip155',
|
||||
smartAccountEnabled: true,
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ import { useSnapshot } from "valtio";
|
||||
import { Hex } from "viem";
|
||||
import { styledToast } from "@/utils/HelperUtil";
|
||||
import { TransactionExecutionError } from "viem";
|
||||
import { SmartAccount } from "permissionless/accounts";
|
||||
|
||||
export default function useSmartAccount(signerPrivateKey: Hex, chain: Chain) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
@ -33,11 +34,6 @@ export default function useSmartAccount(signerPrivateKey: Hex, chain: Chain) {
|
||||
}
|
||||
}, [setLoading])
|
||||
|
||||
const deploy = useCallback(async () => {
|
||||
if (!client) return
|
||||
execute(client?.deploySmartAccount)
|
||||
}, [client, execute])
|
||||
|
||||
const sendTestTransaction = useCallback(async () => {
|
||||
if (!client) return
|
||||
execute(() => client?.sendTransaction({
|
||||
@ -47,6 +43,11 @@ export default function useSmartAccount(signerPrivateKey: Hex, chain: Chain) {
|
||||
}))
|
||||
}, [client, execute])
|
||||
|
||||
const deploy = useCallback(async () => {
|
||||
if (!client) return
|
||||
execute(client?.deploySmartAccount)
|
||||
}, [client, execute])
|
||||
|
||||
useEffect(() => {
|
||||
if (!signerPrivateKey || !chain) return
|
||||
const smartAccountClient = new SmartAccountLib({
|
||||
@ -58,19 +59,17 @@ export default function useSmartAccount(signerPrivateKey: Hex, chain: Chain) {
|
||||
}, [signerPrivateKey, smartAccountSponsorshipEnabled, chain])
|
||||
|
||||
useEffect(() => {
|
||||
client?.init()
|
||||
.then(() => {
|
||||
setIsDeployed(client?.isDeployed)
|
||||
setAddress(client?.address)
|
||||
client?.getAccount()
|
||||
.then((account: SmartAccount) => {
|
||||
setAddress(account.address)
|
||||
})
|
||||
}, [client, chain])
|
||||
|
||||
|
||||
return {
|
||||
address,
|
||||
isDeployed,
|
||||
deploy,
|
||||
loading,
|
||||
sendTestTransaction,
|
||||
deploy
|
||||
}
|
||||
}
|
@ -39,6 +39,8 @@ export class SmartAccountLib {
|
||||
throw new Error('A Pimlico API Key is required')
|
||||
}
|
||||
|
||||
console.log('investigate 2', chain, privateKey, sponsored)
|
||||
|
||||
this.chain = chain
|
||||
this.sponsored = sponsored
|
||||
this.#signerPrivateKey = privateKey
|
||||
|
@ -11,12 +11,15 @@ import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
|
||||
import { SignClientTypes } from '@walletconnect/types'
|
||||
import { getSdkError } from '@walletconnect/utils'
|
||||
import { providers } from 'ethers'
|
||||
import { chains } from './SmartAccountUtils'
|
||||
import { Hex } from 'viem'
|
||||
import { Chain, allowedChains } from './SmartAccountUtils'
|
||||
import SettingsStore from '@/store/SettingsStore'
|
||||
type RequestEventArgs = Omit<SignClientTypes.EventArguments['session_request'], 'verifyContext'>
|
||||
|
||||
|
||||
const getWallet = async (params: any) => {
|
||||
const typedChains: Record<number, Chain> = chains;
|
||||
console.log('get wallet params', params)
|
||||
const chainId = params?.chainId?.split(':')[1]
|
||||
console.log('chain id', chainId)
|
||||
@ -28,12 +31,18 @@ const getWallet = async (params: any) => {
|
||||
const smartAccountEnabledChain = allowedChains.find((chain) => chain.id.toString() === chainId) as Chain
|
||||
console.log('smart account enabled chain', smartAccountEnabledChain)
|
||||
const smartAccounts = await Promise.all(Object.values(eip155Wallets).map(async (wallet) => {
|
||||
console.log('typeed chains', typedChains[chainId])
|
||||
|
||||
const smartAccount = new SmartAccountLib({
|
||||
privateKey: wallet.getPrivateKey() as Hex,
|
||||
chain: smartAccountEnabledChain,
|
||||
chain: typedChains[chainId],
|
||||
sponsored: true, // TODO: Sponsor for now but should be dynamic according to SettingsStore
|
||||
})
|
||||
await smartAccount.init()
|
||||
|
||||
const isDeployed = await smartAccount.checkIfSmartAccountDeployed()
|
||||
if (!isDeployed) {
|
||||
await smartAccount.deploySmartAccount()
|
||||
}
|
||||
return smartAccount
|
||||
}));
|
||||
|
||||
@ -46,6 +55,11 @@ const getWallet = async (params: any) => {
|
||||
export async function approveEIP155Request(requestEvent: RequestEventArgs) {
|
||||
const { params, id } = requestEvent
|
||||
const { chainId, request } = params
|
||||
|
||||
console.log(requestEvent, chainId, "tests")
|
||||
|
||||
SettingsStore.setActiveChainId(chainId)
|
||||
|
||||
const wallet = await getWallet(params)
|
||||
|
||||
switch (request.method) {
|
||||
|
@ -5,6 +5,11 @@ const apiKey = process.env.NEXT_PUBLIC_PIMLICO_KEY
|
||||
|
||||
// Types
|
||||
export const allowedChains = [sepolia, polygonMumbai, goerli] as const
|
||||
// build chains so I can access them by id
|
||||
export const chains = allowedChains.reduce((acc, chain) => {
|
||||
acc[chain.id] = chain
|
||||
return acc
|
||||
}, {} as Record<Chain['id'], Chain>)
|
||||
export type Chain = (typeof allowedChains)[number]
|
||||
export type UrlConfig = {
|
||||
chain: Chain
|
||||
@ -56,7 +61,11 @@ 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 publicRPCUrl = ({ chain }: UrlConfig) => {
|
||||
console.log("investigate", chain)
|
||||
return 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) =>
|
||||
|
@ -32,11 +32,12 @@ 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 { Chain, allowedChains } from '@/utils/SmartAccountUtils'
|
||||
import { Hex } from 'viem'
|
||||
import useSmartAccount from '@/hooks/useSmartAccount'
|
||||
|
||||
const StyledText = styled(Text, {
|
||||
fontWeight: 400
|
||||
@ -51,7 +52,6 @@ export default function SessionProposalModal() {
|
||||
// Get proposal data and wallet address from store
|
||||
const data = useSnapshot(ModalStore.state)
|
||||
const proposal = data?.data?.proposal as SignClientTypes.EventArguments['session_proposal']
|
||||
|
||||
const [isLoadingApprove, setIsLoadingApprove] = useState(false)
|
||||
const [isLoadingReject, setIsLoadingReject] = useState(false)
|
||||
console.log('proposal', data.data?.proposal)
|
||||
@ -170,12 +170,19 @@ export default function SessionProposalModal() {
|
||||
optional.push(chains)
|
||||
}
|
||||
console.log('requestedChains', [...new Set([...required.flat(), ...optional.flat()])])
|
||||
|
||||
return [...new Set([...required.flat(), ...optional.flat()])]
|
||||
}, [proposal])
|
||||
|
||||
// the chains that are supported by the wallet from the proposal
|
||||
const supportedChains = useMemo(
|
||||
() => requestedChains.map(chain => getChainData(chain!)),
|
||||
() => requestedChains.map(chain => {
|
||||
const chainData = getChainData(chain!)
|
||||
|
||||
if (!chainData) return null
|
||||
|
||||
return chainData
|
||||
}),
|
||||
[requestedChains]
|
||||
)
|
||||
|
||||
@ -226,43 +233,50 @@ export default function SessionProposalModal() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const namespaces = buildApprovedNamespaces({
|
||||
proposal: proposal.params,
|
||||
supportedNamespaces
|
||||
})
|
||||
|
||||
// Hanlde approve action, construct session namespace
|
||||
const onApprove = useCallback(async () => {
|
||||
if (proposal) {
|
||||
setIsLoadingApprove(true)
|
||||
const namespaces = buildApprovedNamespaces({
|
||||
proposal: proposal.params,
|
||||
supportedNamespaces
|
||||
})
|
||||
// get keys of namespaces
|
||||
const namespaceKeys = Object.keys(namespaces)
|
||||
const [nameSpaceKey] = namespaceKeys
|
||||
|
||||
// TODO: improve for multi network
|
||||
console.log('namespaces', namespaces['eip155'])
|
||||
const namespaceChains = namespaces['eip155']?.chains?.map((c: string) => c.split(':')[1])
|
||||
const smartAccountEnabledChains: Chain[] = allowedChains.filter(chain => namespaceChains?.includes(chain.id.toString()))
|
||||
// We find a request for a chain that is enabled for smart account
|
||||
if (smartAccountEnabledChains.length) {
|
||||
const signerAddress = namespaces['eip155'].accounts[0].split(':')[2]
|
||||
// get chain ids from namespaces
|
||||
const [chainIds] = namespaceKeys.map(key => namespaces[key].chains)
|
||||
|
||||
if (chainIds) {
|
||||
const allowedChainIds = chainIds.filter(id => {
|
||||
const chainId = id.replace(`${nameSpaceKey}:`, '')
|
||||
return allowedChains.map(chain => chain.id.toString()).includes(chainId)
|
||||
})
|
||||
|
||||
console.log('allowedChainIds', allowedChainIds)
|
||||
|
||||
const chainIdParsed = allowedChainIds[0].replace(`${nameSpaceKey}:`, '')
|
||||
const signerAddress = namespaces[nameSpaceKey].accounts[0].split(':')[2]
|
||||
const wallet = eip155Wallets[signerAddress]
|
||||
const chain = smartAccountEnabledChains[0]
|
||||
if (wallet) {
|
||||
const smartAccountClient = new SmartAccountLib({
|
||||
privateKey: wallet.getPrivateKey() as Hex,
|
||||
chain,
|
||||
sponsored: smartAccountSponsorshipEnabled,
|
||||
})
|
||||
await smartAccountClient.init()
|
||||
const isDeployed = await smartAccountClient.checkIfSmartAccountDeployed()
|
||||
console.log('isDeployed', isDeployed, smartAccountClient.address)
|
||||
|
||||
if (isDeployed) {
|
||||
namespaces.eip155.accounts = [...namespaces.eip155.accounts, `eip155:${chain.id}:${smartAccountClient.address}`]
|
||||
}
|
||||
const chain = allowedChains.find(chain => chain.id.toString() === chainIdParsed)!
|
||||
|
||||
const smartAccountClient = new SmartAccountLib({
|
||||
privateKey: wallet.getPrivateKey() as Hex,
|
||||
chain: allowedChains.find(chain => chain.id.toString() === chainIdParsed)!,
|
||||
sponsored: smartAccountSponsorshipEnabled,
|
||||
})
|
||||
|
||||
const smartAccountAddress = await smartAccountClient.getAccount()
|
||||
if (wallet && smartAccountAddress) {
|
||||
namespaces.eip155.accounts = [...namespaces.eip155.accounts, `${nameSpaceKey}:${chain.id}:${smartAccountAddress.address}`]
|
||||
}
|
||||
|
||||
console.log('approving namespaces:', namespaces.eip155.accounts)
|
||||
}
|
||||
|
||||
console.log('approving namespaces:', namespaces)
|
||||
|
||||
try {
|
||||
try {
|
||||
await web3wallet.approveSession({
|
||||
id: proposal.id,
|
||||
namespaces
|
||||
@ -276,7 +290,7 @@ export default function SessionProposalModal() {
|
||||
}
|
||||
setIsLoadingApprove(false)
|
||||
ModalStore.close()
|
||||
}, [proposal, supportedNamespaces, smartAccountSponsorshipEnabled])
|
||||
}, [namespaces, proposal, smartAccountSponsorshipEnabled])
|
||||
|
||||
// Hanlde reject action
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
@ -339,7 +353,7 @@ export default function SessionProposalModal() {
|
||||
supportedChains.map((chain, i) => {
|
||||
return (
|
||||
<Row key={i}>
|
||||
<ChainAddressMini key={i} address={getAddress(chain?.namespace)} />
|
||||
<ChainAddressMini key={i} address={getAddress(chain?.namespace) || 'test'} />
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
@ -347,9 +361,13 @@ export default function SessionProposalModal() {
|
||||
<Row style={{ color: 'GrayText' }}>Smart Accounts</Row>
|
||||
{smartAccountChains.length &&
|
||||
smartAccountChains.map((chain, i) => {
|
||||
if (!chain) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<Row key={i}>
|
||||
<ChainSmartAddressMini namespace={chain?.namespace!} />
|
||||
<ChainSmartAddressMini chain={chain} />
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
|
Loading…
Reference in New Issue
Block a user