near-safe 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,53 +2,60 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TransactionManager = void 0;
4
4
  const near_ca_1 = require("near-ca");
5
+ const viem_1 = require("viem");
5
6
  const bundler_1 = require("./lib/bundler");
6
- const util_1 = require("./util");
7
- const ethers_multisend_1 = require("ethers-multisend");
7
+ const multisend_1 = require("./lib/multisend");
8
8
  const safe_1 = require("./lib/safe");
9
+ const util_1 = require("./util");
9
10
  class TransactionManager {
10
- constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress, entryPointAddress, safeSaltNonce) {
11
+ constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce) {
11
12
  this.nearAdapter = nearAdapter;
12
13
  this.safePack = safePack;
13
14
  this.pimlicoKey = pimlicoKey;
14
- this.entryPointAddress = entryPointAddress;
15
15
  this.setup = setup;
16
16
  this.address = safeAddress;
17
17
  this.safeSaltNonce = safeSaltNonce;
18
18
  this.deployedChains = new Set();
19
19
  }
20
20
  static async create(config) {
21
- const { nearAdapter, pimlicoKey } = config;
22
- const safePack = await safe_1.ContractSuite.init();
21
+ const { pimlicoKey } = config;
22
+ const [nearAdapter, safePack] = await Promise.all([
23
+ (0, near_ca_1.setupAdapter)({ ...config }),
24
+ safe_1.ContractSuite.init(),
25
+ ]);
23
26
  console.log(`Near Adapter: ${nearAdapter.nearAccountId()} <> ${nearAdapter.address}`);
24
- const setup = await safePack.getSetup([nearAdapter.address]);
27
+ const setup = safePack.getSetup([nearAdapter.address]);
25
28
  const safeAddress = await safePack.addressForSetup(setup, config.safeSaltNonce);
26
- const entryPointAddress = (await safePack.entryPoint.getAddress());
27
29
  console.log(`Safe Address: ${safeAddress}`);
28
- return new TransactionManager(nearAdapter, safePack, pimlicoKey, setup, safeAddress, entryPointAddress, config.safeSaltNonce || "0");
30
+ return new TransactionManager(nearAdapter, safePack, pimlicoKey, setup, safeAddress, config.safeSaltNonce || "0");
29
31
  }
30
32
  get mpcAddress() {
31
33
  return this.nearAdapter.address;
32
34
  }
35
+ get mpcContractId() {
36
+ return this.nearAdapter.mpcContract.contract.contractId;
37
+ }
33
38
  async getBalance(chainId) {
34
- const provider = near_ca_1.Network.fromChainId(chainId).client;
35
- return await provider.getBalance({ address: this.address });
39
+ return await (0, util_1.getClient)(chainId).getBalance({ address: this.address });
36
40
  }
37
41
  bundlerForChainId(chainId) {
38
- return new bundler_1.Erc4337Bundler(this.entryPointAddress, this.pimlicoKey, chainId);
42
+ return new bundler_1.Erc4337Bundler(this.safePack.entryPoint.address, this.pimlicoKey, chainId);
39
43
  }
40
44
  async buildTransaction(args) {
41
45
  const { transactions, usePaymaster, chainId } = args;
42
- const bundler = this.bundlerForChainId(chainId);
43
- const gasFees = (await bundler.getGasPrice()).fast;
44
- // Build Singular MetaTransaction for Multisend from transaction list.
45
46
  if (transactions.length === 0) {
46
47
  throw new Error("Empty transaction set!");
47
48
  }
48
- const tx = transactions.length > 1 ? (0, ethers_multisend_1.encodeMulti)(transactions) : transactions[0];
49
- const safeNotDeployed = !(await this.safeDeployed(chainId));
50
- const rawUserOp = await this.safePack.buildUserOp(tx, this.address, gasFees, this.setup, safeNotDeployed, this.safeSaltNonce);
51
- const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed);
49
+ const bundler = this.bundlerForChainId(chainId);
50
+ const [gasFees, nonce, safeDeployed] = await Promise.all([
51
+ bundler.getGasPrice(),
52
+ this.safePack.getNonce(this.address, chainId),
53
+ this.safeDeployed(chainId),
54
+ ]);
55
+ // Build Singular MetaTransaction for Multisend from transaction list.
56
+ const tx = transactions.length > 1 ? (0, multisend_1.encodeMulti)(transactions) : transactions[0];
57
+ const rawUserOp = await this.safePack.buildUserOp(nonce, tx, this.address, gasFees.fast, this.setup, !safeDeployed, this.safeSaltNonce);
58
+ const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, !safeDeployed);
52
59
  const unsignedUserOp = { ...rawUserOp, ...paymasterData };
53
60
  return unsignedUserOp;
54
61
  }
@@ -93,11 +100,11 @@ class TransactionManager {
93
100
  return userOpReceipt;
94
101
  }
95
102
  async safeDeployed(chainId) {
103
+ // Early exit if already known.
96
104
  if (chainId in this.deployedChains) {
97
105
  return true;
98
106
  }
99
- const provider = near_ca_1.Network.fromChainId(chainId).client;
100
- const deployed = (await provider.getCode({ address: this.address })) !== "0x";
107
+ const deployed = await (0, util_1.isContract)(this.address, chainId);
101
108
  if (deployed) {
102
109
  this.deployedChains.add(chainId);
103
110
  }
@@ -107,7 +114,7 @@ class TransactionManager {
107
114
  return {
108
115
  to: this.address,
109
116
  value: "0",
110
- data: this.safePack.singleton.interface.encodeFunctionData("addOwnerWithThreshold", [address, 1]),
117
+ data: this.safePack.addOwnerData(address),
111
118
  };
112
119
  }
113
120
  async safeSufficientlyFunded(chainId, transactions, gasCost) {
@@ -118,5 +125,20 @@ class TransactionManager {
118
125
  const safeBalance = await this.getBalance(chainId);
119
126
  return txValue + gasCost < safeBalance;
120
127
  }
128
+ async broadcastEvm(chainId, outcome, unsignedUserOp) {
129
+ const signature = (0, util_1.packSignature)((0, viem_1.serializeSignature)((0, near_ca_1.signatureFromOutcome)(outcome)));
130
+ try {
131
+ return {
132
+ signature,
133
+ receipt: await this.executeTransaction(chainId, {
134
+ ...unsignedUserOp,
135
+ signature,
136
+ }),
137
+ };
138
+ }
139
+ catch (error) {
140
+ throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
141
+ }
142
+ }
121
143
  }
122
144
  exports.TransactionManager = TransactionManager;
@@ -79,4 +79,14 @@ export interface GasPrice {
79
79
  maxFeePerGas: Hex;
80
80
  maxPriorityFeePerGas: Hex;
81
81
  }
82
+ export declare enum OperationType {
83
+ Call = 0,
84
+ DelegateCall = 1
85
+ }
86
+ export interface MetaTransaction {
87
+ readonly to: string;
88
+ readonly value: string;
89
+ readonly data: string;
90
+ readonly operation?: OperationType;
91
+ }
82
92
  export {};
package/dist/cjs/types.js CHANGED
@@ -1,2 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OperationType = void 0;
4
+ var OperationType;
5
+ (function (OperationType) {
6
+ OperationType[OperationType["Call"] = 0] = "Call";
7
+ OperationType[OperationType["DelegateCall"] = 1] = "DelegateCall";
8
+ })(OperationType || (exports.OperationType = OperationType = {}));
@@ -1,10 +1,11 @@
1
- import { PaymasterData } from "./types.js";
2
- import { MetaTransaction } from "ethers-multisend";
3
- import { Hex } from "viem";
1
+ import { Address, Hex, PublicClient } from "viem";
2
+ import { PaymasterData, MetaTransaction } from "./types";
4
3
  export declare const PLACEHOLDER_SIG: `0x${string}`;
5
4
  type IntLike = Hex | bigint | string | number;
6
5
  export declare const packGas: (hi: IntLike, lo: IntLike) => string;
7
6
  export declare function packSignature(signature: `0x${string}`, validFrom?: number, validTo?: number): Hex;
8
7
  export declare function packPaymasterData(data: PaymasterData): Hex;
9
8
  export declare function containsValue(transactions: MetaTransaction[]): boolean;
9
+ export declare function isContract(address: Address, chainId: number): Promise<boolean>;
10
+ export declare function getClient(chainId: number): PublicClient;
10
11
  export {};
package/dist/cjs/util.js CHANGED
@@ -4,6 +4,9 @@ exports.packGas = exports.PLACEHOLDER_SIG = void 0;
4
4
  exports.packSignature = packSignature;
5
5
  exports.packPaymasterData = packPaymasterData;
6
6
  exports.containsValue = containsValue;
7
+ exports.isContract = isContract;
8
+ exports.getClient = getClient;
9
+ const near_ca_1 = require("near-ca");
7
10
  const viem_1 = require("viem");
8
11
  exports.PLACEHOLDER_SIG = (0, viem_1.encodePacked)(["uint48", "uint48"], [0, 0]);
9
12
  const packGas = (hi, lo) => (0, viem_1.encodePacked)(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
@@ -24,3 +27,9 @@ function packPaymasterData(data) {
24
27
  function containsValue(transactions) {
25
28
  return transactions.some((tx) => tx.value !== "0");
26
29
  }
30
+ async function isContract(address, chainId) {
31
+ return (await getClient(chainId).getCode({ address })) !== undefined;
32
+ }
33
+ function getClient(chainId) {
34
+ return near_ca_1.Network.fromChainId(chainId).client;
35
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "./tx-manager";
2
2
  export * from "./types";
3
3
  export * from "./util";
4
+ export { Network, BaseTx, SignRequestData, populateTx } from "near-ca";
package/dist/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./tx-manager";
2
2
  export * from "./types";
3
3
  export * from "./util";
4
+ export { Network, populateTx } from "near-ca";
@@ -1,15 +1,37 @@
1
- import { ethers } from "ethers";
2
- import { GasPrices, PaymasterData, UnsignedUserOperation, UserOperation, UserOperationReceipt } from "../types.js";
1
+ import { Address, Hash, PublicClient, Transport } from "viem";
2
+ import { GasPrices, PaymasterData, UnsignedUserOperation, UserOperation, UserOperationReceipt } from "../types";
3
+ type BundlerRpcSchema = [
4
+ {
5
+ Method: "pm_sponsorUserOperation";
6
+ Parameters: [UnsignedUserOperation, Address];
7
+ ReturnType: PaymasterData;
8
+ },
9
+ {
10
+ Method: "eth_sendUserOperation";
11
+ Parameters: [UserOperation, Address];
12
+ ReturnType: Hash;
13
+ },
14
+ {
15
+ Method: "pimlico_getUserOperationGasPrice";
16
+ Parameters: [];
17
+ ReturnType: GasPrices;
18
+ },
19
+ {
20
+ Method: "eth_getUserOperationReceipt";
21
+ Parameters: [Hash];
22
+ ReturnType: UserOperationReceipt | null;
23
+ }
24
+ ];
3
25
  export declare class Erc4337Bundler {
4
- provider: ethers.JsonRpcProvider;
5
- entryPointAddress: string;
26
+ client: PublicClient<Transport, undefined, undefined, BundlerRpcSchema>;
27
+ entryPointAddress: Address;
6
28
  apiKey: string;
7
29
  chainId: number;
8
- constructor(entryPointAddress: string, apiKey: string, chainId: number);
9
- client(chainId: number): ethers.JsonRpcProvider;
30
+ constructor(entryPointAddress: Address, apiKey: string, chainId: number);
10
31
  getPaymasterData(rawUserOp: UnsignedUserOperation, usePaymaster: boolean, safeNotDeployed: boolean): Promise<PaymasterData>;
11
- sendUserOperation(userOp: UserOperation): Promise<string>;
32
+ sendUserOperation(userOp: UserOperation): Promise<Hash>;
12
33
  getGasPrice(): Promise<GasPrices>;
13
- _getUserOpReceiptInner(userOpHash: string): Promise<UserOperationReceipt | null>;
14
- getUserOpReceipt(userOpHash: string): Promise<UserOperationReceipt>;
34
+ getUserOpReceipt(userOpHash: Hash): Promise<UserOperationReceipt>;
35
+ private _getUserOpReceiptInner;
15
36
  }
37
+ export {};
@@ -1,12 +1,10 @@
1
- // TODO: Ethers dependency is only for Generic HTTP Provider
2
- import { ethers } from "ethers";
3
- import { PLACEHOLDER_SIG } from "../util.js";
4
- import { toHex } from "viem";
1
+ import { createPublicClient, http, rpcSchema, toHex, RpcError, HttpRequestError, } from "viem";
2
+ import { PLACEHOLDER_SIG } from "../util";
5
3
  function bundlerUrl(chainId, apikey) {
6
4
  return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${apikey}`;
7
5
  }
8
6
  export class Erc4337Bundler {
9
- provider;
7
+ client;
10
8
  entryPointAddress;
11
9
  apiKey;
12
10
  chainId;
@@ -14,41 +12,37 @@ export class Erc4337Bundler {
14
12
  this.entryPointAddress = entryPointAddress;
15
13
  this.apiKey = apiKey;
16
14
  this.chainId = chainId;
17
- this.provider = new ethers.JsonRpcProvider(bundlerUrl(chainId, apiKey));
18
- }
19
- client(chainId) {
20
- return new ethers.JsonRpcProvider(bundlerUrl(chainId, this.apiKey));
15
+ this.client = createPublicClient({
16
+ transport: http(bundlerUrl(chainId, this.apiKey)),
17
+ rpcSchema: rpcSchema(),
18
+ });
21
19
  }
22
20
  async getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed) {
23
21
  // TODO: Keep this option out of the bundler
24
22
  if (usePaymaster) {
25
23
  console.log("Requesting paymaster data...");
26
- const data = this.provider.send("pm_sponsorUserOperation", [
27
- { ...rawUserOp, signature: PLACEHOLDER_SIG },
28
- this.entryPointAddress,
29
- ]);
30
- return data;
24
+ return handleRequest(() => this.client.request({
25
+ method: "pm_sponsorUserOperation",
26
+ params: [
27
+ { ...rawUserOp, signature: PLACEHOLDER_SIG },
28
+ this.entryPointAddress,
29
+ ],
30
+ }));
31
31
  }
32
32
  return defaultPaymasterData(safeNotDeployed);
33
33
  }
34
34
  async sendUserOperation(userOp) {
35
- try {
36
- const userOpHash = await this.provider.send("eth_sendUserOperation", [
37
- userOp,
38
- this.entryPointAddress,
39
- ]);
40
- return userOpHash;
41
- }
42
- catch (err) {
43
- const error = err.error;
44
- throw new Error(`Failed to send user op with: ${error.message}`);
45
- }
35
+ return handleRequest(() => this.client.request({
36
+ method: "eth_sendUserOperation",
37
+ params: [userOp, this.entryPointAddress],
38
+ }));
39
+ // throw new Error(`Failed to send user op with: ${error.message}`);
46
40
  }
47
41
  async getGasPrice() {
48
- return this.provider.send("pimlico_getUserOperationGasPrice", []);
49
- }
50
- async _getUserOpReceiptInner(userOpHash) {
51
- return this.provider.send("eth_getUserOperationReceipt", [userOpHash]);
42
+ return handleRequest(() => this.client.request({
43
+ method: "pimlico_getUserOperationGasPrice",
44
+ params: [],
45
+ }));
52
46
  }
53
47
  async getUserOpReceipt(userOpHash) {
54
48
  let userOpReceipt = null;
@@ -59,6 +53,31 @@ export class Erc4337Bundler {
59
53
  }
60
54
  return userOpReceipt;
61
55
  }
56
+ async _getUserOpReceiptInner(userOpHash) {
57
+ return handleRequest(() => this.client.request({
58
+ method: "eth_getUserOperationReceipt",
59
+ params: [userOpHash],
60
+ }));
61
+ }
62
+ }
63
+ async function handleRequest(clientMethod) {
64
+ try {
65
+ return await clientMethod();
66
+ }
67
+ catch (error) {
68
+ if (error instanceof HttpRequestError) {
69
+ if (error.status === 401) {
70
+ throw new Error("Unauthorized request. Please check your API key.");
71
+ }
72
+ else {
73
+ console.error(`Request failed with status ${error.status}: ${error.message}`);
74
+ }
75
+ }
76
+ else if (error instanceof RpcError) {
77
+ throw new Error(`Failed to send user op with: ${error.message}`);
78
+ }
79
+ throw new Error(`Unexpected error ${error instanceof Error ? error.message : String(error)}`);
80
+ }
62
81
  }
63
82
  // TODO(bh2smith) Should probably get reasonable estimates here:
64
83
  const defaultPaymasterData = (safeNotDeployed) => {
@@ -0,0 +1,3 @@
1
+ import { MetaTransaction } from "../types";
2
+ export declare const MULTI_SEND_ABI: string[];
3
+ export declare function encodeMulti(transactions: readonly MetaTransaction[], multiSendContractAddress?: string): MetaTransaction;
@@ -0,0 +1,36 @@
1
+ import { encodeFunctionData, encodePacked, parseAbi, size, } from "viem";
2
+ import { OperationType } from "../types";
3
+ export const MULTI_SEND_ABI = ["function multiSend(bytes memory transactions)"];
4
+ const MULTISEND_141 = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526";
5
+ const MULTISEND_CALLONLY_141 = "0x9641d764fc13c8B624c04430C7356C1C7C8102e2";
6
+ /// Encodes the transaction as packed bytes of:
7
+ /// - `operation` as a `uint8` with `0` for a `call` or `1` for a `delegatecall` (=> 1 byte),
8
+ /// - `to` as an `address` (=> 20 bytes),
9
+ /// - `value` as a `uint256` (=> 32 bytes),
10
+ /// - length of `data` as a `uint256` (=> 32 bytes),
11
+ /// - `data` as `bytes`.
12
+ const encodeMetaTx = (tx) => encodePacked(["uint8", "address", "uint256", "uint256", "bytes"], [
13
+ tx.operation || OperationType.Call,
14
+ tx.to,
15
+ BigInt(tx.value),
16
+ BigInt(size(tx.data)),
17
+ tx.data,
18
+ ]);
19
+ const remove0x = (hexString) => hexString.slice(2);
20
+ // Encodes a batch of module transactions into a single multiSend module transaction.
21
+ // A module transaction is an object with fields corresponding to a Gnosis Safe's (i.e., Zodiac IAvatar's) `execTransactionFromModule` method parameters.
22
+ export function encodeMulti(transactions, multiSendContractAddress = transactions.some((t) => t.operation === OperationType.DelegateCall)
23
+ ? MULTISEND_141
24
+ : MULTISEND_CALLONLY_141) {
25
+ const encodedTransactions = "0x" + transactions.map(encodeMetaTx).map(remove0x).join("");
26
+ return {
27
+ operation: OperationType.DelegateCall,
28
+ to: multiSendContractAddress,
29
+ value: "0x00",
30
+ data: encodeFunctionData({
31
+ abi: parseAbi(MULTI_SEND_ABI),
32
+ functionName: "multiSend",
33
+ args: [encodedTransactions],
34
+ }),
35
+ };
36
+ }
@@ -1,25 +1,27 @@
1
- import { ethers } from "ethers";
2
- import { GasPrice, UnsignedUserOperation, UserOperation } from "../types";
3
- import { MetaTransaction } from "ethers-multisend";
4
- import { Address, Hash, Hex } from "viem";
1
+ import { Address, Hash, Hex, ParseAbi, PublicClient } from "viem";
2
+ import { GasPrice, MetaTransaction, UnsignedUserOperation, UserOperation } from "../types";
3
+ interface DeploymentData {
4
+ abi: unknown[] | ParseAbi<readonly string[]>;
5
+ address: `0x${string}`;
6
+ }
5
7
  /**
6
8
  * All contracts used in account creation & execution
7
9
  */
8
10
  export declare class ContractSuite {
9
- provider: ethers.JsonRpcProvider;
10
- singleton: ethers.Contract;
11
- proxyFactory: ethers.Contract;
12
- m4337: ethers.Contract;
13
- moduleSetup: ethers.Contract;
14
- entryPoint: ethers.Contract;
15
- constructor(provider: ethers.JsonRpcProvider, singleton: ethers.Contract, proxyFactory: ethers.Contract, m4337: ethers.Contract, moduleSetup: ethers.Contract, entryPoint: ethers.Contract);
11
+ dummyClient: PublicClient;
12
+ singleton: DeploymentData;
13
+ proxyFactory: DeploymentData;
14
+ m4337: DeploymentData;
15
+ moduleSetup: DeploymentData;
16
+ entryPoint: DeploymentData;
17
+ constructor(client: PublicClient, singleton: DeploymentData, proxyFactory: DeploymentData, m4337: DeploymentData, moduleSetup: DeploymentData, entryPoint: DeploymentData);
16
18
  static init(): Promise<ContractSuite>;
17
- addressForSetup(setup: ethers.BytesLike, saltNonce?: string): Promise<Address>;
18
- getSetup(owners: string[]): Promise<Hex>;
19
+ addressForSetup(setup: Hex, saltNonce?: string): Promise<Address>;
20
+ getSetup(owners: string[]): Hex;
21
+ addOwnerData(newOwner: Address): Hex;
19
22
  getOpHash(unsignedUserOp: UserOperation): Promise<Hash>;
20
- factoryDataForSetup(safeNotDeployed: boolean, setup: string, safeSaltNonce: string): {
21
- factory?: Address;
22
- factoryData?: Hex;
23
- };
24
- buildUserOp(txData: MetaTransaction, safeAddress: Address, feeData: GasPrice, setup: string, safeNotDeployed: boolean, safeSaltNonce: string): Promise<UnsignedUserOperation>;
23
+ private factoryDataForSetup;
24
+ buildUserOp(nonce: bigint, txData: MetaTransaction, safeAddress: Address, feeData: GasPrice, setup: string, safeNotDeployed: boolean, safeSaltNonce: string): Promise<UnsignedUserOperation>;
25
+ getNonce(address: Address, chainId: number): Promise<bigint>;
25
26
  }
27
+ export {};