near-safe 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk";
2
+ import { EIP712TypedData, RecoveryData } from "near-ca";
3
+ import { Address, Hash } from "viem";
4
+ export type DecodedSafeMessage = {
5
+ decodedMessage: string | EIP712TypedData;
6
+ safeMessageMessage: string;
7
+ safeMessageHash: Hash;
8
+ };
9
+ export type MinimalSafeInfo = Pick<SafeInfo, "address" | "version" | "chainId">;
10
+ /**
11
+ * Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`.
12
+ * The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`.
13
+ *
14
+ * @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data
15
+ * @param safe SafeInfo of the opened Safe
16
+ * @returns `{
17
+ * decodedMessage,
18
+ * safeMessageMessage,
19
+ * safeMessageHash
20
+ * }`
21
+ */
22
+ export declare function decodeSafeMessage(message: string | EIP712TypedData, safe: MinimalSafeInfo): DecodedSafeMessage;
23
+ export declare function safeMessageTxData(method: string, message: DecodedSafeMessage, sender: Address): {
24
+ evmMessage: string;
25
+ payload: number[];
26
+ recoveryData: RecoveryData;
27
+ };
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeSafeMessage = decodeSafeMessage;
4
+ exports.safeMessageTxData = safeMessageTxData;
5
+ const near_ca_1 = require("near-ca");
6
+ const semver_1 = require("semver");
7
+ const viem_1 = require("viem");
8
+ /*
9
+ * From v1.3.0, EIP-1271 support was moved to the CompatibilityFallbackHandler.
10
+ * Also 1.3.0 introduces the chainId in the domain part of the SafeMessage
11
+ */
12
+ const EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION = "1.3.0";
13
+ const generateSafeMessageMessage = (message) => {
14
+ return typeof message === "string"
15
+ ? (0, viem_1.hashMessage)(message)
16
+ : (0, viem_1.hashTypedData)(message);
17
+ };
18
+ /**
19
+ * Generates `SafeMessage` typed data for EIP-712
20
+ * https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L12
21
+ * @param safe Safe which will sign the message
22
+ * @param message Message to sign
23
+ * @returns `SafeMessage` types for signing
24
+ */
25
+ const generateSafeMessageTypedData = ({ version, chainId, address }, message) => {
26
+ if (!version) {
27
+ throw Error("Cannot create SafeMessage without version information");
28
+ }
29
+ const isHandledByFallbackHandler = (0, semver_1.gte)(version, EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION);
30
+ const verifyingContract = address.value;
31
+ return {
32
+ domain: isHandledByFallbackHandler
33
+ ? {
34
+ chainId: Number(BigInt(chainId)),
35
+ verifyingContract,
36
+ }
37
+ : { verifyingContract },
38
+ types: {
39
+ SafeMessage: [{ name: "message", type: "bytes" }],
40
+ },
41
+ message: {
42
+ message: generateSafeMessageMessage(message),
43
+ },
44
+ primaryType: "SafeMessage",
45
+ };
46
+ };
47
+ const generateSafeMessageHash = (safe, message) => {
48
+ const typedData = generateSafeMessageTypedData(safe, message);
49
+ return (0, viem_1.hashTypedData)(typedData);
50
+ };
51
+ /**
52
+ * If message is a hex value and is Utf8 encoded string we decode it, else we return the raw message
53
+ * @param {string} message raw input message
54
+ * @returns {string}
55
+ */
56
+ const getDecodedMessage = (message) => {
57
+ if ((0, viem_1.isHex)(message)) {
58
+ try {
59
+ return (0, viem_1.fromHex)(message, "string");
60
+ }
61
+ catch (e) {
62
+ // the hex string is not UTF8 encoding so return the raw message.
63
+ }
64
+ }
65
+ return message;
66
+ };
67
+ /**
68
+ * Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`.
69
+ * The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`.
70
+ *
71
+ * @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data
72
+ * @param safe SafeInfo of the opened Safe
73
+ * @returns `{
74
+ * decodedMessage,
75
+ * safeMessageMessage,
76
+ * safeMessageHash
77
+ * }`
78
+ */
79
+ function decodeSafeMessage(message, safe) {
80
+ const decodedMessage = typeof message === "string" ? getDecodedMessage(message) : message;
81
+ return {
82
+ decodedMessage,
83
+ safeMessageMessage: generateSafeMessageMessage(decodedMessage),
84
+ safeMessageHash: generateSafeMessageHash(safe, decodedMessage),
85
+ };
86
+ }
87
+ function safeMessageTxData(method, message, sender) {
88
+ return {
89
+ evmMessage: message.safeMessageMessage,
90
+ payload: (0, near_ca_1.toPayload)(message.safeMessageHash),
91
+ recoveryData: {
92
+ type: method,
93
+ data: {
94
+ address: sender,
95
+ // TODO - Upgrade Signable Message in near-ca
96
+ // @ts-expect-error: Type 'string | EIP712TypedData' is not assignable to type 'SignableMessage'.
97
+ message: decodedMessage,
98
+ },
99
+ },
100
+ };
101
+ }
102
+ // const isEIP712TypedData = (obj: any): obj is EIP712TypedData => {
103
+ // return (
104
+ // typeof obj === "object" &&
105
+ // obj != null &&
106
+ // "domain" in obj &&
107
+ // "types" in obj &&
108
+ // "message" in obj
109
+ // );
110
+ // };
111
+ // export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean =>
112
+ // !isEIP712TypedData(obj) && isHash(obj);
@@ -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";
@@ -31,7 +31,7 @@ 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
37
  addOwnerTx(address: Address): MetaTransaction;
@@ -40,4 +40,19 @@ export declare class TransactionManager {
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
  }
@@ -6,6 +6,7 @@ const viem_1 = require("viem");
6
6
  const bundler_1 = require("./lib/bundler");
7
7
  const multisend_1 = require("./lib/multisend");
8
8
  const safe_1 = require("./lib/safe");
9
+ const safe_message_1 = require("./lib/safe-message");
9
10
  const util_1 = require("./util");
10
11
  class TransactionManager {
11
12
  constructor(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce) {
@@ -66,27 +67,15 @@ class TransactionManager {
66
67
  async opHash(userOp) {
67
68
  return this.safePack.getOpHash(userOp);
68
69
  }
69
- async encodeSignRequest(tx) {
70
- const unsignedUserOp = await this.buildTransaction({
71
- chainId: tx.chainId,
72
- transactions: [
73
- {
74
- to: tx.to,
75
- value: (tx.value || 0n).toString(),
76
- data: tx.data || "0x",
77
- },
78
- ],
79
- usePaymaster: true,
80
- });
81
- const safeOpHash = (await this.opHash(unsignedUserOp));
82
- const signRequest = await this.nearAdapter.encodeSignRequest({
83
- method: "hash",
84
- chainId: 0,
85
- params: safeOpHash,
86
- });
70
+ async encodeSignRequest(signRequest, usePaymaster) {
71
+ const data = await this.requestRouter(signRequest, usePaymaster);
87
72
  return {
88
- ...signRequest,
89
- evmMessage: JSON.stringify(unsignedUserOp),
73
+ nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
74
+ path: this.nearAdapter.derivationPath,
75
+ payload: data.payload,
76
+ key_version: 0,
77
+ }),
78
+ ...data,
90
79
  };
91
80
  }
92
81
  async executeTransaction(chainId, userOp) {
@@ -140,5 +129,56 @@ class TransactionManager {
140
129
  throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
141
130
  }
142
131
  }
132
+ /**
133
+ * Handles routing of signature requests based on the provided method, chain ID, and parameters.
134
+ *
135
+ * @async
136
+ * @function requestRouter
137
+ * @param {SignRequestData} params - An object containing the method, chain ID, and request parameters.
138
+ * @returns {Promise<{ evmMessage: string; payload: number[]; recoveryData: RecoveryData }>}
139
+ * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
140
+ * the payload (hashed data), and recovery data needed for reconstructing the signature request.
141
+ */
142
+ async requestRouter({ method, chainId, params }, usePaymaster) {
143
+ const safeInfo = {
144
+ address: { value: this.address },
145
+ chainId: chainId.toString(),
146
+ // TODO: Should be able to read this from on chain.
147
+ version: "1.4.1+L2",
148
+ };
149
+ // TODO: We are provided with sender in the input, but also expect safeInfo.
150
+ // We should either confirm they agree or ignore one of the two.
151
+ switch (method) {
152
+ case "eth_signTypedData":
153
+ case "eth_signTypedData_v4":
154
+ case "eth_sign": {
155
+ const [sender, messageOrData] = params;
156
+ return (0, safe_message_1.safeMessageTxData)(method, (0, safe_message_1.decodeSafeMessage)(messageOrData, safeInfo), sender);
157
+ }
158
+ case "personal_sign": {
159
+ const [messageHash, sender] = params;
160
+ return (0, safe_message_1.safeMessageTxData)(method, (0, safe_message_1.decodeSafeMessage)(messageHash, safeInfo), sender);
161
+ }
162
+ case "eth_sendTransaction": {
163
+ const transactions = (0, util_1.metaTransactionsFromRequest)(params);
164
+ const userOp = await this.buildTransaction({
165
+ chainId,
166
+ transactions,
167
+ usePaymaster,
168
+ });
169
+ const opHash = await this.opHash(userOp);
170
+ return {
171
+ payload: (0, near_ca_1.toPayload)(opHash),
172
+ evmMessage: JSON.stringify(userOp),
173
+ recoveryData: {
174
+ type: method,
175
+ // TODO: Double check that this is sufficient for UI.
176
+ // We may want to adapt and return the `MetaTransactions` instead.
177
+ data: opHash,
178
+ },
179
+ };
180
+ }
181
+ }
182
+ }
143
183
  }
144
184
  exports.TransactionManager = TransactionManager;
@@ -1,3 +1,4 @@
1
+ import { SessionRequestParams } from "near-ca";
1
2
  import { Address, Hex, PublicClient } from "viem";
2
3
  import { PaymasterData, MetaTransaction } from "./types";
3
4
  export declare const PLACEHOLDER_SIG: `0x${string}`;
@@ -8,4 +9,5 @@ export declare function packPaymasterData(data: PaymasterData): Hex;
8
9
  export declare function containsValue(transactions: MetaTransaction[]): boolean;
9
10
  export declare function isContract(address: Address, chainId: number): Promise<boolean>;
10
11
  export declare function getClient(chainId: number): PublicClient;
12
+ export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
11
13
  export {};
package/dist/cjs/util.js CHANGED
@@ -6,8 +6,10 @@ exports.packPaymasterData = packPaymasterData;
6
6
  exports.containsValue = containsValue;
7
7
  exports.isContract = isContract;
8
8
  exports.getClient = getClient;
9
+ exports.metaTransactionsFromRequest = metaTransactionsFromRequest;
9
10
  const near_ca_1 = require("near-ca");
10
11
  const viem_1 = require("viem");
12
+ //
11
13
  exports.PLACEHOLDER_SIG = (0, viem_1.encodePacked)(["uint48", "uint48"], [0, 0]);
12
14
  const packGas = (hi, lo) => (0, viem_1.encodePacked)(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
13
15
  exports.packGas = packGas;
@@ -33,3 +35,26 @@ async function isContract(address, chainId) {
33
35
  function getClient(chainId) {
34
36
  return near_ca_1.Network.fromChainId(chainId).client;
35
37
  }
38
+ function metaTransactionsFromRequest(params) {
39
+ let transactions;
40
+ if ((0, viem_1.isHex)(params)) {
41
+ // If RLP hex is given, decode the transaction and build EthTransactionParams
42
+ const tx = (0, viem_1.parseTransaction)(params);
43
+ transactions = [
44
+ {
45
+ from: viem_1.zeroAddress, // TODO: This is a hack - but its unused.
46
+ to: tx.to,
47
+ value: tx.value ? (0, viem_1.toHex)(tx.value) : "0x00",
48
+ data: tx.data || "0x",
49
+ },
50
+ ];
51
+ }
52
+ else {
53
+ transactions = params;
54
+ }
55
+ return transactions.map((tx) => ({
56
+ to: tx.to,
57
+ value: tx.value || "0x00",
58
+ data: tx.data || "0x",
59
+ }));
60
+ }
@@ -0,0 +1,27 @@
1
+ import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk";
2
+ import { EIP712TypedData, RecoveryData } from "near-ca";
3
+ import { Address, Hash } from "viem";
4
+ export type DecodedSafeMessage = {
5
+ decodedMessage: string | EIP712TypedData;
6
+ safeMessageMessage: string;
7
+ safeMessageHash: Hash;
8
+ };
9
+ export type MinimalSafeInfo = Pick<SafeInfo, "address" | "version" | "chainId">;
10
+ /**
11
+ * Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`.
12
+ * The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`.
13
+ *
14
+ * @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data
15
+ * @param safe SafeInfo of the opened Safe
16
+ * @returns `{
17
+ * decodedMessage,
18
+ * safeMessageMessage,
19
+ * safeMessageHash
20
+ * }`
21
+ */
22
+ export declare function decodeSafeMessage(message: string | EIP712TypedData, safe: MinimalSafeInfo): DecodedSafeMessage;
23
+ export declare function safeMessageTxData(method: string, message: DecodedSafeMessage, sender: Address): {
24
+ evmMessage: string;
25
+ payload: number[];
26
+ recoveryData: RecoveryData;
27
+ };
@@ -0,0 +1,108 @@
1
+ import { toPayload } from "near-ca";
2
+ import { gte } from "semver";
3
+ import { fromHex, hashMessage, hashTypedData, isHex, } from "viem";
4
+ /*
5
+ * From v1.3.0, EIP-1271 support was moved to the CompatibilityFallbackHandler.
6
+ * Also 1.3.0 introduces the chainId in the domain part of the SafeMessage
7
+ */
8
+ const EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION = "1.3.0";
9
+ const generateSafeMessageMessage = (message) => {
10
+ return typeof message === "string"
11
+ ? hashMessage(message)
12
+ : hashTypedData(message);
13
+ };
14
+ /**
15
+ * Generates `SafeMessage` typed data for EIP-712
16
+ * https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L12
17
+ * @param safe Safe which will sign the message
18
+ * @param message Message to sign
19
+ * @returns `SafeMessage` types for signing
20
+ */
21
+ const generateSafeMessageTypedData = ({ version, chainId, address }, message) => {
22
+ if (!version) {
23
+ throw Error("Cannot create SafeMessage without version information");
24
+ }
25
+ const isHandledByFallbackHandler = gte(version, EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION);
26
+ const verifyingContract = address.value;
27
+ return {
28
+ domain: isHandledByFallbackHandler
29
+ ? {
30
+ chainId: Number(BigInt(chainId)),
31
+ verifyingContract,
32
+ }
33
+ : { verifyingContract },
34
+ types: {
35
+ SafeMessage: [{ name: "message", type: "bytes" }],
36
+ },
37
+ message: {
38
+ message: generateSafeMessageMessage(message),
39
+ },
40
+ primaryType: "SafeMessage",
41
+ };
42
+ };
43
+ const generateSafeMessageHash = (safe, message) => {
44
+ const typedData = generateSafeMessageTypedData(safe, message);
45
+ return hashTypedData(typedData);
46
+ };
47
+ /**
48
+ * If message is a hex value and is Utf8 encoded string we decode it, else we return the raw message
49
+ * @param {string} message raw input message
50
+ * @returns {string}
51
+ */
52
+ const getDecodedMessage = (message) => {
53
+ if (isHex(message)) {
54
+ try {
55
+ return fromHex(message, "string");
56
+ }
57
+ catch (e) {
58
+ // the hex string is not UTF8 encoding so return the raw message.
59
+ }
60
+ }
61
+ return message;
62
+ };
63
+ /**
64
+ * Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`.
65
+ * The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`.
66
+ *
67
+ * @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data
68
+ * @param safe SafeInfo of the opened Safe
69
+ * @returns `{
70
+ * decodedMessage,
71
+ * safeMessageMessage,
72
+ * safeMessageHash
73
+ * }`
74
+ */
75
+ export function decodeSafeMessage(message, safe) {
76
+ const decodedMessage = typeof message === "string" ? getDecodedMessage(message) : message;
77
+ return {
78
+ decodedMessage,
79
+ safeMessageMessage: generateSafeMessageMessage(decodedMessage),
80
+ safeMessageHash: generateSafeMessageHash(safe, decodedMessage),
81
+ };
82
+ }
83
+ export function safeMessageTxData(method, message, sender) {
84
+ return {
85
+ evmMessage: message.safeMessageMessage,
86
+ payload: toPayload(message.safeMessageHash),
87
+ recoveryData: {
88
+ type: method,
89
+ data: {
90
+ address: sender,
91
+ // TODO - Upgrade Signable Message in near-ca
92
+ // @ts-expect-error: Type 'string | EIP712TypedData' is not assignable to type 'SignableMessage'.
93
+ message: decodedMessage,
94
+ },
95
+ },
96
+ };
97
+ }
98
+ // const isEIP712TypedData = (obj: any): obj is EIP712TypedData => {
99
+ // return (
100
+ // typeof obj === "object" &&
101
+ // obj != null &&
102
+ // "domain" in obj &&
103
+ // "types" in obj &&
104
+ // "message" in obj
105
+ // );
106
+ // };
107
+ // export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean =>
108
+ // !isEIP712TypedData(obj) && isHash(obj);
@@ -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";
@@ -31,7 +31,7 @@ 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
37
  addOwnerTx(address: Address): MetaTransaction;
@@ -40,4 +40,19 @@ export declare class TransactionManager {
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,9 +1,10 @@
1
- import { 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 { getClient, isContract, 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;
@@ -70,27 +71,15 @@ export class TransactionManager {
70
71
  async opHash(userOp) {
71
72
  return this.safePack.getOpHash(userOp);
72
73
  }
73
- async encodeSignRequest(tx) {
74
- const unsignedUserOp = await this.buildTransaction({
75
- chainId: tx.chainId,
76
- transactions: [
77
- {
78
- to: tx.to,
79
- value: (tx.value || 0n).toString(),
80
- data: tx.data || "0x",
81
- },
82
- ],
83
- usePaymaster: true,
84
- });
85
- const safeOpHash = (await this.opHash(unsignedUserOp));
86
- const signRequest = await this.nearAdapter.encodeSignRequest({
87
- method: "hash",
88
- chainId: 0,
89
- params: safeOpHash,
90
- });
74
+ async encodeSignRequest(signRequest, usePaymaster) {
75
+ const data = await this.requestRouter(signRequest, usePaymaster);
91
76
  return {
92
- ...signRequest,
93
- 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,
94
83
  };
95
84
  }
96
85
  async executeTransaction(chainId, userOp) {
@@ -144,4 +133,55 @@ export class TransactionManager {
144
133
  throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
145
134
  }
146
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
+ }
147
187
  }
@@ -1,3 +1,4 @@
1
+ import { SessionRequestParams } from "near-ca";
1
2
  import { Address, Hex, PublicClient } from "viem";
2
3
  import { PaymasterData, MetaTransaction } from "./types";
3
4
  export declare const PLACEHOLDER_SIG: `0x${string}`;
@@ -8,4 +9,5 @@ export declare function packPaymasterData(data: PaymasterData): Hex;
8
9
  export declare function containsValue(transactions: MetaTransaction[]): boolean;
9
10
  export declare function isContract(address: Address, chainId: number): Promise<boolean>;
10
11
  export declare function getClient(chainId: number): PublicClient;
12
+ export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
11
13
  export {};
package/dist/esm/util.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Network } from "near-ca";
2
- import { concatHex, encodePacked, toHex, } from "viem";
2
+ import { concatHex, encodePacked, toHex, isHex, parseTransaction, zeroAddress, } from "viem";
3
+ //
3
4
  export const PLACEHOLDER_SIG = encodePacked(["uint48", "uint48"], [0, 0]);
4
5
  export const packGas = (hi, lo) => encodePacked(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
5
6
  export function packSignature(signature, validFrom = 0, validTo = 0) {
@@ -24,3 +25,26 @@ export async function isContract(address, chainId) {
24
25
  export function getClient(chainId) {
25
26
  return Network.fromChainId(chainId).client;
26
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.3.0",
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,14 +41,17 @@
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
46
  "near-api-js": "^5.0.0",
46
- "near-ca": "^0.5.2",
47
+ "near-ca": "^0.5.6",
48
+ "semver": "^7.6.3",
47
49
  "viem": "^2.16.5"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@types/jest": "^29.5.12",
51
53
  "@types/node": "^22.3.0",
54
+ "@types/semver": "^7.5.8",
52
55
  "@types/yargs": "^17.0.32",
53
56
  "@typescript-eslint/eslint-plugin": "^8.1.0",
54
57
  "@typescript-eslint/parser": "^8.1.0",