near-safe 0.3.0 → 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.
@@ -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",