Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
31 changes: 16 additions & 15 deletions libs/remix-lib/src/execution/txRunnerWeb3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Transaction as InternalTransaction } from './txRunner'
import { Web3 } from 'web3'
import { BrowserProvider } from 'ethers'
import { normalizeHexAddress } from '../helpers/uiHelper'
import { aaSupportedNetworks, aaLocalStorageKey, getPimlicoBundlerURL, aaDeterminiticProxyAddress } from '../helpers/aaConstants'
import { toBigInt, toHex, toChecksumAddress } from 'web3-utils'
import { randomBytes } from 'crypto'
import "viem/window"
Expand Down Expand Up @@ -107,7 +108,7 @@ export class TxRunnerWeb3 {
} else {
try {
if (tx.fromSmartAccount) {
const { txHash, contractAddress } = await this.sendUserOp(tx)
const { txHash, contractAddress } = await this.sendUserOp(tx, network.id)
cb(null, txHash, isCreation, true, contractAddress)
} else {
const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
Expand Down Expand Up @@ -203,8 +204,11 @@ export class TxRunnerWeb3 {
callback(new Error('Gas estimation failed because of an unknown internal error. This may indicated that the transaction will fail.'))
return
}
if (tx.fromSmartAccount && tx.value === "0" && err && err.error && err.error.indexOf('insufficient funds for transfer') !== -1) {
// Do not show dialog for insufficient funds as smart account may be using paymaster
if (tx.fromSmartAccount && tx.value === "0" &&
err && err.message && err.message.includes('missing revert data')
) {
// Do not show dialog for 'missing revert data'
// tx fees can be managed by paymaster in case of smart account tx
// @todo If paymaster is used, check if balance/credits are available
err = null
}
Expand All @@ -227,16 +231,13 @@ export class TxRunnerWeb3 {
})
}

async sendUserOp (tx) {
const localStorageKey = 'smartAccounts'
const PUBLIC_NODE_URL = "https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9"
const determiniticProxyAddress = "0x4e59b44847b379578588920cA78FbF26c0B4956C"
const network = 'sepolia'
const chain = chains[network]
const BUNDLER_URL = `https://pimlico.remixproject.org/api/proxy/${chain.id}`
async sendUserOp (tx, chainId) {
const chain = chains[aaSupportedNetworks[chainId].name]
const PUBLIC_NODE_URL = aaSupportedNetworks[chainId].publicNodeUrl
const BUNDLER_URL = getPimlicoBundlerURL(chainId)

// Check that saOwner is there in MM addresses
let smartAccountsObj = localStorage.getItem(localStorageKey)
let smartAccountsObj = localStorage.getItem(aaLocalStorageKey)
smartAccountsObj = JSON.parse(smartAccountsObj)
const saDetails = smartAccountsObj[chain.id][tx.from]
const saOwner = saDetails['ownerEOA']
Expand All @@ -250,7 +251,7 @@ export class TxRunnerWeb3 {

const publicClient = createPublicClient({
chain,
transport: http(PUBLIC_NODE_URL) // choose any provider here
transport: http(PUBLIC_NODE_URL)
})

const safeAccount = await toSafeSmartAccount({
Expand Down Expand Up @@ -286,15 +287,15 @@ export class TxRunnerWeb3 {

const expectedDeploymentAddress = getContractAddress({
bytecode,
from: determiniticProxyAddress,
from: aaDeterminiticProxyAddress,
opcode: 'CREATE2',
salt
})
let txHash, contractAddress
if (!tx.to) {
// contract deployment transaction
txHash = await saClient.sendTransaction({
to: determiniticProxyAddress,
to: aaDeterminiticProxyAddress,
data: encodePacked(["bytes32", "bytes"], [salt, bytecode])
})
// check if code is deployed to expectedDeployment Address
Expand All @@ -305,7 +306,7 @@ export class TxRunnerWeb3 {
contractAddress = expectedDeploymentAddress
} else {
contractAddress = undefined
console.error('Error in contract deployment')
console.error('Error in contract deployment using smart account')
}
} else {
// contract interaction transaction
Expand Down
26 changes: 26 additions & 0 deletions libs/remix-lib/src/helpers/aaConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ZeroAddress } from 'ethers'

// AA02: Add network name and public URL to support contract transactions using smart account
export const aaSupportedNetworks = {
"11155111": {
name: "sepolia",
publicNodeUrl: "https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9"
},
// "10200": {
// name: "gnosisChiado",
// publicNodeUrl: "https://rpc.chiadochain.net/"
// }
}

export const getPimlicoBundlerURL = (chainId) => {
return `https://pimlico.remixproject.org/api/proxy/${chainId}`
}

export const aaLocalStorageKey = 'smartAccounts'

// AA03: Check if this address is valid for newly added network
// This determiniticProxyAddress is used for replay protection during contract deployment
// See: https://github.com/safe-global/safe-smart-account?tab=readme-ov-file#replay-protection-eip-155
export const aaDeterminiticProxyAddress = "0x4e59b44847b379578588920cA78FbF26c0B4956C"

export const toAddress = ZeroAddress // A dummy zero value tx is made to this zero address to create existence of smart account
1 change: 1 addition & 0 deletions libs/remix-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TxRunnerVM } from './execution/txRunnerVM'
import { TxRunnerWeb3 } from './execution/txRunnerWeb3'
import * as txResultHelper from './helpers/txResultHelper'
export { ConsoleLogs } from './helpers/hhconsoleSigs'
export { aaSupportedNetworks, aaLocalStorageKey, getPimlicoBundlerURL, aaDeterminiticProxyAddress, toAddress } from './helpers/aaConstants'
export { ICompilerApi, ConfigurationSettings, iSolJsonBinData, iSolJsonBinDataBuild } from './types/ICompilerApi'
export { QueryParams } from './query-params'
export { VMexecutionResult } from './execution/txRunnerVM'
Expand Down
32 changes: 15 additions & 17 deletions libs/remix-ui/run-tab/src/lib/actions/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RunTab } from "../types/run-tab"
import { clearInstances, setAccount, setExecEnv } from "./actions"
import { displayNotification, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, setMatchPassphrase, setPassphrase } from "./payload"
import { toChecksumAddress } from '@ethereumjs/util'
import { aaSupportedNetworks, aaLocalStorageKey, getPimlicoBundlerURL, toAddress } from '@remix-project/remix-lib'
import { SmartAccount } from "../types"
import "viem/window"
import { custom, createWalletClient, createPublicClient, http } from "viem"
Expand Down Expand Up @@ -109,14 +110,13 @@ export const createNewBlockchainAccount = async (plugin: RunTab, dispatch: React
}

export const createSmartAccount = async (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const localStorageKey = 'smartAccounts'
const PUBLIC_NODE_URL = "https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9"
const toAddress = "0xAFdAC33F6F134D46bAbE74d9125F3bf8e8AB3a44" // A dummy zero value tx is made to this address to create existence of smart account
const safeAddresses: string[] = Object.keys(plugin.REACT_API.smartAccounts)
const network = 'sepolia'
const chain = chains[network]
const BUNDLER_URL = `https://pimlico.remixproject.org/api/proxy/${chain.id}`

const { chainId } = plugin.REACT_API
const chain = chains[aaSupportedNetworks[chainId].name]
const PUBLIC_NODE_URL = aaSupportedNetworks[chainId].publicNodeUrl
const BUNDLER_URL = getPimlicoBundlerURL(chainId)

const safeAddresses: string[] = Object.keys(plugin.REACT_API.smartAccounts)
let salt

// @ts-ignore
Expand Down Expand Up @@ -168,6 +168,7 @@ export const createSmartAccount = async (plugin: RunTab, dispatch: React.Dispatc
estimateFeesPerGas: async () => (await paymasterClient.getUserOperationGasPrice()).fast,
}
})

// Make a dummy tx to force smart account deployment
const useropHash = await saClient.sendUserOperation({
calls: [{
Expand All @@ -177,9 +178,8 @@ export const createSmartAccount = async (plugin: RunTab, dispatch: React.Dispatc
})
await saClient.waitForUserOperationReceipt({ hash: useropHash })

// TO verify creation, check if there is a contract code at this address
// To verify creation, check if there is a contract code at this address
const safeAddress = safeAccount.address

const sAccount: SmartAccount = {
address : safeAccount.address,
salt,
Expand All @@ -188,10 +188,10 @@ export const createSmartAccount = async (plugin: RunTab, dispatch: React.Dispatc
}
plugin.REACT_API.smartAccounts[safeAddress] = sAccount
// Save smart accounts in local storage
const smartAccountsStr = localStorage.getItem(localStorageKey)
const smartAccountsStr = localStorage.getItem(aaLocalStorageKey)
const smartAccountsObj = JSON.parse(smartAccountsStr)
smartAccountsObj[plugin.REACT_API.chainId] = plugin.REACT_API.smartAccounts
localStorage.setItem(localStorageKey, JSON.stringify(smartAccountsObj))
smartAccountsObj[chainId] = plugin.REACT_API.smartAccounts
localStorage.setItem(aaLocalStorageKey, JSON.stringify(smartAccountsObj))

return plugin.call('notification', 'toast', `Safe account ${safeAccount.address} created for owner ${account}`)
} catch (error) {
Expand All @@ -202,21 +202,19 @@ export const createSmartAccount = async (plugin: RunTab, dispatch: React.Dispatc

export const loadSmartAccounts = async (plugin) => {
const { chainId } = plugin.REACT_API
const localStorageKey = 'smartAccounts'

const smartAccountsStr = localStorage.getItem(localStorageKey)
const smartAccountsStr = localStorage.getItem(aaLocalStorageKey)
if (smartAccountsStr) {
const smartAccountsObj = JSON.parse(smartAccountsStr)
if (smartAccountsObj[chainId]) {
plugin.REACT_API.smartAccounts = smartAccountsObj[chainId]
} else {
smartAccountsObj[chainId] = {}
localStorage.setItem(localStorageKey, JSON.stringify(smartAccountsObj))
localStorage.setItem(aaLocalStorageKey, JSON.stringify(smartAccountsObj))
}
} else {
const objToStore = {}
objToStore[chainId] = {}
localStorage.setItem(localStorageKey, JSON.stringify(objToStore))
localStorage.setItem(aaLocalStorageKey, JSON.stringify(objToStore))
}
}

Expand Down
39 changes: 20 additions & 19 deletions libs/remix-ui/run-tab/src/lib/components/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,33 @@ export function AccountUI(props: AccountProps) {
const ownerEOA = useRef(null)

const intl = useIntl()
const smartAccounts: string[] = networkName.includes('Sepolia') ? Object.keys(props.runTabPlugin.REACT_API.smartAccounts) : []
const aaSupportedChainIds = ["11155111"] // AA01: Add chain id here to show 'Create Smart Account' button in Udapp
const smartAccounts: string[] = aaSupportedChainIds.some(e => networkName.includes(e)) ? Object.keys(props.runTabPlugin.REACT_API.smartAccounts) : []

useEffect(() => {
if (accounts.length > 0 && !accounts.includes(selectedAccount)) {
props.setAccount(accounts[0])
}
}, [accounts, selectedAccount])

// Uncomment this when we want to show 'Create Smart Account' button
// useEffect(() => {
// if (networkName.includes('Sepolia')) {
// if (smartAccounts.length > 0 && smartAccounts.includes(selectedAccount)) {
// setSmartAccountSelected(true)
// setEnableCSM(false)
// ownerEOA.current = props.runTabPlugin.REACT_API.smartAccounts[selectedAccount].ownerEOA
// }
// else {
// setSmartAccountSelected(false)
// setEnableCSM(true)
// ownerEOA.current = null
// }
// } else {
// setEnableCSM(false)
// setSmartAccountSelected(false)
// }
// }, [selectedAccount])
// Comment this when not to show 'Create Smart Account' button
useEffect(() => {
if (aaSupportedChainIds.some(e => networkName.includes(e))) {
if (smartAccounts.length > 0 && smartAccounts.includes(selectedAccount)) {
setSmartAccountSelected(true)
setEnableCSM(false)
ownerEOA.current = props.runTabPlugin.REACT_API.smartAccounts[selectedAccount].ownerEOA
}
else {
setSmartAccountSelected(false)
setEnableCSM(true)
ownerEOA.current = null
}
} else {
setEnableCSM(false)
setSmartAccountSelected(false)
}
}, [selectedAccount])

useEffect(() => {
props.setAccount('')
Expand Down