Skip to content

Commit

Permalink
Partial Migration to Viem (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored Sep 14, 2024
1 parent 98a00a0 commit e60ddee
Show file tree
Hide file tree
Showing 10 changed files with 2,033 additions and 122 deletions.
12 changes: 12 additions & 0 deletions jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"preset": "ts-jest",
"testEnvironment": "node",
"coverageThreshold": {
"global": {
"branches": 60,
"functions": 60,
"lines": 60,
"statements": 60
}
}
}
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"start": "yarn example",
"example": "tsx examples/send-tx.ts",
"lint": "eslint . --ignore-pattern dist/",
"test": "jest",
"fmt": "prettier --write '{src,examples,tests}/**/*.{js,jsx,ts,tsx}'",
"all": "yarn fmt && yarn lint && yarn build"
},
Expand All @@ -44,16 +45,21 @@
"ethers": "^6.13.1",
"ethers-multisend": "^3.1.0",
"near-api-js": "^5.0.0",
"near-ca": "^0.5.2"
"near-ca": "^0.5.2",
"viem": "^2.16.5",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^22.3.0",
"@types/yargs": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"dotenv": "^16.4.5",
"eslint": "^9.6.0",
"jest": "^29.7.0",
"prettier": "^3.3.2",
"ts-jest": "^29.1.5",
"tsx": "^4.16.0",
"typescript": "^5.5.2",
"yargs": "^17.7.2"
Expand Down
11 changes: 6 additions & 5 deletions src/lib/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
UnsignedUserOperation,
UserOperation,
UserOperationReceipt,
} from "../types";
import { PLACEHOLDER_SIG } from "../util";
} from "../types.js";
import { PLACEHOLDER_SIG } from "../util.js";
import { toHex } from "viem";

export class Erc4337Bundler {
provider: ethers.JsonRpcProvider;
Expand Down Expand Up @@ -71,8 +72,8 @@ export class Erc4337Bundler {
// TODO(bh2smith) Should probably get reasonable estimates here:
const defaultPaymasterData = (safeNotDeployed: boolean): PaymasterData => {
return {
verificationGasLimit: ethers.toBeHex(safeNotDeployed ? 500000 : 100000),
callGasLimit: ethers.toBeHex(100000),
preVerificationGas: ethers.toBeHex(100000),
verificationGasLimit: toHex(safeNotDeployed ? 500000 : 100000),
callGasLimit: toHex(100000),
preVerificationGas: toHex(100000),
};
};
18 changes: 0 additions & 18 deletions src/lib/near.ts

This file was deleted.

24 changes: 11 additions & 13 deletions src/lib/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util";
import { GasPrice, UnsignedUserOperation, UserOperation } from "../types";
import { MetaTransaction } from "ethers-multisend";
import { Address, Hash, Hex } from "viem";

/**
* All contracts used in account creation & execution
Expand Down Expand Up @@ -86,7 +87,7 @@ export class ContractSuite {
async addressForSetup(
setup: ethers.BytesLike,
saltNonce?: string
): Promise<string> {
): Promise<Address> {
// bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
// cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
const salt = ethers.keccak256(
Expand All @@ -109,10 +110,10 @@ export class ContractSuite {
await this.proxyFactory.getAddress(),
salt,
ethers.keccak256(initCode)
);
) as Address;
}

async getSetup(owners: string[]): Promise<string> {
async getSetup(owners: string[]): Promise<Hex> {
const setup = await this.singleton.interface.encodeFunctionData("setup", [
owners,
1, // We use sign threshold of 1.
Expand All @@ -125,13 +126,10 @@ export class ContractSuite {
0,
ethers.ZeroAddress,
]);
return setup;
return setup as Hex;
}

async getOpHash(
unsignedUserOp: UserOperation
// paymasterData: PaymasterData
): Promise<string> {
async getOpHash(unsignedUserOp: UserOperation): Promise<Hash> {
return this.m4337.getOperationHash({
...unsignedUserOp,
initCode: unsignedUserOp.factory
Expand All @@ -157,21 +155,21 @@ export class ContractSuite {
safeNotDeployed: boolean,
setup: string,
safeSaltNonce: string
): { factory?: ethers.AddressLike; factoryData?: string } {
): { factory?: Address; factoryData?: Hex } {
return safeNotDeployed
? {
factory: this.proxyFactory.target,
factory: this.proxyFactory.target as Address,
factoryData: this.proxyFactory.interface.encodeFunctionData(
"createProxyWithNonce",
[this.singleton.target, setup, safeSaltNonce]
),
) as Hex,
}
: {};
}

async buildUserOp(
txData: MetaTransaction,
safeAddress: ethers.AddressLike,
safeAddress: Address,
feeData: GasPrice,
setup: string,
safeNotDeployed: boolean,
Expand All @@ -187,7 +185,7 @@ export class ContractSuite {
BigInt(txData.value),
txData.data,
txData.operation || 0,
]),
]) as Hex,
...feeData,
};
return rawUserOp;
Expand Down
14 changes: 7 additions & 7 deletions src/tx-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import { ethers } from "ethers";
import { NearEthAdapter, NearEthTxData, BaseTx, Network } from "near-ca";
import { Erc4337Bundler } from "./lib/bundler";
import { packSignature } from "./util";
import { getNearSignature } from "./lib/near";
import { UserOperation, UserOperationReceipt } from "./types";
import { MetaTransaction, encodeMulti } from "ethers-multisend";
import { ContractSuite } from "./lib/safe";
import { Address, Hash, Hex } from "viem";

export class TransactionManager {
readonly provider: ethers.JsonRpcProvider;
readonly nearAdapter: NearEthAdapter;
private safePack: ContractSuite;
private bundler: Erc4337Bundler;
private setup: string;
readonly address: string;
readonly address: Address;
readonly chainId: number;
private safeSaltNonce: string;
private _safeNotDeployed: boolean;
Expand All @@ -25,7 +25,7 @@ export class TransactionManager {
bundler: Erc4337Bundler,
setup: string,
chainId: number,
safeAddress: string,
safeAddress: Address,
safeSaltNonce: string,
safeNotDeployed: boolean
) {
Expand Down Expand Up @@ -94,7 +94,7 @@ export class TransactionManager {
return this._safeNotDeployed;
}

get mpcAddress(): `0x${string}` {
get mpcAddress(): Address {
return this.nearAdapter.address;
}

Expand Down Expand Up @@ -135,12 +135,12 @@ export class TransactionManager {
return unsignedUserOp;
}

async signTransaction(safeOpHash: string): Promise<string> {
const signature = await getNearSignature(this.nearAdapter, safeOpHash);
async signTransaction(safeOpHash: Hex): Promise<Hex> {
const signature = await this.nearAdapter.sign(safeOpHash);
return packSignature(signature);
}

async opHash(userOp: UserOperation): Promise<string> {
async opHash(userOp: UserOperation): Promise<Hash> {
return this.safePack.getOpHash(userOp);
}
async encodeSignRequest(tx: BaseTx): Promise<NearEthTxData> {
Expand Down
51 changes: 24 additions & 27 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { ethers } from "ethers";
import { Address, Hex } from "viem";

export interface UnsignedUserOperation {
sender: ethers.AddressLike;
sender: Address;
nonce: string;
factory?: ethers.AddressLike;
factoryData?: ethers.BytesLike;
callData: string;
maxPriorityFeePerGas: string;
maxFeePerGas: string;
factory?: Address;
factoryData?: Hex;
callData: Hex;
maxPriorityFeePerGas: Hex;
maxFeePerGas: Hex;
}

/**
* Supported Representation of UserOperation for EntryPoint v0.7
*/
export interface UserOperation extends UnsignedUserOperation {
verificationGasLimit: string;
callGasLimit: string;
preVerificationGas: string;
signature?: string;
verificationGasLimit: Hex;
callGasLimit: Hex;
preVerificationGas: Hex;
signature?: Hex;
}

export interface PaymasterData {
paymaster?: string;
paymasterData?: string;
paymasterVerificationGasLimit?: string;
paymasterPostOpGasLimit?: string;
verificationGasLimit: string;
callGasLimit: string;
preVerificationGas: string;
paymaster?: Address;
paymasterData?: Hex;
paymasterVerificationGasLimit?: Hex;
paymasterPostOpGasLimit?: Hex;
verificationGasLimit: Hex;
callGasLimit: Hex;
preVerificationGas: Hex;
}

export interface UserOptions {
Expand All @@ -37,10 +37,7 @@ export interface UserOptions {
recoveryAddress?: string;
}

export type TStatus = "success" | "reverted";
export type Address = ethers.AddressLike;
export type Hex = `0x${string}`;
export type Hash = `0x${string}`;
export type TxStatus = "success" | "reverted";

interface Log {
logIndex: string;
Expand All @@ -56,20 +53,20 @@ interface Log {
interface Receipt {
transactionHash: Hex;
transactionIndex: bigint;
blockHash: Hash;
blockHash: Hex;
blockNumber: bigint;
from: Address;
to: Address | null;
to?: Address;
cumulativeGasUsed: bigint;
status: TStatus;
status: TxStatus;
gasUsed: bigint;
contractAddress: Address | null;
contractAddress?: Address;
logsBloom: Hex;
effectiveGasPrice: bigint;
}

export type UserOperationReceipt = {
userOpHash: Hash;
userOpHash: Hex;
entryPoint: Address;
sender: Address;
nonce: bigint;
Expand Down
41 changes: 19 additions & 22 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import { ethers } from "ethers";
import { PaymasterData } from "./types";
import { PaymasterData } from "./types.js";
import { MetaTransaction } from "ethers-multisend";
import { Hex, concatHex, encodePacked, toHex } from "viem";

export const PLACEHOLDER_SIG = ethers.solidityPacked(
["uint48", "uint48"],
[0, 0]
);
export const PLACEHOLDER_SIG = encodePacked(["uint48", "uint48"], [0, 0]);

export const packGas = (
hi: ethers.BigNumberish,
lo: ethers.BigNumberish
): string => ethers.solidityPacked(["uint128", "uint128"], [hi, lo]);
type IntLike = Hex | bigint | string | number;

export const packGas = (hi: IntLike, lo: IntLike): string =>
encodePacked(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);

export function packSignature(
signature: string,
signature: `0x${string}`,
validFrom: number = 0,
validTo: number = 0
): string {
return ethers.solidityPacked(
): Hex {
return encodePacked(
["uint48", "uint48", "bytes"],
[validFrom, validTo, signature]
);
}

export function packPaymasterData(data: PaymasterData): string {
return data.paymaster
? ethers.hexlify(
ethers.concat([
data.paymaster,
ethers.toBeHex(data.paymasterVerificationGasLimit || "0x", 16),
ethers.toBeHex(data.paymasterPostOpGasLimit || "0x", 16),
export function packPaymasterData(data: PaymasterData): Hex {
return (
data.paymaster
? concatHex([
data.paymaster!,
toHex(BigInt(data.paymasterVerificationGasLimit || 0n), { size: 16 }),
toHex(BigInt(data.paymasterPostOpGasLimit || 0n), { size: 16 }),
data.paymasterData || "0x",
])
)
: "0x";
: "0x"
) as Hex;
}

export function containsValue(transactions: MetaTransaction[]): boolean {
Expand Down
Loading

0 comments on commit e60ddee

Please sign in to comment.