near-safe 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 {};