near-safe 0.2.1 → 0.3.1

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.
@@ -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
  }