Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Counterfactual signing of smart accounts #461

Merged
merged 17 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion advanced/dapps/react-dapp-v2/.env.local.example
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions advanced/dapps/react-dapp-v2/src/chains/eip155.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions advanced/dapps/react-dapp-v2/src/constants/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions advanced/dapps/react-dapp-v2/src/helpers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ 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)

if (!address) return <Spinner />
return (
<ChainAddressMini address={address}/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ export default function SmartAccountCard({
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}`, chain)

function onCopy() {
Expand All @@ -48,16 +44,6 @@ export default function SmartAccountCard({
SettingsStore.setActiveChainId(chainId)
await updateSignClientChainId(chainId.toString(), address)
}

async function onCreateSmartAccount() {
try {
if (!isDeployed) {
await deploy()
}
} catch (error) {
console.error(error)
}
}

return (
<ChainCard rgb={rgb} flexDirection="row" alignItems="center" flexWrap="wrap">
Expand Down Expand Up @@ -110,35 +96,16 @@ export default function SmartAccountCard({
</Text>
</>
) : null}
{isDeployed && smartAccountAddress ? (
<Button
size="md"
css={{ marginTop: 20, width: '100%' }}
onClick={sendTestTransaction}
disabled={!isActiveChain || loading}
>
{loading ? <Loading size="sm" /> : 'Send Test TX'}
</Button>
) : (
<>
<Button
disabled={!isActiveChain || loading}
disabled={!isActiveChain}
size="sm"
css={{ marginTop: 20, width: '100%' }}
onClick={() => window.open(FAUCET_URLS[chain?.name], '_blank')}
>
{name} Faucet
</Button>
<Button
disabled={!isActiveChain || loading}
size="sm"
css={{ marginTop: 10, width: '100%' }}
onClick={onCreateSmartAccount}
>
{loading ? <Loading size="sm" css={{ paddingTop: 10 }} /> : 'Create Smart Account'}
</Button>
</>
)}
Bacis marked this conversation as resolved.
Show resolved Hide resolved
</ChainCard>
)
}
2 changes: 1 addition & 1 deletion advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
56 changes: 7 additions & 49 deletions advanced/wallets/react-wallet-v2/src/hooks/useSmartAccount.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,16 @@
import { SmartAccountLib } from "@/lib/SmartAccountLib";
import SettingsStore from "@/store/SettingsStore";
import { Chain, VITALIK_ADDRESS } from "@/utils/SmartAccountUtils";
import { useCallback, useEffect, useState } from "react";
import { Chain } from "@/utils/SmartAccountUtils";
import { useEffect, useState } from "react";
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)
const [client, setClient] = useState<SmartAccountLib>();
const [isDeployed, setIsDeployed] = useState(false)
const [address, setAddress] = useState<Hex>()
const { smartAccountSponsorshipEnabled } = useSnapshot(SettingsStore.state);

const execute = useCallback(async (callback: () => void) => {
try {
setLoading(true)
const res = await callback()
console.log('result:', res)
setLoading(false)
}
catch (e) {
if (e instanceof TransactionExecutionError) {
// shorten the error message
styledToast(e.cause.message, 'error')
} else if (e instanceof Error) {
styledToast(e.message, 'error')
}
console.error(e)
setLoading(false)
}
}, [setLoading])

const deploy = useCallback(async () => {
if (!client) return
execute(client?.deploySmartAccount)
}, [client, execute])

const sendTestTransaction = useCallback(async () => {
if (!client) return
execute(() => client?.sendTransaction({
Bacis marked this conversation as resolved.
Show resolved Hide resolved
to: VITALIK_ADDRESS,
value: 0n,
data: '0x',
}))
}, [client, execute])

useEffect(() => {
if (!signerPrivateKey || !chain) return
const smartAccountClient = new SmartAccountLib({
Expand All @@ -58,19 +22,13 @@ 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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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'
type RequestEventArgs = Omit<SignClientTypes.EventArguments['session_request'], 'verifyContext'>
Expand All @@ -30,11 +31,16 @@ const getWallet = async (params: any) => {
const smartAccounts = await Promise.all(Object.values(eip155Wallets).map(async (wallet) => {
const smartAccount = new SmartAccountLib({
privateKey: wallet.getPrivateKey() as Hex,
chain: smartAccountEnabledChain,
chain: chains[chainId],
sponsored: true, // TODO: Sponsor for now but should be dynamic according to SettingsStore
})
await smartAccount.init()
return smartAccount
const isDeployed = await smartAccount.checkIfSmartAccountDeployed()
if (isDeployed) {
return smartAccount
} else {
await smartAccount.deploySmartAccount()
return smartAccount
Bacis marked this conversation as resolved.
Show resolved Hide resolved
}
}));

const smartAccountAddress = getWalletAddressFromParams(smartAccounts.map(acc => acc.address!), params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 any)
Bacis marked this conversation as resolved.
Show resolved Hide resolved
export type Chain = (typeof allowedChains)[number]
export type UrlConfig = {
chain: Chain
Expand Down
70 changes: 30 additions & 40 deletions advanced/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -170,6 +170,7 @@ export default function SessionProposalModal() {
optional.push(chains)
}
console.log('requestedChains', [...new Set([...required.flat(), ...optional.flat()])])

return [...new Set([...required.flat(), ...optional.flat()])]
}, [proposal])

Expand All @@ -184,6 +185,10 @@ export default function SessionProposalModal() {
[supportedChains]
)



console.log(smartAccountChains, "smartAccountChains")

// get required chains that are not supported by the wallet
const notSupportedChains = useMemo(() => {
if (!proposal) return []
Expand Down Expand Up @@ -226,43 +231,31 @@ export default function SessionProposalModal() {
}
}, [])

const namespaces = buildApprovedNamespaces({
proposal: proposal.params,
supportedNamespaces
})
const chainId = namespaces['eip155'].chains?.[0]
const chainIdParsed = chainId?.replace('eip155:', '')
const signerAddress = namespaces['eip155'].accounts[0].split(':')[2]
const wallet = eip155Wallets[signerAddress]
const chain = allowedChains.find(chain => chain.id.toString() === chainIdParsed)!
Bacis marked this conversation as resolved.
Show resolved Hide resolved

const {
address: smartAccountAddress,
} = useSmartAccount(wallet.getPrivateKey() as Hex, chain)

// Hanlde approve action, construct session namespace
const onApprove = useCallback(async () => {
if (proposal) {
setIsLoadingApprove(true)
const namespaces = buildApprovedNamespaces({
proposal: proposal.params,
supportedNamespaces
})

// 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]
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}`]
}
}
if (wallet && smartAccountAddress) {
namespaces.eip155.accounts = [...namespaces.eip155.accounts, `eip155:${chain.id}:${smartAccountAddress}`]
}

console.log('approving namespaces:', namespaces)

try {
try {
await web3wallet.approveSession({
id: proposal.id,
namespaces
Expand All @@ -276,7 +269,7 @@ export default function SessionProposalModal() {
}
setIsLoadingApprove(false)
ModalStore.close()
}, [proposal, supportedNamespaces, smartAccountSponsorshipEnabled])
}, [chain, namespaces, proposal, smartAccountAddress, wallet])

// Hanlde reject action
// eslint-disable-next-line react-hooks/rules-of-hooks
Expand Down Expand Up @@ -345,14 +338,11 @@ export default function SessionProposalModal() {
})}

<Row style={{ color: 'GrayText' }}>Smart Accounts</Row>
{smartAccountChains.length &&
smartAccountChains.map((chain, i) => {
return (
<Row key={i}>
<ChainSmartAddressMini namespace={chain?.namespace!} />
</Row>
)
})}
{smartAccountAddress &&
<Row key={1}>
<ChainAddressMini key={1} address={smartAccountAddress} />
</Row>
}
</Grid>
<Grid>
<Row style={{ color: 'GrayText' }} justify="flex-end">
Expand Down
Loading