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

Interact using smart account #5055

Merged
merged 10 commits into from
Sep 24, 2024
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
22 changes: 16 additions & 6 deletions apps/remix-ide/src/blockchain/blockchain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -708,20 +708,30 @@ export class Blockchain extends Plugin {
(_) => this.executionContext.currentblockGasLimit()
)

web3Runner.event.register('transactionBroadcasted', (txhash) => {
web3Runner.event.register('transactionBroadcasted', (txhash, isUserOp) => {
this.executionContext.detectNetwork((error, network) => {
if (error || !network) return
if (network.name === 'VM') return
const viewEtherScanLink = etherScanLink(network.name, txhash)

if (viewEtherScanLink) {
if (isUserOp) {
const userOpLink = `https://jiffyscan.xyz/userOpHash/${txhash}?network=${network.name}`
this.call(
'terminal',
'logHtml',
<a href={etherScanLink(network.name, txhash)} target="_blank">
view on etherscan
<a href={userOpLink} target="_blank">
view on Jiffyscan
</a>
)
} else {
const viewEtherScanLink = etherScanLink(network.name, txhash)
if (viewEtherScanLink) {
this.call(
'terminal',
'logHtml',
<a href={etherScanLink(network.name, txhash)} target="_blank">
view on Etherscan
</a>
)
}
}
})
})
Expand Down
63 changes: 52 additions & 11 deletions libs/remix-lib/src/execution/txRunnerWeb3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { EventManager } from '../eventManager'
import type { Transaction as InternalTransaction } from './txRunner'
import Web3 from 'web3'
import { toBigInt, toHex } from 'web3-utils'
import { createPublicClient, createWalletClient, http, custom, WalletClient } from "viem"
import { sepolia } from 'viem/chains'
import "viem/window"
import { V06 } from "userop"

export class TxRunnerWeb3 {
event
Expand Down Expand Up @@ -38,11 +42,11 @@ export class TxRunnerWeb3 {

let currentDateTime = new Date();
const start = currentDateTime.getTime() / 1000
const cb = (err, resp) => {
const cb = (err, resp, isUserOp) => {
if (err) {
return callback(err, resp)
}
this.event.trigger('transactionBroadcasted', [resp])
this.event.trigger('transactionBroadcasted', [resp, isUserOp])
const listenOnResponse = () => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
Expand All @@ -64,13 +68,13 @@ export class TxRunnerWeb3 {
async (value) => {
try {
const res = await (this.getWeb3() as any).eth.personal.sendTransaction({ ...tx, value }, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, res.transactionHash)
cb(null, res.transactionHash, false)
} catch (e) {
console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)
// in case the receipt is available, we consider that only the execution failed but the transaction went through.
// So we don't consider this to be an error.
if (e.receipt) cb(null, e.receipt.transactionHash)
else cb(e, null)
if (e.receipt) cb(null, e.receipt.transactionHash, false)
else cb(e, null, false)
}
},
() => {
Expand All @@ -79,14 +83,19 @@ export class TxRunnerWeb3 {
)
} else {
try {
const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, res.transactionHash)
if (tx.fromSmartAccount) {
const userOp = await sendUserOp(tx)
cb(null, userOp.userOpHash, true)
} else {
const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true })
cb(null, res.transactionHash, false)
}
} catch (e) {
console.log(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)
// in case the receipt is available, we consider that only the execution failed but the transaction went through.
// So we don't consider this to be an error.
if (e.receipt) cb(null, e.receipt.transactionHash)
else cb(e, null)
if (e.receipt) cb(null, e.receipt.transactionHash, false)
else cb(e, null, false)
}
}
}
Expand All @@ -102,9 +111,8 @@ export class TxRunnerWeb3 {
}

runInNode (from, fromSmartAccount, to, data, value, gasLimit, useCall, timestamp, confirmCb, gasEstimationForceSend, promptCb, callback) {
const tx = { from: from, to: to, data: data, value: value }
const tx = { from: from, fromSmartAccount, to: to, data: data, value: value }
if (!from) return callback('the value of "from" is not defined. Please make sure an account is selected.')
if (fromSmartAccount) return callback('from address is from a smart account')
if (useCall) {
if (this._api && this._api.isVM()) {
(this.getWeb3() as any).remix.registerCallId(timestamp)
Expand Down Expand Up @@ -186,6 +194,39 @@ export class TxRunnerWeb3 {
}
}

const sendUserOp = async (tx) => {
console.log('sendUserOp--tx-->', tx)
const bundlerEndpoint = "https://public.stackup.sh/api/v1/node/ethereum-sepolia"

const ethClient: any = createPublicClient({
chain: sepolia,
transport: http(bundlerEndpoint)
})
// @ts-ignore
const [account] = await window.ethereum!.request({ method: 'eth_requestAccounts' })

const walletClient: WalletClient = createWalletClient({
account,
chain: sepolia,
transport: custom(window.ethereum)
})

// @ts-ignore
const smartAccount = new V06.Account.Instance({
...V06.Account.Common.SimpleAccount.base(ethClient, walletClient),
})
const sender = await smartAccount.getSender()
console.log('sender--->', sender)

const userOp = await smartAccount.encodeCallData("execute", [tx.to, tx.value, tx.data]).sendUserOperation()
console.log('userOp---->', userOp)
return userOp

// const receipt = await userOp.wait()
// console.log('receipt---->', receipt)

}

async function tryTillReceiptAvailable (txhash: string, web3: Web3) {
try {
const receipt = await web3.eth.getTransactionReceipt(txhash)
Expand Down
69 changes: 37 additions & 32 deletions libs/remix-ui/run-tab/src/lib/actions/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ 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 { createPublicClient, createWalletClient, http, custom, PublicClient } from "viem"
import { createPublicClient, createWalletClient, http, custom } from "viem"
import { sepolia } from 'viem/chains'
import "viem/window"
import { V06 } from "userop"

declare global {
interface Window {
ethereum?: any
}
}
import { SmartAccountDetails } from "../types"

export const updateAccountBalances = async (plugin: RunTab, dispatch: React.Dispatch<any>) => {
const accounts = plugin.REACT_API.accounts.loadedAccounts
Expand All @@ -33,8 +29,8 @@ export const fillAccountsList = async (plugin: RunTab, dispatch: React.Dispatch<
const provider = plugin.blockchain.getProvider()
let accounts = await plugin.blockchain.getAccounts()
if (provider && provider.startsWith('injected') && accounts?.length) {
await loadSmartAccounts(plugin, accounts[0])
if (plugin.REACT_API.smartAccounts.addresses.length) accounts.push(...plugin.REACT_API.smartAccounts.addresses)
// await loadSmartAccounts(plugin, accounts[0])
// if (plugin.REACT_API.smartAccounts.addresses.length) accounts.push(...plugin.REACT_API.smartAccounts.addresses)
}
if (!accounts) accounts = []

Expand Down Expand Up @@ -109,30 +105,48 @@ export const createSmartAccount = async (plugin: RunTab, dispatch: React.Dispatc
chain: sepolia,
transport: http(bundlerEndpoint)
})

const smartAddresses = Object.keys(plugin.REACT_API.smartAccounts)
console.log('smartAddresses--->', smartAddresses)
// @ts-ignore
const accounts = await window.ethereum!.request({ method: 'eth_requestAccounts' })
console.log('accounts---->', accounts) // Non checksummed
const account = plugin.REACT_API.accounts.selectedAccount
console.log('account---account->', account)
const walletClient: any = createWalletClient({
account,
chain: sepolia,
transport: custom(window.ethereum)
})

const addresses = await walletClient.getAddresses()

// @ts-ignore
const smartAccount = new V06.Account.Instance({
...V06.Account.Common.SimpleAccount.base(ethClient, walletClient),
})
await smartAccount.setSalt(BigInt(plugin.REACT_API.smartAccounts.addresses.length))
const lastSalt = smartAddresses.length ? plugin.REACT_API.smartAccounts[smartAddresses[smartAddresses.length - 1]].salt : -1
const salt = lastSalt + 1
console.log('salt---->', salt)
await smartAccount.setSalt(BigInt(salt))
const sender = await smartAccount.getSender()
plugin.REACT_API.smartAccounts.addresses.push(sender)

// Save smart accounts w.r.t. primary address of WalletClient
const smartAccountsStr = localStorage.getItem(localStorageKey)
const smartAccountsObj = JSON.parse(smartAccountsStr)
smartAccountsObj[plugin.REACT_API.chainId][addresses[0]].push(sender)
localStorage.setItem(localStorageKey, JSON.stringify(smartAccountsObj))

plugin.call('notification', 'toast', `smart account created with address ${sender}`)
console.log('sender---->', sender)
if (!smartAddresses.includes(sender)) {
const saDetails: SmartAccountDetails = {
address : sender,
salt,
owner: account,
timestamp: Date.now()
}
console.log('saDetails---->', saDetails)
plugin.REACT_API.smartAccounts[sender] = saDetails
// Save smart accounts in local storage
const smartAccountsStr = localStorage.getItem(localStorageKey)
const smartAccountsObj = JSON.parse(smartAccountsStr)
smartAccountsObj[plugin.REACT_API.chainId] = plugin.REACT_API.smartAccounts
localStorage.setItem(localStorageKey, JSON.stringify(smartAccountsObj))

await fillAccountsList(plugin, dispatch)
plugin.call('notification', 'toast', `smart account created with address ${sender}`)
// await fillAccountsList(plugin, dispatch)
}
}

export const loadSmartAccounts = async (plugin, primaryAddress) => {
Expand All @@ -143,23 +157,14 @@ export const loadSmartAccounts = async (plugin, primaryAddress) => {
if (smartAccountsStr) {
const smartAccountsObj = JSON.parse(smartAccountsStr)
if (smartAccountsObj[chainId]) {
if (smartAccountsObj[chainId][primaryAddress]) {
for (const obj of smartAccountsObj[chainId][primaryAddress]) {
plugin.REACT_API.smartAccounts.addresses.push(obj)
}
} else {
smartAccountsObj[chainId][primaryAddress] = []
localStorage.setItem(localStorageKey, JSON.stringify(smartAccountsObj))
}
plugin.REACT_API.smartAccounts = smartAccountsObj[chainId]
} else {
smartAccountsObj[chainId] = {}
smartAccountsObj[chainId][primaryAddress] = []
localStorage.setItem(localStorageKey, JSON.stringify(smartAccountsObj))
}
} else {
const objToStore = {}
objToStore[chainId] = {}
objToStore[chainId][primaryAddress] = []
localStorage.setItem(localStorageKey, JSON.stringify(objToStore))
}
}
Expand Down
4 changes: 1 addition & 3 deletions libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ export const runTabInitialState: RunTabState = {
error: null,
selectedAccount: ''
},
smartAccounts: {
addresses: []
},
smartAccounts: {},
sendValue: '0',
sendUnit: 'wei',
gasLimit: 0,
Expand Down
19 changes: 10 additions & 9 deletions libs/remix-ui/run-tab/src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export type Provider = {
position: number
}

export type SmartAccountDetails = {
address: string
salt: number
owner: string
timestamp: number
}

export interface RunTabState {
accounts: {
loadedAccounts: Record<string, string>,
Expand All @@ -45,9 +52,7 @@ export interface RunTabState {
error: string,
selectedAccount: string
},
smartAccounts: {
addresses: Record<string, any>[]
},
smartAccounts: Record<string, SmartAccountDetails>,
sendValue: string,
sendUnit: 'ether' | 'finney' | 'gwei' | 'wei',
gasLimit: number,
Expand Down Expand Up @@ -143,9 +148,7 @@ export interface SettingsProps {
isSuccessful: boolean,
error: string
},
smartAccounts: {
addresses: Record<string, any>[]
},
smartAccounts: Record<string, SmartAccountDetails>,
setAccount: (account: string) => void,
setUnit: (unit: 'ether' | 'finney' | 'gwei' | 'wei') => void,
sendValue: string,
Expand Down Expand Up @@ -196,9 +199,7 @@ export interface AccountProps {
isSuccessful: boolean,
error: string
},
smartAccounts: {
addresses: Record<string, any>[]
},
smartAccounts: Record<string, SmartAccountDetails>,
setAccount: (account: string) => void,
personalMode: boolean,
createNewBlockchainAccount: (cbMessage: JSX.Element) => void,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@
"timers-browserify": "^2.0.12",
"ts-node": "10.9.1",
"tslint": "~6.0.0",
"typescript": "^4.8.4",
"typescript": "5.0.4",
"uglify-js": "^2.8.16",
"url": "^0.11.0",
"vm-browserify": "^1.1.2",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30239,6 +30239,11 @@ typescript-tuple@^2.2.1:
dependencies:
typescript-compare "^0.0.2"

[email protected]:
version "5.0.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==

typescript@^4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
Expand Down
Loading