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

Feat/multiple transactions #36

Merged
merged 5 commits into from
Nov 22, 2023
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
5 changes: 5 additions & 0 deletions .changeset/honest-taxis-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"permissionless": patch
---

Enable batch calls for Safe account
130 changes: 93 additions & 37 deletions src/accounts/privateKeyToSafeSmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,35 +40,23 @@ export const EIP712_SAFE_OPERATION_TYPE = {
]
}

const SAFE_ADDRESSES_MAP: {
const SAFE_VERSION_TO_ADDRESSES_MAP: {
[key in SafeVersion]: {
[chainId: string]: {
ADD_MODULES_LIB_ADDRESS: Address
SAFE_4337_MODULE_ADDRESS: Address
SAFE_PROXY_FACTORY_ADDRESS: Address
SAFE_SINGLETON_ADDRESS: Address
}
ADD_MODULES_LIB_ADDRESS: Address
SAFE_4337_MODULE_ADDRESS: Address
SAFE_PROXY_FACTORY_ADDRESS: Address
SAFE_SINGLETON_ADDRESS: Address
MULTI_SEND_CALL_ONLY_ADDRESS: Address
}
} = {
"1.4.1": {
"11155111": {
ADD_MODULES_LIB_ADDRESS:
"0x191EFDC03615B575922289DC339F4c70aC5C30Af",
SAFE_4337_MODULE_ADDRESS:
"0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38",
SAFE_PROXY_FACTORY_ADDRESS:
"0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a"
},
"5": {
ADD_MODULES_LIB_ADDRESS:
"0x191EFDC03615B575922289DC339F4c70aC5C30Af",
SAFE_4337_MODULE_ADDRESS:
"0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38",
SAFE_PROXY_FACTORY_ADDRESS:
"0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a"
}
ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af",
SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38",
SAFE_PROXY_FACTORY_ADDRESS:
"0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a",
MULTI_SEND_CALL_ONLY_ADDRESS:
"0x9641d764fc13c8B624c04430C7356C1C7C8102e2"
}
}

Expand Down Expand Up @@ -371,36 +359,39 @@ export async function privateKeyToSafeSmartAccount<
safe4337ModuleAddress: _safe4337ModuleAddress,
safeProxyFactoryAddress: _safeProxyFactoryAddress,
safeSingletonAddress: _safeSingletonAddress,
multiSendCallOnlyAddress: _multiSendCallOnlyAddress,
saltNonce = 0n
}: {
privateKey: Hex
safeVersion: SafeVersion
privateKey: Hex
entryPoint: Address
addModuleLibAddress?: Address
safe4337ModuleAddress?: Address
safeProxyFactoryAddress?: Address
safeSingletonAddress?: Address
multiSendCallOnlyAddress?: Address
saltNonce?: bigint
}
): Promise<PrivateKeySafeSmartAccount<TTransport, TChain>> {
const privateKeyAccount = privateKeyToAccount(privateKey)

const chainId = await getChainId(client)
const chainIdString: string = chainId.toString()

const addModuleLibAddress: Address =
const addModuleLibAddress =
_addModuleLibAddress ??
SAFE_ADDRESSES_MAP[safeVersion][chainIdString].ADD_MODULES_LIB_ADDRESS
const safe4337ModuleAddress: Address =
SAFE_VERSION_TO_ADDRESSES_MAP[safeVersion].ADD_MODULES_LIB_ADDRESS
const safe4337ModuleAddress =
_safe4337ModuleAddress ??
SAFE_ADDRESSES_MAP[safeVersion][chainIdString].SAFE_4337_MODULE_ADDRESS
const safeProxyFactoryAddress: Address =
SAFE_VERSION_TO_ADDRESSES_MAP[safeVersion].SAFE_4337_MODULE_ADDRESS
const safeProxyFactoryAddress =
_safeProxyFactoryAddress ??
SAFE_ADDRESSES_MAP[safeVersion][chainIdString]
.SAFE_PROXY_FACTORY_ADDRESS
const safeSingletonAddress: Address =
SAFE_VERSION_TO_ADDRESSES_MAP[safeVersion].SAFE_PROXY_FACTORY_ADDRESS
const safeSingletonAddress =
_safeSingletonAddress ??
SAFE_ADDRESSES_MAP[safeVersion][chainIdString].SAFE_SINGLETON_ADDRESS
SAFE_VERSION_TO_ADDRESSES_MAP[safeVersion].SAFE_SINGLETON_ADDRESS
const multiSendCallOnlyAddress =
_multiSendCallOnlyAddress ??
SAFE_VERSION_TO_ADDRESSES_MAP[safeVersion].MULTI_SEND_CALL_ONLY_ADDRESS

const accountAddress = await getAccountAddress<TTransport, TChain>({
client,
Expand Down Expand Up @@ -544,7 +535,72 @@ export async function privateKeyToSafeSmartAccount<
async encodeDeployCallData(_) {
throw new Error("Safe account doesn't support account deployment")
},
async encodeCallData({ to, value, data }) {
async encodeCallData(args) {
let to: Address
let value: bigint
let data: Hex

if (Array.isArray(args)) {
const argsArray = args as {
to: Address
value: bigint
data: Hex
}[]

to = multiSendCallOnlyAddress
value = 0n
data = encodeFunctionData({
abi: [
{
inputs: [
{
internalType: "bytes",
name: "transactions",
type: "bytes"
}
],
name: "multiSend",
outputs: [],
stateMutability: "payable",
type: "function"
}
],
functionName: "multiSend",
args: [
`0x${argsArray
.map(({ to, value, data }) => {
const datBytes = toBytes(data)
return encodePacked(
[
"uint8",
"address",
"uint256",
"uint256",
"bytes"
],
[
0,
to,
value,
BigInt(datBytes.length),
data
]
).slice(2)
})
.join("")}`
]
})
} else {
const singleTransaction = args as {
to: Address
value: bigint
data: Hex
}
to = singleTransaction.to
data = singleTransaction.data
value = singleTransaction.value
}

return encodeFunctionData({
abi: [
{
Expand Down
43 changes: 42 additions & 1 deletion src/accounts/privateKeyToSimpleSmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,48 @@ export async function privateKeyToSimpleSmartAccount<
async encodeDeployCallData(_) {
throw new Error("Simple account doesn't support account deployment")
},
async encodeCallData({ to, value, data }) {
async encodeCallData(args) {
if (Array.isArray(args)) {
const argsArray = args as {
to: Address
value: bigint
data: Hex
}[]
return encodeFunctionData({
abi: [
{
inputs: [
{
internalType: "address[]",
name: "dest",
type: "address[]"
},
{
internalType: "bytes[]",
name: "func",
type: "bytes[]"
}
],
name: "executeBatch",
outputs: [],
stateMutability: "nonpayable",
type: "function"
}
],
functionName: "executeBatch",
args: [
argsArray.map((a) => a.to),
argsArray.map((a) => a.data)
]
})
}

const { to, value, data } = args as {
to: Address
value: bigint
data: Hex
}

return encodeFunctionData({
abi: [
{
Expand Down
18 changes: 13 additions & 5 deletions src/accounts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ export type SmartAccount<
entryPoint: Address
getNonce: () => Promise<bigint>
getInitCode: () => Promise<Hex>
encodeCallData: ({
to,
value,
data
}: { to: Address; value: bigint; data: Hex }) => Promise<Hex>
encodeCallData: (
args:
| {
to: Address
value: bigint
data: Hex
}
| {
to: Address
value: bigint
data: Hex
}[]
) => Promise<Hex>
getDummySignature(): Promise<Hex>
encodeDeployCallData: <TAbi extends Abi | readonly unknown[] = Abi>({
abi,
Expand Down
10 changes: 9 additions & 1 deletion src/actions/bundler/chainId.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Account, Chain, Client, Transport } from "viem"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { BundlerRpcSchema } from "../../types/bundler.js"

/**
* Returns the supported chain id by the bundler service
Expand All @@ -22,7 +24,13 @@ import type { BundlerClient } from "../../clients/createBundlerClient.js"
* // Return 5n for Goerli
*
*/
export const chainId = async (client: BundlerClient) => {
export const chainId = async <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined
>(
client: Client<TTransport, TChain, TAccount, BundlerRpcSchema>
) => {
return Number(
await client.request({
method: "eth_chainId",
Expand Down
11 changes: 8 additions & 3 deletions src/actions/bundler/estimateUserOperationGas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Address } from "viem"
import type { Account, Address, Chain, Client, Transport } from "viem"
import type { PartialBy } from "viem/types/utils"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { BundlerRpcSchema } from "../../types/bundler.js"
import type { UserOperation } from "../../types/userOperation.js"
import type { UserOperationWithBigIntAsHex } from "../../types/userOperation.js"
import { deepHexlify } from "../../utils/deepHexlify.js"
Expand Down Expand Up @@ -46,8 +47,12 @@ export type EstimateUserOperationGasReturnType = {
* // Return {preVerificationGas: 43492n, verificationGasLimit: 59436n, callGasLimit: 9000n}
*
*/
export const estimateUserOperationGas = async (
client: BundlerClient,
export const estimateUserOperationGas = async <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined
>(
client: Client<TTransport, TChain, TAccount, BundlerRpcSchema>,
args: EstimateUserOperationGasParameters
): Promise<EstimateUserOperationGasReturnType> => {
const { userOperation, entryPoint } = args
Expand Down
11 changes: 8 additions & 3 deletions src/actions/bundler/getUserOperationByHash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Address, Hash } from "viem"
import type { Account, Address, Chain, Client, Hash, Transport } from "viem"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { BundlerRpcSchema } from "../../types/bundler.js"
import type { UserOperation } from "../../types/userOperation.js"

export type GetUserOperationByHashParameters = {
Expand Down Expand Up @@ -36,8 +37,12 @@ export type GetUserOperationByHashReturnType = {
* getUserOperationByHash(bundlerClient, {hash: userOpHash})
*
*/
export const getUserOperationByHash = async (
client: BundlerClient,
export const getUserOperationByHash = async <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined
>(
client: Client<TTransport, TChain, TAccount, BundlerRpcSchema>,
{ hash }: GetUserOperationByHashParameters
): Promise<GetUserOperationByHashReturnType> => {
const params: [Hash] = [hash]
Expand Down
19 changes: 16 additions & 3 deletions src/actions/bundler/getUserOperationReceipt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import type { Address, Hash, Hex } from "viem"
import type {
Account,
Address,
Chain,
Client,
Hash,
Hex,
Transport
} from "viem"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { BundlerRpcSchema } from "../../types/bundler.js"
import type { TStatus } from "../../types/userOperation.js"
import { transactionReceiptStatus } from "../../utils/deepHexlify.js"

Expand Down Expand Up @@ -62,8 +71,12 @@ export type GetUserOperationReceiptReturnType = {
* getUserOperationReceipt(bundlerClient, {hash: userOpHash})
*
*/
export const getUserOperationReceipt = async (
client: BundlerClient,
export const getUserOperationReceipt = async <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined
>(
client: Client<TTransport, TChain, TAccount, BundlerRpcSchema>,
{ hash }: GetUserOperationReceiptParameters
): Promise<GetUserOperationReceiptReturnType | null> => {
const params: [Hash] = [hash]
Expand Down
11 changes: 8 additions & 3 deletions src/actions/bundler/sendUserOperation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Address, Hash } from "viem"
import type { Account, Address, Chain, Client, Hash, Transport } from "viem"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { BundlerRpcSchema } from "../../types/bundler.js"
import type {
UserOperation,
UserOperationWithBigIntAsHex
Expand Down Expand Up @@ -36,8 +37,12 @@ export type SendUserOperationParameters = {
*
* // Return '0xe9fad2cd67f9ca1d0b7a6513b2a42066784c8df938518da2b51bb8cc9a89ea34'
*/
export const sendUserOperation = async (
client: BundlerClient,
export const sendUserOperation = async <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account | undefined = Account | undefined
>(
client: Client<TTransport, TChain, TAccount, BundlerRpcSchema>,
args: SendUserOperationParameters
): Promise<Hash> => {
const { userOperation, entryPoint } = args
Expand Down
Loading