near-safe 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,19 +1,20 @@
1
1
  import { getProxyFactoryDeployment, getSafeL2SingletonDeployment, } from "@safe-global/safe-deployments";
2
2
  import { getSafe4337ModuleDeployment, getSafeModuleSetupDeployment, } from "@safe-global/safe-modules-deployments";
3
- import { ethers } from "ethers";
4
- import { PLACEHOLDER_SIG, packGas, packPaymasterData } from "../util";
3
+ import { encodeFunctionData, encodePacked, getCreate2Address, keccak256, parseAbi, toHex, zeroAddress, } from "viem";
4
+ import { PLACEHOLDER_SIG, getClient, packGas, packPaymasterData, } from "../util";
5
5
  /**
6
6
  * All contracts used in account creation & execution
7
7
  */
8
8
  export class ContractSuite {
9
- provider;
9
+ // Used only for stateless contract reads.
10
+ dummyClient;
10
11
  singleton;
11
12
  proxyFactory;
12
13
  m4337;
13
14
  moduleSetup;
14
15
  entryPoint;
15
- constructor(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint) {
16
- this.provider = provider;
16
+ constructor(client, singleton, proxyFactory, m4337, moduleSetup, entryPoint) {
17
+ this.dummyClient = client;
17
18
  this.singleton = singleton;
18
19
  this.proxyFactory = proxyFactory;
19
20
  this.m4337 = m4337;
@@ -22,108 +23,154 @@ export class ContractSuite {
22
23
  }
23
24
  static async init() {
24
25
  // TODO - this is a cheeky hack.
25
- const provider = new ethers.JsonRpcProvider("https://rpc2.sepolia.org");
26
- const safeDeployment = (fn) => getDeployment(fn, { provider, version: "1.4.1" });
26
+ const client = getClient(11155111);
27
+ const safeDeployment = (fn) => getDeployment(fn, { version: "1.4.1" });
27
28
  const m4337Deployment = async (fn) => {
28
- return getDeployment(fn, { provider, version: "0.3.0" });
29
+ return getDeployment(fn, { version: "0.3.0" });
29
30
  };
30
- // Need this first to get entryPoint address
31
- const m4337 = await m4337Deployment(getSafe4337ModuleDeployment);
32
- const [singleton, proxyFactory, moduleSetup, supportedEntryPoint] = await Promise.all([
31
+ const [singleton, proxyFactory, moduleSetup, m4337] = await Promise.all([
33
32
  safeDeployment(getSafeL2SingletonDeployment),
34
33
  safeDeployment(getProxyFactoryDeployment),
35
34
  m4337Deployment(getSafeModuleSetupDeployment),
36
- m4337.SUPPORTED_ENTRYPOINT(),
35
+ m4337Deployment(getSafe4337ModuleDeployment),
37
36
  ]);
38
- const entryPoint = new ethers.Contract(supportedEntryPoint, ["function getNonce(address, uint192 key) view returns (uint256 nonce)"], provider);
39
- console.log("Initialized ERC4337 & Safe Module Contracts:", {
40
- singleton: await singleton.getAddress(),
41
- proxyFactory: await proxyFactory.getAddress(),
42
- m4337: await m4337.getAddress(),
43
- moduleSetup: await moduleSetup.getAddress(),
44
- entryPoint: await entryPoint.getAddress(),
37
+ // console.log("Initialized ERC4337 & Safe Module Contracts:", {
38
+ // singleton: await singleton.getAddress(),
39
+ // proxyFactory: await proxyFactory.getAddress(),
40
+ // m4337: await m4337.getAddress(),
41
+ // moduleSetup: await moduleSetup.getAddress(),
42
+ // entryPoint: await entryPoint.getAddress(),
43
+ // });
44
+ return new ContractSuite(client, singleton, proxyFactory, m4337, moduleSetup,
45
+ // EntryPoint:
46
+ {
47
+ address: (await client.readContract({
48
+ address: m4337.address,
49
+ abi: m4337.abi,
50
+ functionName: "SUPPORTED_ENTRYPOINT",
51
+ })),
52
+ abi: parseAbi([
53
+ "function getNonce(address, uint192 key) view returns (uint256 nonce)",
54
+ ]),
45
55
  });
46
- return new ContractSuite(provider, singleton, proxyFactory, m4337, moduleSetup, entryPoint);
47
56
  }
48
57
  async addressForSetup(setup, saltNonce) {
49
58
  // bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
50
59
  // cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
51
- const salt = ethers.keccak256(ethers.solidityPacked(["bytes32", "uint256"], [ethers.keccak256(setup), saltNonce || 0]));
60
+ const salt = keccak256(encodePacked(["bytes32", "uint256"], [keccak256(setup), BigInt(saltNonce || "0")]));
52
61
  // abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
53
62
  // cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L29
54
- const initCode = ethers.solidityPacked(["bytes", "uint256"], [
55
- await this.proxyFactory.proxyCreationCode(),
56
- await this.singleton.getAddress(),
63
+ const initCode = encodePacked(["bytes", "uint256"], [
64
+ (await this.dummyClient.readContract({
65
+ address: this.proxyFactory.address,
66
+ abi: this.proxyFactory.abi,
67
+ functionName: "proxyCreationCode",
68
+ })),
69
+ BigInt(this.singleton.address),
57
70
  ]);
58
- return ethers.getCreate2Address(await this.proxyFactory.getAddress(), salt, ethers.keccak256(initCode));
71
+ return getCreate2Address({
72
+ from: this.proxyFactory.address,
73
+ salt,
74
+ bytecodeHash: keccak256(initCode),
75
+ });
59
76
  }
60
- async getSetup(owners) {
61
- const setup = await this.singleton.interface.encodeFunctionData("setup", [
62
- owners,
63
- 1, // We use sign threshold of 1.
64
- this.moduleSetup.target,
65
- this.moduleSetup.interface.encodeFunctionData("enableModules", [
66
- [this.m4337.target],
67
- ]),
68
- this.m4337.target,
69
- ethers.ZeroAddress,
70
- 0,
71
- ethers.ZeroAddress,
72
- ]);
73
- return setup;
77
+ getSetup(owners) {
78
+ return encodeFunctionData({
79
+ abi: this.singleton.abi,
80
+ functionName: "setup",
81
+ args: [
82
+ owners,
83
+ 1, // We use sign threshold of 1.
84
+ this.moduleSetup.address,
85
+ encodeFunctionData({
86
+ abi: this.moduleSetup.abi,
87
+ functionName: "enableModules",
88
+ args: [[this.m4337.address]],
89
+ }),
90
+ this.m4337.address,
91
+ zeroAddress,
92
+ 0,
93
+ zeroAddress,
94
+ ],
95
+ });
96
+ }
97
+ addOwnerData(newOwner) {
98
+ return encodeFunctionData({
99
+ abi: this.singleton.abi,
100
+ functionName: "addOwnerWithThreshold",
101
+ args: [newOwner, 1],
102
+ });
74
103
  }
75
104
  async getOpHash(unsignedUserOp) {
76
105
  const { factory, factoryData, verificationGasLimit, callGasLimit, maxPriorityFeePerGas, maxFeePerGas, } = unsignedUserOp;
77
- return this.m4337.getOperationHash({
78
- ...unsignedUserOp,
79
- initCode: factory
80
- ? ethers.solidityPacked(["address", "bytes"], [factory, factoryData])
81
- : "0x",
82
- accountGasLimits: packGas(verificationGasLimit, callGasLimit),
83
- gasFees: packGas(maxPriorityFeePerGas, maxFeePerGas),
84
- paymasterAndData: packPaymasterData(unsignedUserOp),
85
- signature: PLACEHOLDER_SIG,
106
+ const opHash = await this.dummyClient.readContract({
107
+ address: this.m4337.address,
108
+ abi: this.m4337.abi,
109
+ functionName: "getOperationHash",
110
+ args: [
111
+ {
112
+ ...unsignedUserOp,
113
+ initCode: factory
114
+ ? encodePacked(["address", "bytes"], [factory, factoryData])
115
+ : "0x",
116
+ accountGasLimits: packGas(verificationGasLimit, callGasLimit),
117
+ gasFees: packGas(maxPriorityFeePerGas, maxFeePerGas),
118
+ paymasterAndData: packPaymasterData(unsignedUserOp),
119
+ signature: PLACEHOLDER_SIG,
120
+ },
121
+ ],
86
122
  });
123
+ return opHash;
87
124
  }
88
125
  factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce) {
89
126
  return safeNotDeployed
90
127
  ? {
91
- factory: this.proxyFactory.target,
92
- factoryData: this.proxyFactory.interface.encodeFunctionData("createProxyWithNonce", [this.singleton.target, setup, safeSaltNonce]),
128
+ factory: this.proxyFactory.address,
129
+ factoryData: encodeFunctionData({
130
+ abi: this.proxyFactory.abi,
131
+ functionName: "createProxyWithNonce",
132
+ args: [this.singleton.address, setup, safeSaltNonce],
133
+ }),
93
134
  }
94
135
  : {};
95
136
  }
96
- async buildUserOp(txData, safeAddress, feeData, setup, safeNotDeployed, safeSaltNonce) {
97
- const rawUserOp = {
137
+ async buildUserOp(nonce, txData, safeAddress, feeData, setup, safeNotDeployed, safeSaltNonce) {
138
+ return {
98
139
  sender: safeAddress,
99
- nonce: ethers.toBeHex(await this.entryPoint.getNonce(safeAddress, 0)),
140
+ nonce: toHex(nonce),
100
141
  ...this.factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce),
101
142
  // <https://github.com/safe-global/safe-modules/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/4337/contracts/Safe4337Module.sol#L172>
102
- callData: this.m4337.interface.encodeFunctionData("executeUserOp", [
103
- txData.to,
104
- BigInt(txData.value),
105
- txData.data,
106
- txData.operation || 0,
107
- ]),
143
+ callData: encodeFunctionData({
144
+ abi: this.m4337.abi,
145
+ functionName: "executeUserOp",
146
+ args: [
147
+ txData.to,
148
+ BigInt(txData.value),
149
+ txData.data,
150
+ txData.operation || 0,
151
+ ],
152
+ }),
108
153
  ...feeData,
109
154
  };
110
- return rawUserOp;
155
+ }
156
+ async getNonce(address, chainId) {
157
+ const nonce = (await getClient(chainId).readContract({
158
+ abi: this.entryPoint.abi,
159
+ address: this.entryPoint.address,
160
+ functionName: "getNonce",
161
+ args: [address, 0],
162
+ }));
163
+ return nonce;
111
164
  }
112
165
  }
113
- async function getDeployment(fn, { provider, version }) {
114
- const { chainId } = await provider.getNetwork();
166
+ async function getDeployment(fn, { version }) {
115
167
  const deployment = fn({ version });
116
168
  if (!deployment) {
117
- throw new Error(`Deployment not found for ${fn.name} version ${version} on chainId ${chainId}`);
118
- }
119
- let address = deployment.networkAddresses[`${chainId}`];
120
- if (!address) {
121
- // console.warn(
122
- // `Deployment asset ${fn.name} not listed on chainId ${chainId}, using likely fallback. For more info visit https://github.com/safe-global/safe-modules-deployments`
123
- // );
124
- // TODO: This is a cheeky hack. Real solution proposed in
125
- // https://github.com/Mintbase/near-safe/issues/42
126
- address = deployment.networkAddresses["11155111"];
169
+ throw new Error(`Deployment not found for ${fn.name} version ${version}`);
127
170
  }
128
- return new ethers.Contract(address, deployment.abi, provider);
171
+ // TODO: maybe call parseAbi on deployment.abi here.
172
+ return {
173
+ address: deployment.networkAddresses["11155111"],
174
+ abi: deployment.abi,
175
+ };
129
176
  }
@@ -1,5 +1,5 @@
1
1
  import { FinalExecutionOutcome } from "near-api-js/lib/providers";
2
- import { NearEthAdapter, NearEthTxData, BaseTx } from "near-ca";
2
+ import { NearEthAdapter, SignRequestData, NearEthTxData, RecoveryData } from "near-ca";
3
3
  import { Address, Hash, Hex } from "viem";
4
4
  import { Erc4337Bundler } from "./lib/bundler";
5
5
  import { ContractSuite } from "./lib/safe";
@@ -7,13 +7,12 @@ import { MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
7
7
  export declare class TransactionManager {
8
8
  readonly nearAdapter: NearEthAdapter;
9
9
  readonly address: Address;
10
- readonly entryPointAddress: Address;
11
10
  private safePack;
12
11
  private setup;
13
12
  private pimlicoKey;
14
13
  private safeSaltNonce;
15
14
  private deployedChains;
16
- constructor(nearAdapter: NearEthAdapter, safePack: ContractSuite, pimlicoKey: string, setup: string, safeAddress: Address, entryPointAddress: Address, safeSaltNonce: string);
15
+ constructor(nearAdapter: NearEthAdapter, safePack: ContractSuite, pimlicoKey: string, setup: string, safeAddress: Address, safeSaltNonce: string);
17
16
  static create(config: {
18
17
  accountId: string;
19
18
  mpcContractId: string;
@@ -22,6 +21,7 @@ export declare class TransactionManager {
22
21
  safeSaltNonce?: string;
23
22
  }): Promise<TransactionManager>;
24
23
  get mpcAddress(): Address;
24
+ get mpcContractId(): string;
25
25
  getBalance(chainId: number): Promise<bigint>;
26
26
  bundlerForChainId(chainId: number): Erc4337Bundler;
27
27
  buildTransaction(args: {
@@ -31,13 +31,28 @@ export declare class TransactionManager {
31
31
  }): Promise<UserOperation>;
32
32
  signTransaction(safeOpHash: Hex): Promise<Hex>;
33
33
  opHash(userOp: UserOperation): Promise<Hash>;
34
- encodeSignRequest(tx: BaseTx): Promise<NearEthTxData>;
34
+ encodeSignRequest(signRequest: SignRequestData, usePaymaster: boolean): Promise<NearEthTxData>;
35
35
  executeTransaction(chainId: number, userOp: UserOperation): Promise<UserOperationReceipt>;
36
36
  safeDeployed(chainId: number): Promise<boolean>;
37
- addOwnerTx(address: string): MetaTransaction;
37
+ addOwnerTx(address: Address): MetaTransaction;
38
38
  safeSufficientlyFunded(chainId: number, transactions: MetaTransaction[], gasCost: bigint): Promise<boolean>;
39
39
  broadcastEvm(chainId: number, outcome: FinalExecutionOutcome, unsignedUserOp: UserOperation): Promise<{
40
40
  signature: Hex;
41
41
  receipt: UserOperationReceipt;
42
42
  }>;
43
+ /**
44
+ * Handles routing of signature requests based on the provided method, chain ID, and parameters.
45
+ *
46
+ * @async
47
+ * @function requestRouter
48
+ * @param {SignRequestData} params - An object containing the method, chain ID, and request parameters.
49
+ * @returns {Promise<{ evmMessage: string; payload: number[]; recoveryData: RecoveryData }>}
50
+ * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
51
+ * the payload (hashed data), and recovery data needed for reconstructing the signature request.
52
+ */
53
+ requestRouter({ method, chainId, params }: SignRequestData, usePaymaster: boolean): Promise<{
54
+ evmMessage: string;
55
+ payload: number[];
56
+ recoveryData: RecoveryData;
57
+ }>;
43
58
  }
@@ -1,23 +1,22 @@
1
- import { Network, setupAdapter, signatureFromOutcome, } from "near-ca";
1
+ import { setupAdapter, signatureFromOutcome, toPayload, } from "near-ca";
2
2
  import { serializeSignature } from "viem";
3
3
  import { Erc4337Bundler } from "./lib/bundler";
4
4
  import { encodeMulti } from "./lib/multisend";
5
5
  import { ContractSuite } from "./lib/safe";
6
- import { packSignature } from "./util";
6
+ import { decodeSafeMessage, safeMessageTxData } from "./lib/safe-message";
7
+ import { getClient, isContract, metaTransactionsFromRequest, packSignature, } from "./util";
7
8
  export class TransactionManager {
8
9
  nearAdapter;
9
10
  address;
10
- entryPointAddress;
11
11
  safePack;
12
12
  setup;
13
13
  pimlicoKey;
14
14
  safeSaltNonce;
15
15
  deployedChains;
16
- constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress, entryPointAddress, safeSaltNonce) {
16
+ constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce) {
17
17
  this.nearAdapter = nearAdapter;
18
18
  this.safePack = safePack;
19
19
  this.pimlicoKey = pimlicoKey;
20
- this.entryPointAddress = entryPointAddress;
21
20
  this.setup = setup;
22
21
  this.address = safeAddress;
23
22
  this.safeSaltNonce = safeSaltNonce;
@@ -30,34 +29,38 @@ export class TransactionManager {
30
29
  ContractSuite.init(),
31
30
  ]);
32
31
  console.log(`Near Adapter: ${nearAdapter.nearAccountId()} <> ${nearAdapter.address}`);
33
- const setup = await safePack.getSetup([nearAdapter.address]);
32
+ const setup = safePack.getSetup([nearAdapter.address]);
34
33
  const safeAddress = await safePack.addressForSetup(setup, config.safeSaltNonce);
35
- const entryPointAddress = (await safePack.entryPoint.getAddress());
36
34
  console.log(`Safe Address: ${safeAddress}`);
37
- return new TransactionManager(nearAdapter, safePack, pimlicoKey, setup, safeAddress, entryPointAddress, config.safeSaltNonce || "0");
35
+ return new TransactionManager(nearAdapter, safePack, pimlicoKey, setup, safeAddress, config.safeSaltNonce || "0");
38
36
  }
39
37
  get mpcAddress() {
40
38
  return this.nearAdapter.address;
41
39
  }
40
+ get mpcContractId() {
41
+ return this.nearAdapter.mpcContract.contract.contractId;
42
+ }
42
43
  async getBalance(chainId) {
43
- const provider = Network.fromChainId(chainId).client;
44
- return await provider.getBalance({ address: this.address });
44
+ return await getClient(chainId).getBalance({ address: this.address });
45
45
  }
46
46
  bundlerForChainId(chainId) {
47
- return new Erc4337Bundler(this.entryPointAddress, this.pimlicoKey, chainId);
47
+ return new Erc4337Bundler(this.safePack.entryPoint.address, this.pimlicoKey, chainId);
48
48
  }
49
49
  async buildTransaction(args) {
50
50
  const { transactions, usePaymaster, chainId } = args;
51
- const bundler = this.bundlerForChainId(chainId);
52
- const gasFees = (await bundler.getGasPrice()).fast;
53
- // Build Singular MetaTransaction for Multisend from transaction list.
54
51
  if (transactions.length === 0) {
55
52
  throw new Error("Empty transaction set!");
56
53
  }
54
+ const bundler = this.bundlerForChainId(chainId);
55
+ const [gasFees, nonce, safeDeployed] = await Promise.all([
56
+ bundler.getGasPrice(),
57
+ this.safePack.getNonce(this.address, chainId),
58
+ this.safeDeployed(chainId),
59
+ ]);
60
+ // Build Singular MetaTransaction for Multisend from transaction list.
57
61
  const tx = transactions.length > 1 ? encodeMulti(transactions) : transactions[0];
58
- const safeNotDeployed = !(await this.safeDeployed(chainId));
59
- const rawUserOp = await this.safePack.buildUserOp(tx, this.address, gasFees, this.setup, safeNotDeployed, this.safeSaltNonce);
60
- const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed);
62
+ const rawUserOp = await this.safePack.buildUserOp(nonce, tx, this.address, gasFees.fast, this.setup, !safeDeployed, this.safeSaltNonce);
63
+ const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, !safeDeployed);
61
64
  const unsignedUserOp = { ...rawUserOp, ...paymasterData };
62
65
  return unsignedUserOp;
63
66
  }
@@ -68,27 +71,15 @@ export class TransactionManager {
68
71
  async opHash(userOp) {
69
72
  return this.safePack.getOpHash(userOp);
70
73
  }
71
- async encodeSignRequest(tx) {
72
- const unsignedUserOp = await this.buildTransaction({
73
- chainId: tx.chainId,
74
- transactions: [
75
- {
76
- to: tx.to,
77
- value: (tx.value || 0n).toString(),
78
- data: tx.data || "0x",
79
- },
80
- ],
81
- usePaymaster: true,
82
- });
83
- const safeOpHash = (await this.opHash(unsignedUserOp));
84
- const signRequest = await this.nearAdapter.encodeSignRequest({
85
- method: "hash",
86
- chainId: 0,
87
- params: safeOpHash,
88
- });
74
+ async encodeSignRequest(signRequest, usePaymaster) {
75
+ const data = await this.requestRouter(signRequest, usePaymaster);
89
76
  return {
90
- ...signRequest,
91
- evmMessage: JSON.stringify(unsignedUserOp),
77
+ nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
78
+ path: this.nearAdapter.derivationPath,
79
+ payload: data.payload,
80
+ key_version: 0,
81
+ }),
82
+ ...data,
92
83
  };
93
84
  }
94
85
  async executeTransaction(chainId, userOp) {
@@ -102,11 +93,11 @@ export class TransactionManager {
102
93
  return userOpReceipt;
103
94
  }
104
95
  async safeDeployed(chainId) {
96
+ // Early exit if already known.
105
97
  if (chainId in this.deployedChains) {
106
98
  return true;
107
99
  }
108
- const provider = Network.fromChainId(chainId).client;
109
- const deployed = (await provider.getCode({ address: this.address })) !== "0x";
100
+ const deployed = await isContract(this.address, chainId);
110
101
  if (deployed) {
111
102
  this.deployedChains.add(chainId);
112
103
  }
@@ -116,7 +107,7 @@ export class TransactionManager {
116
107
  return {
117
108
  to: this.address,
118
109
  value: "0",
119
- data: this.safePack.singleton.interface.encodeFunctionData("addOwnerWithThreshold", [address, 1]),
110
+ data: this.safePack.addOwnerData(address),
120
111
  };
121
112
  }
122
113
  async safeSufficientlyFunded(chainId, transactions, gasCost) {
@@ -142,4 +133,55 @@ export class TransactionManager {
142
133
  throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
143
134
  }
144
135
  }
136
+ /**
137
+ * Handles routing of signature requests based on the provided method, chain ID, and parameters.
138
+ *
139
+ * @async
140
+ * @function requestRouter
141
+ * @param {SignRequestData} params - An object containing the method, chain ID, and request parameters.
142
+ * @returns {Promise<{ evmMessage: string; payload: number[]; recoveryData: RecoveryData }>}
143
+ * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
144
+ * the payload (hashed data), and recovery data needed for reconstructing the signature request.
145
+ */
146
+ async requestRouter({ method, chainId, params }, usePaymaster) {
147
+ const safeInfo = {
148
+ address: { value: this.address },
149
+ chainId: chainId.toString(),
150
+ // TODO: Should be able to read this from on chain.
151
+ version: "1.4.1+L2",
152
+ };
153
+ // TODO: We are provided with sender in the input, but also expect safeInfo.
154
+ // We should either confirm they agree or ignore one of the two.
155
+ switch (method) {
156
+ case "eth_signTypedData":
157
+ case "eth_signTypedData_v4":
158
+ case "eth_sign": {
159
+ const [sender, messageOrData] = params;
160
+ return safeMessageTxData(method, decodeSafeMessage(messageOrData, safeInfo), sender);
161
+ }
162
+ case "personal_sign": {
163
+ const [messageHash, sender] = params;
164
+ return safeMessageTxData(method, decodeSafeMessage(messageHash, safeInfo), sender);
165
+ }
166
+ case "eth_sendTransaction": {
167
+ const transactions = metaTransactionsFromRequest(params);
168
+ const userOp = await this.buildTransaction({
169
+ chainId,
170
+ transactions,
171
+ usePaymaster,
172
+ });
173
+ const opHash = await this.opHash(userOp);
174
+ return {
175
+ payload: toPayload(opHash),
176
+ evmMessage: JSON.stringify(userOp),
177
+ recoveryData: {
178
+ type: method,
179
+ // TODO: Double check that this is sufficient for UI.
180
+ // We may want to adapt and return the `MetaTransactions` instead.
181
+ data: opHash,
182
+ },
183
+ };
184
+ }
185
+ }
186
+ }
145
187
  }
@@ -1,4 +1,5 @@
1
- import { Hex } from "viem";
1
+ import { SessionRequestParams } from "near-ca";
2
+ import { Address, Hex, PublicClient } from "viem";
2
3
  import { PaymasterData, MetaTransaction } from "./types";
3
4
  export declare const PLACEHOLDER_SIG: `0x${string}`;
4
5
  type IntLike = Hex | bigint | string | number;
@@ -6,4 +7,7 @@ export declare const packGas: (hi: IntLike, lo: IntLike) => string;
6
7
  export declare function packSignature(signature: `0x${string}`, validFrom?: number, validTo?: number): Hex;
7
8
  export declare function packPaymasterData(data: PaymasterData): Hex;
8
9
  export declare function containsValue(transactions: MetaTransaction[]): boolean;
10
+ export declare function isContract(address: Address, chainId: number): Promise<boolean>;
11
+ export declare function getClient(chainId: number): PublicClient;
12
+ export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
9
13
  export {};
package/dist/esm/util.js CHANGED
@@ -1,4 +1,6 @@
1
- import { concatHex, encodePacked, toHex } from "viem";
1
+ import { Network } from "near-ca";
2
+ import { concatHex, encodePacked, toHex, isHex, parseTransaction, zeroAddress, } from "viem";
3
+ //
2
4
  export const PLACEHOLDER_SIG = encodePacked(["uint48", "uint48"], [0, 0]);
3
5
  export const packGas = (hi, lo) => encodePacked(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
4
6
  export function packSignature(signature, validFrom = 0, validTo = 0) {
@@ -17,3 +19,32 @@ export function packPaymasterData(data) {
17
19
  export function containsValue(transactions) {
18
20
  return transactions.some((tx) => tx.value !== "0");
19
21
  }
22
+ export async function isContract(address, chainId) {
23
+ return (await getClient(chainId).getCode({ address })) !== undefined;
24
+ }
25
+ export function getClient(chainId) {
26
+ return Network.fromChainId(chainId).client;
27
+ }
28
+ export function metaTransactionsFromRequest(params) {
29
+ let transactions;
30
+ if (isHex(params)) {
31
+ // If RLP hex is given, decode the transaction and build EthTransactionParams
32
+ const tx = parseTransaction(params);
33
+ transactions = [
34
+ {
35
+ from: zeroAddress, // TODO: This is a hack - but its unused.
36
+ to: tx.to,
37
+ value: tx.value ? toHex(tx.value) : "0x00",
38
+ data: tx.data || "0x",
39
+ },
40
+ ];
41
+ }
42
+ else {
43
+ transactions = params;
44
+ }
45
+ return transactions.map((tx) => ({
46
+ to: tx.to,
47
+ value: tx.value || "0x00",
48
+ data: tx.data || "0x",
49
+ }));
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "near-safe",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "description": "An SDK for controlling Ethereum Smart Accounts via ERC4337 from a Near Account.",
6
6
  "author": "bh2smith",
@@ -41,27 +41,33 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@safe-global/safe-deployments": "^1.37.0",
44
+ "@safe-global/safe-gateway-typescript-sdk": "^3.22.2",
44
45
  "@safe-global/safe-modules-deployments": "^2.2.0",
45
- "ethers": "^6.13.1",
46
46
  "near-api-js": "^5.0.0",
47
- "near-ca": "^0.5.2",
48
- "viem": "^2.16.5",
49
- "yargs": "^17.7.2"
47
+ "near-ca": "^0.5.6",
48
+ "semver": "^7.6.3",
49
+ "viem": "^2.16.5"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/jest": "^29.5.12",
53
53
  "@types/node": "^22.3.0",
54
+ "@types/semver": "^7.5.8",
54
55
  "@types/yargs": "^17.0.32",
55
56
  "@typescript-eslint/eslint-plugin": "^8.1.0",
56
57
  "@typescript-eslint/parser": "^8.1.0",
57
58
  "dotenv": "^16.4.5",
58
59
  "eslint": "^9.6.0",
59
60
  "eslint-plugin-import": "^2.30.0",
61
+ "ethers": "^6.13.1",
60
62
  "jest": "^29.7.0",
61
63
  "prettier": "^3.3.2",
62
64
  "ts-jest": "^29.1.5",
63
65
  "tsx": "^4.16.0",
64
66
  "typescript": "^5.5.2",
65
67
  "yargs": "^17.7.2"
68
+ },
69
+ "resolutions": {
70
+ "glob": "^9.0.0",
71
+ "base-x": "^3.0.0"
66
72
  }
67
73
  }