near-safe 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk";
2
+ import { EIP712TypedData } from "near-ca";
3
+ import { 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;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeSafeMessage = decodeSafeMessage;
4
+ const semver_1 = require("semver");
5
+ const viem_1 = require("viem");
6
+ /*
7
+ * From v1.3.0, EIP-1271 support was moved to the CompatibilityFallbackHandler.
8
+ * Also 1.3.0 introduces the chainId in the domain part of the SafeMessage
9
+ */
10
+ const EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION = "1.3.0";
11
+ const generateSafeMessageMessage = (message) => {
12
+ return typeof message === "string"
13
+ ? (0, viem_1.hashMessage)(message)
14
+ : (0, viem_1.hashTypedData)(message);
15
+ };
16
+ /**
17
+ * Generates `SafeMessage` typed data for EIP-712
18
+ * https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L12
19
+ * @param safe Safe which will sign the message
20
+ * @param message Message to sign
21
+ * @returns `SafeMessage` types for signing
22
+ */
23
+ const generateSafeMessageTypedData = ({ version, chainId, address }, message) => {
24
+ if (!version) {
25
+ throw Error("Cannot create SafeMessage without version information");
26
+ }
27
+ const isHandledByFallbackHandler = (0, semver_1.gte)(version, EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION);
28
+ const verifyingContract = address.value;
29
+ return {
30
+ domain: isHandledByFallbackHandler
31
+ ? {
32
+ chainId: Number(BigInt(chainId)),
33
+ verifyingContract,
34
+ }
35
+ : { verifyingContract },
36
+ types: {
37
+ SafeMessage: [{ name: "message", type: "bytes" }],
38
+ },
39
+ message: {
40
+ message: generateSafeMessageMessage(message),
41
+ },
42
+ primaryType: "SafeMessage",
43
+ };
44
+ };
45
+ const generateSafeMessageHash = (safe, message) => {
46
+ const typedData = generateSafeMessageTypedData(safe, message);
47
+ return (0, viem_1.hashTypedData)(typedData);
48
+ };
49
+ /**
50
+ * If message is a hex value and is Utf8 encoded string we decode it, else we return the raw message
51
+ * @param {string} message raw input message
52
+ * @returns {string}
53
+ */
54
+ const getDecodedMessage = (message) => {
55
+ if ((0, viem_1.isHex)(message)) {
56
+ try {
57
+ return (0, viem_1.fromHex)(message, "string");
58
+ }
59
+ catch (e) {
60
+ // the hex string is not UTF8 encoding so return the raw message.
61
+ }
62
+ }
63
+ return message;
64
+ };
65
+ /**
66
+ * Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`.
67
+ * The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`.
68
+ *
69
+ * @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data
70
+ * @param safe SafeInfo of the opened Safe
71
+ * @returns `{
72
+ * decodedMessage,
73
+ * safeMessageMessage,
74
+ * safeMessageHash
75
+ * }`
76
+ */
77
+ function decodeSafeMessage(message, safe) {
78
+ const decodedMessage = typeof message === "string" ? getDecodedMessage(message) : message;
79
+ return {
80
+ decodedMessage,
81
+ safeMessageMessage: generateSafeMessageMessage(decodedMessage),
82
+ safeMessageHash: generateSafeMessageHash(safe, decodedMessage),
83
+ };
84
+ }
85
+ // const isEIP712TypedData = (obj: any): obj is EIP712TypedData => {
86
+ // return (
87
+ // typeof obj === "object" &&
88
+ // obj != null &&
89
+ // "domain" in obj &&
90
+ // "types" in obj &&
91
+ // "message" in obj
92
+ // );
93
+ // };
94
+ // export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean =>
95
+ // !isEIP712TypedData(obj) && isHash(obj);
@@ -1,9 +1,9 @@
1
1
  import { FinalExecutionOutcome } from "near-api-js/lib/providers";
2
- import { NearEthAdapter, NearEthTxData, BaseTx } from "near-ca";
2
+ import { NearEthAdapter, SignRequestData } 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";
6
- import { MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
6
+ import { EncodedTxData, MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
7
7
  export declare class TransactionManager {
8
8
  readonly nearAdapter: NearEthAdapter;
9
9
  readonly address: Address;
@@ -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<EncodedTxData>;
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
+ hash: Hash;
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,19 @@ 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 { payload, evmMessage, hash } = 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,
76
+ key_version: 0,
77
+ }),
78
+ evmData: {
79
+ chainId: signRequest.chainId,
80
+ data: evmMessage,
81
+ hash,
82
+ },
90
83
  };
91
84
  }
92
85
  async executeTransaction(chainId, userOp) {
@@ -140,5 +133,61 @@ class TransactionManager {
140
133
  throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
141
134
  }
142
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 [_, messageOrData] = params;
160
+ const message = (0, safe_message_1.decodeSafeMessage)(messageOrData, safeInfo);
161
+ return {
162
+ evmMessage: message.safeMessageMessage,
163
+ payload: (0, near_ca_1.toPayload)(message.safeMessageHash),
164
+ hash: message.safeMessageHash,
165
+ };
166
+ }
167
+ case "personal_sign": {
168
+ const [messageHash, _] = params;
169
+ const message = (0, safe_message_1.decodeSafeMessage)(messageHash, safeInfo);
170
+ return {
171
+ evmMessage: message.safeMessageMessage,
172
+ payload: (0, near_ca_1.toPayload)(message.safeMessageHash),
173
+ hash: message.safeMessageHash,
174
+ };
175
+ }
176
+ case "eth_sendTransaction": {
177
+ const transactions = (0, util_1.metaTransactionsFromRequest)(params);
178
+ const userOp = await this.buildTransaction({
179
+ chainId,
180
+ transactions,
181
+ usePaymaster,
182
+ });
183
+ const opHash = await this.opHash(userOp);
184
+ return {
185
+ payload: (0, near_ca_1.toPayload)(opHash),
186
+ evmMessage: JSON.stringify(userOp),
187
+ hash: await this.opHash(userOp),
188
+ };
189
+ }
190
+ }
191
+ }
143
192
  }
144
193
  exports.TransactionManager = TransactionManager;
@@ -1,4 +1,5 @@
1
- import { Address, Hex } from "viem";
1
+ import { FunctionCallTransaction, SignArgs } from "near-ca";
2
+ import { Address, Hash, Hex, TransactionSerializable } from "viem";
2
3
  export interface UnsignedUserOperation {
3
4
  sender: Address;
4
5
  nonce: string;
@@ -89,4 +90,14 @@ export interface MetaTransaction {
89
90
  readonly data: string;
90
91
  readonly operation?: OperationType;
91
92
  }
93
+ export interface EncodedTxData {
94
+ evmData: {
95
+ chainId: number;
96
+ data: string | TransactionSerializable;
97
+ hash: Hash;
98
+ };
99
+ nearPayload: FunctionCallTransaction<{
100
+ request: SignArgs;
101
+ }>;
102
+ }
92
103
  export {};
@@ -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,22 @@
1
+ import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk";
2
+ import { EIP712TypedData } from "near-ca";
3
+ import { 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;
@@ -0,0 +1,92 @@
1
+ import { gte } from "semver";
2
+ import { fromHex, hashMessage, hashTypedData, isHex, } from "viem";
3
+ /*
4
+ * From v1.3.0, EIP-1271 support was moved to the CompatibilityFallbackHandler.
5
+ * Also 1.3.0 introduces the chainId in the domain part of the SafeMessage
6
+ */
7
+ const EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION = "1.3.0";
8
+ const generateSafeMessageMessage = (message) => {
9
+ return typeof message === "string"
10
+ ? hashMessage(message)
11
+ : hashTypedData(message);
12
+ };
13
+ /**
14
+ * Generates `SafeMessage` typed data for EIP-712
15
+ * https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L12
16
+ * @param safe Safe which will sign the message
17
+ * @param message Message to sign
18
+ * @returns `SafeMessage` types for signing
19
+ */
20
+ const generateSafeMessageTypedData = ({ version, chainId, address }, message) => {
21
+ if (!version) {
22
+ throw Error("Cannot create SafeMessage without version information");
23
+ }
24
+ const isHandledByFallbackHandler = gte(version, EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION);
25
+ const verifyingContract = address.value;
26
+ return {
27
+ domain: isHandledByFallbackHandler
28
+ ? {
29
+ chainId: Number(BigInt(chainId)),
30
+ verifyingContract,
31
+ }
32
+ : { verifyingContract },
33
+ types: {
34
+ SafeMessage: [{ name: "message", type: "bytes" }],
35
+ },
36
+ message: {
37
+ message: generateSafeMessageMessage(message),
38
+ },
39
+ primaryType: "SafeMessage",
40
+ };
41
+ };
42
+ const generateSafeMessageHash = (safe, message) => {
43
+ const typedData = generateSafeMessageTypedData(safe, message);
44
+ return hashTypedData(typedData);
45
+ };
46
+ /**
47
+ * If message is a hex value and is Utf8 encoded string we decode it, else we return the raw message
48
+ * @param {string} message raw input message
49
+ * @returns {string}
50
+ */
51
+ const getDecodedMessage = (message) => {
52
+ if (isHex(message)) {
53
+ try {
54
+ return fromHex(message, "string");
55
+ }
56
+ catch (e) {
57
+ // the hex string is not UTF8 encoding so return the raw message.
58
+ }
59
+ }
60
+ return message;
61
+ };
62
+ /**
63
+ * Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`.
64
+ * The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`.
65
+ *
66
+ * @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data
67
+ * @param safe SafeInfo of the opened Safe
68
+ * @returns `{
69
+ * decodedMessage,
70
+ * safeMessageMessage,
71
+ * safeMessageHash
72
+ * }`
73
+ */
74
+ export function decodeSafeMessage(message, safe) {
75
+ const decodedMessage = typeof message === "string" ? getDecodedMessage(message) : message;
76
+ return {
77
+ decodedMessage,
78
+ safeMessageMessage: generateSafeMessageMessage(decodedMessage),
79
+ safeMessageHash: generateSafeMessageHash(safe, decodedMessage),
80
+ };
81
+ }
82
+ // const isEIP712TypedData = (obj: any): obj is EIP712TypedData => {
83
+ // return (
84
+ // typeof obj === "object" &&
85
+ // obj != null &&
86
+ // "domain" in obj &&
87
+ // "types" in obj &&
88
+ // "message" in obj
89
+ // );
90
+ // };
91
+ // export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean =>
92
+ // !isEIP712TypedData(obj) && isHash(obj);
@@ -1,9 +1,9 @@
1
1
  import { FinalExecutionOutcome } from "near-api-js/lib/providers";
2
- import { NearEthAdapter, NearEthTxData, BaseTx } from "near-ca";
2
+ import { NearEthAdapter, SignRequestData } 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";
6
- import { MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
6
+ import { EncodedTxData, MetaTransaction, UserOperation, UserOperationReceipt } from "./types";
7
7
  export declare class TransactionManager {
8
8
  readonly nearAdapter: NearEthAdapter;
9
9
  readonly address: Address;
@@ -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<EncodedTxData>;
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
+ hash: Hash;
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 } 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,19 @@ 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 { payload, evmMessage, hash } = 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,
80
+ key_version: 0,
81
+ }),
82
+ evmData: {
83
+ chainId: signRequest.chainId,
84
+ data: evmMessage,
85
+ hash,
86
+ },
94
87
  };
95
88
  }
96
89
  async executeTransaction(chainId, userOp) {
@@ -144,4 +137,60 @@ export class TransactionManager {
144
137
  throw new Error(`Failed EVM broadcast: ${error instanceof Error ? error.message : String(error)}`);
145
138
  }
146
139
  }
140
+ /**
141
+ * Handles routing of signature requests based on the provided method, chain ID, and parameters.
142
+ *
143
+ * @async
144
+ * @function requestRouter
145
+ * @param {SignRequestData} params - An object containing the method, chain ID, and request parameters.
146
+ * @returns {Promise<{ evmMessage: string; payload: number[]; recoveryData: RecoveryData }>}
147
+ * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
148
+ * the payload (hashed data), and recovery data needed for reconstructing the signature request.
149
+ */
150
+ async requestRouter({ method, chainId, params }, usePaymaster) {
151
+ const safeInfo = {
152
+ address: { value: this.address },
153
+ chainId: chainId.toString(),
154
+ // TODO: Should be able to read this from on chain.
155
+ version: "1.4.1+L2",
156
+ };
157
+ // TODO: We are provided with sender in the input, but also expect safeInfo.
158
+ // We should either confirm they agree or ignore one of the two.
159
+ switch (method) {
160
+ case "eth_signTypedData":
161
+ case "eth_signTypedData_v4":
162
+ case "eth_sign": {
163
+ const [_, messageOrData] = params;
164
+ const message = decodeSafeMessage(messageOrData, safeInfo);
165
+ return {
166
+ evmMessage: message.safeMessageMessage,
167
+ payload: toPayload(message.safeMessageHash),
168
+ hash: message.safeMessageHash,
169
+ };
170
+ }
171
+ case "personal_sign": {
172
+ const [messageHash, _] = params;
173
+ const message = decodeSafeMessage(messageHash, safeInfo);
174
+ return {
175
+ evmMessage: message.safeMessageMessage,
176
+ payload: toPayload(message.safeMessageHash),
177
+ hash: message.safeMessageHash,
178
+ };
179
+ }
180
+ case "eth_sendTransaction": {
181
+ const transactions = metaTransactionsFromRequest(params);
182
+ const userOp = await this.buildTransaction({
183
+ chainId,
184
+ transactions,
185
+ usePaymaster,
186
+ });
187
+ const opHash = await this.opHash(userOp);
188
+ return {
189
+ payload: toPayload(opHash),
190
+ evmMessage: JSON.stringify(userOp),
191
+ hash: await this.opHash(userOp),
192
+ };
193
+ }
194
+ }
195
+ }
147
196
  }
@@ -1,4 +1,5 @@
1
- import { Address, Hex } from "viem";
1
+ import { FunctionCallTransaction, SignArgs } from "near-ca";
2
+ import { Address, Hash, Hex, TransactionSerializable } from "viem";
2
3
  export interface UnsignedUserOperation {
3
4
  sender: Address;
4
5
  nonce: string;
@@ -89,4 +90,14 @@ export interface MetaTransaction {
89
90
  readonly data: string;
90
91
  readonly operation?: OperationType;
91
92
  }
93
+ export interface EncodedTxData {
94
+ evmData: {
95
+ chainId: number;
96
+ data: string | TransactionSerializable;
97
+ hash: Hash;
98
+ };
99
+ nearPayload: FunctionCallTransaction<{
100
+ request: SignArgs;
101
+ }>;
102
+ }
92
103
  export {};
@@ -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.2",
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",