near-safe 0.7.6 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/cjs/constants.d.ts +1 -0
- package/dist/cjs/constants.js +2 -1
- package/dist/cjs/decode.d.ts +14 -0
- package/dist/cjs/decode.js +110 -0
- package/dist/cjs/lib/safe-message.d.ts +3 -1
- package/dist/cjs/lib/safe-message.js +21 -0
- package/dist/cjs/lib/safe.d.ts +2 -0
- package/dist/cjs/lib/safe.js +25 -0
- package/dist/cjs/near-safe.d.ts +12 -14
- package/dist/cjs/near-safe.js +54 -62
- package/dist/cjs/types.d.ts +6 -11
- package/dist/cjs/util.d.ts +1 -0
- package/dist/cjs/util.js +9 -0
- package/dist/esm/constants.d.ts +1 -0
- package/dist/esm/constants.js +1 -0
- package/dist/esm/decode.d.ts +14 -0
- package/dist/esm/decode.js +103 -0
- package/dist/esm/lib/safe-message.d.ts +3 -1
- package/dist/esm/lib/safe-message.js +20 -1
- package/dist/esm/lib/safe.d.ts +2 -0
- package/dist/esm/lib/safe.js +27 -2
- package/dist/esm/near-safe.d.ts +12 -14
- package/dist/esm/near-safe.js +58 -66
- package/dist/esm/types.d.ts +6 -11
- package/dist/esm/util.d.ts +1 -0
- package/dist/esm/util.js +8 -0
- package/package.json +2 -2
package/dist/cjs/constants.d.ts
CHANGED
package/dist/cjs/constants.js
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.DEFAULT_SAFE_SALT_NONCE = exports.USER_OP_IDENTIFIER = void 0;
|
3
|
+
exports.SENTINEL_OWNERS = exports.DEFAULT_SAFE_SALT_NONCE = exports.USER_OP_IDENTIFIER = void 0;
|
4
4
|
const viem_1 = require("viem");
|
5
5
|
const DOMAIN_SEPARATOR = "bitte/near-safe";
|
6
6
|
// 0x62697474652f6e6561722d7361666500
|
7
7
|
exports.USER_OP_IDENTIFIER = (0, viem_1.toHex)(DOMAIN_SEPARATOR, { size: 16 });
|
8
8
|
// 130811896738364114529934864114944206080
|
9
9
|
exports.DEFAULT_SAFE_SALT_NONCE = BigInt(exports.USER_OP_IDENTIFIER).toString();
|
10
|
+
exports.SENTINEL_OWNERS = "0x0000000000000000000000000000000000000001";
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { EIP712TypedData } from "near-ca";
|
2
|
+
import { Hex, TransactionSerializable } from "viem";
|
3
|
+
import { DecodedTxData, SafeEncodedSignRequest, UserOperation } from "./types";
|
4
|
+
/**
|
5
|
+
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
6
|
+
*
|
7
|
+
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
8
|
+
* @returns {DecodedTxData} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
9
|
+
*/
|
10
|
+
export declare function decodeTxData({ evmMessage, chainId, }: SafeEncodedSignRequest): DecodedTxData;
|
11
|
+
export declare function decodeTransactionSerializable(chainId: number, tx: TransactionSerializable): DecodedTxData;
|
12
|
+
export declare function decodeRlpHex(chainId: number, tx: Hex): DecodedTxData;
|
13
|
+
export declare function decodeTypedData(chainId: number, data: EIP712TypedData): DecodedTxData;
|
14
|
+
export declare function decodeUserOperation(chainId: number, userOp: UserOperation): DecodedTxData;
|
@@ -0,0 +1,110 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.decodeTxData = decodeTxData;
|
4
|
+
exports.decodeTransactionSerializable = decodeTransactionSerializable;
|
5
|
+
exports.decodeRlpHex = decodeRlpHex;
|
6
|
+
exports.decodeTypedData = decodeTypedData;
|
7
|
+
exports.decodeUserOperation = decodeUserOperation;
|
8
|
+
const ethers_multisend_1 = require("ethers-multisend");
|
9
|
+
const viem_1 = require("viem");
|
10
|
+
const deployments_1 = require("./_gen/deployments");
|
11
|
+
const multisend_1 = require("./lib/multisend");
|
12
|
+
const safe_message_1 = require("./lib/safe-message");
|
13
|
+
/**
|
14
|
+
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
15
|
+
*
|
16
|
+
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
17
|
+
* @returns {DecodedTxData} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
18
|
+
*/
|
19
|
+
function decodeTxData({ evmMessage, chainId, }) {
|
20
|
+
const data = evmMessage;
|
21
|
+
if ((0, safe_message_1.isRlpHex)(evmMessage)) {
|
22
|
+
decodeRlpHex(chainId, evmMessage);
|
23
|
+
}
|
24
|
+
if ((0, safe_message_1.isTransactionSerializable)(data)) {
|
25
|
+
return decodeTransactionSerializable(chainId, data);
|
26
|
+
}
|
27
|
+
if (typeof data !== "string") {
|
28
|
+
return decodeTypedData(chainId, data);
|
29
|
+
}
|
30
|
+
try {
|
31
|
+
// Stringified UserOperation.
|
32
|
+
const userOp = JSON.parse(data);
|
33
|
+
return decodeUserOperation(chainId, userOp);
|
34
|
+
}
|
35
|
+
catch (error) {
|
36
|
+
if (error instanceof SyntaxError) {
|
37
|
+
// Raw message string.
|
38
|
+
return {
|
39
|
+
chainId,
|
40
|
+
costEstimate: "0",
|
41
|
+
transactions: [],
|
42
|
+
message: data,
|
43
|
+
};
|
44
|
+
}
|
45
|
+
else {
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
47
|
+
throw new Error(`decodeTxData: Unexpected error - ${message}`);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
function decodeTransactionSerializable(chainId, tx) {
|
52
|
+
const { gas, maxFeePerGas, maxPriorityFeePerGas, to } = tx;
|
53
|
+
if (chainId !== tx.chainId) {
|
54
|
+
throw Error(`Transaction chainId mismatch ${chainId} != ${tx.chainId}`);
|
55
|
+
}
|
56
|
+
if (!gas || !maxFeePerGas || !maxPriorityFeePerGas) {
|
57
|
+
throw Error(`Insufficient feeData for ${(0, viem_1.serializeTransaction)(tx)}. Check https://rawtxdecode.in/`);
|
58
|
+
}
|
59
|
+
if (!to) {
|
60
|
+
throw Error(`Transaction is missing the 'to' in ${(0, viem_1.serializeTransaction)(tx)}. Check https://rawtxdecode.in/`);
|
61
|
+
}
|
62
|
+
return {
|
63
|
+
chainId,
|
64
|
+
// This is an upper bound on the gas fees (could be lower)
|
65
|
+
costEstimate: (0, viem_1.formatEther)(gas * (maxFeePerGas + maxPriorityFeePerGas)),
|
66
|
+
transactions: [
|
67
|
+
{
|
68
|
+
to,
|
69
|
+
value: (tx.value || 0n).toString(),
|
70
|
+
data: tx.data || "0x",
|
71
|
+
},
|
72
|
+
],
|
73
|
+
};
|
74
|
+
}
|
75
|
+
function decodeRlpHex(chainId, tx) {
|
76
|
+
return decodeTransactionSerializable(chainId, (0, viem_1.parseTransaction)(tx));
|
77
|
+
}
|
78
|
+
function decodeTypedData(chainId, data) {
|
79
|
+
return {
|
80
|
+
chainId,
|
81
|
+
costEstimate: "0",
|
82
|
+
transactions: [],
|
83
|
+
message: data,
|
84
|
+
};
|
85
|
+
}
|
86
|
+
function decodeUserOperation(chainId, userOp) {
|
87
|
+
const { callGasLimit, maxFeePerGas, maxPriorityFeePerGas } = userOp;
|
88
|
+
const maxGasPrice = BigInt(maxFeePerGas) + BigInt(maxPriorityFeePerGas);
|
89
|
+
const { args } = (0, viem_1.decodeFunctionData)({
|
90
|
+
abi: deployments_1.SAFE_DEPLOYMENTS.m4337.abi,
|
91
|
+
data: userOp.callData,
|
92
|
+
});
|
93
|
+
// Determine if singular or double!
|
94
|
+
const transactions = (0, multisend_1.isMultisendTx)(args)
|
95
|
+
? (0, ethers_multisend_1.decodeMulti)(args[2])
|
96
|
+
: [
|
97
|
+
{
|
98
|
+
to: args[0],
|
99
|
+
value: args[1],
|
100
|
+
data: args[2],
|
101
|
+
operation: args[3],
|
102
|
+
},
|
103
|
+
];
|
104
|
+
return {
|
105
|
+
chainId,
|
106
|
+
// This is an upper bound on the gas fees (could be lower)
|
107
|
+
costEstimate: (0, viem_1.formatEther)(BigInt(callGasLimit) * maxGasPrice),
|
108
|
+
transactions,
|
109
|
+
};
|
110
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk";
|
2
2
|
import { EIP712TypedData } from "near-ca";
|
3
|
-
import { Hash } from "viem";
|
3
|
+
import { Hash, Hex, TransactionSerializable } from "viem";
|
4
4
|
export type DecodedSafeMessage = {
|
5
5
|
decodedMessage: string | EIP712TypedData;
|
6
6
|
safeMessageMessage: string;
|
@@ -20,3 +20,5 @@ export type MinimalSafeInfo = Pick<SafeInfo, "address" | "version" | "chainId">;
|
|
20
20
|
* }`
|
21
21
|
*/
|
22
22
|
export declare function decodeSafeMessage(message: string | EIP712TypedData, safe: MinimalSafeInfo): DecodedSafeMessage;
|
23
|
+
export declare function isTransactionSerializable(data: unknown): data is TransactionSerializable;
|
24
|
+
export declare function isRlpHex(data: unknown): data is Hex;
|
@@ -1,6 +1,8 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.decodeSafeMessage = decodeSafeMessage;
|
4
|
+
exports.isTransactionSerializable = isTransactionSerializable;
|
5
|
+
exports.isRlpHex = isRlpHex;
|
4
6
|
const semver_1 = require("semver");
|
5
7
|
const viem_1 = require("viem");
|
6
8
|
/*
|
@@ -93,3 +95,22 @@ function decodeSafeMessage(message, safe) {
|
|
93
95
|
// };
|
94
96
|
// export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean =>
|
95
97
|
// !isEIP712TypedData(obj) && isHash(obj);
|
98
|
+
// Cheeky attempt to serialize. return true if successful!
|
99
|
+
function isTransactionSerializable(data) {
|
100
|
+
try {
|
101
|
+
(0, viem_1.serializeTransaction)(data);
|
102
|
+
return true;
|
103
|
+
}
|
104
|
+
catch (error) {
|
105
|
+
return false;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
function isRlpHex(data) {
|
109
|
+
try {
|
110
|
+
(0, viem_1.parseTransaction)(data);
|
111
|
+
return true;
|
112
|
+
}
|
113
|
+
catch (error) {
|
114
|
+
return false;
|
115
|
+
}
|
116
|
+
}
|
package/dist/cjs/lib/safe.d.ts
CHANGED
@@ -14,8 +14,10 @@ export declare class SafeContractSuite {
|
|
14
14
|
addressForSetup(setup: Hex, saltNonce: string): Promise<Address>;
|
15
15
|
getSetup(owners: string[]): Hex;
|
16
16
|
addOwnerData(newOwner: Address): Hex;
|
17
|
+
removeOwnerData(chainId: number, safeAddress: Address, owner: Address): Promise<Hex>;
|
17
18
|
getOpHash(chainId: number, unsignedUserOp: UserOperation): Promise<Hash>;
|
18
19
|
private factoryDataForSetup;
|
19
20
|
buildUserOp(nonce: bigint, txData: MetaTransaction, safeAddress: Address, feeData: GasPrice, setup: string, safeNotDeployed: boolean, safeSaltNonce: string): Promise<UnsignedUserOperation>;
|
20
21
|
getNonce(address: Address, chainId: number): Promise<bigint>;
|
22
|
+
prevOwner(chainId: number, safeAddress: Address, owner: Address): Promise<Address>;
|
21
23
|
}
|
package/dist/cjs/lib/safe.js
CHANGED
@@ -65,6 +65,15 @@ class SafeContractSuite {
|
|
65
65
|
args: [newOwner, 1],
|
66
66
|
});
|
67
67
|
}
|
68
|
+
async removeOwnerData(chainId, safeAddress, owner) {
|
69
|
+
const prevOwner = await this.prevOwner(chainId, safeAddress, owner);
|
70
|
+
return (0, viem_1.encodeFunctionData)({
|
71
|
+
abi: this.singleton.abi,
|
72
|
+
functionName: "removeOwner",
|
73
|
+
// Keep threshold at 1!
|
74
|
+
args: [prevOwner, owner, 1],
|
75
|
+
});
|
76
|
+
}
|
68
77
|
async getOpHash(chainId, unsignedUserOp) {
|
69
78
|
const { factory, factoryData, verificationGasLimit, callGasLimit, maxPriorityFeePerGas, maxFeePerGas, } = unsignedUserOp;
|
70
79
|
const client = await (0, util_1.getClient)(chainId);
|
@@ -131,5 +140,21 @@ class SafeContractSuite {
|
|
131
140
|
}));
|
132
141
|
return nonce;
|
133
142
|
}
|
143
|
+
async prevOwner(chainId, safeAddress, owner) {
|
144
|
+
const client = (0, util_1.getClient)(chainId);
|
145
|
+
const currentOwners = await client.readContract({
|
146
|
+
address: safeAddress,
|
147
|
+
// abi: this.singleton.abi,
|
148
|
+
abi: (0, viem_1.parseAbi)([
|
149
|
+
"function getOwners() public view returns (address[] memory)",
|
150
|
+
]),
|
151
|
+
functionName: "getOwners",
|
152
|
+
});
|
153
|
+
const ownerIndex = currentOwners.findIndex((t) => t === owner);
|
154
|
+
if (ownerIndex === -1) {
|
155
|
+
throw new Error(`Not a current owner: ${owner}`);
|
156
|
+
}
|
157
|
+
return ownerIndex > 0 ? currentOwners[ownerIndex - 1] : constants_1.SENTINEL_OWNERS;
|
158
|
+
}
|
134
159
|
}
|
135
160
|
exports.SafeContractSuite = SafeContractSuite;
|
package/dist/cjs/near-safe.d.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import { NearConfig } from "near-api-js/lib/near";
|
2
2
|
import { FinalExecutionOutcome } from "near-api-js/lib/providers";
|
3
|
-
import { NearEthAdapter, SignRequestData } from "near-ca";
|
3
|
+
import { NearEthAdapter, SignRequestData, EncodedSignRequest } from "near-ca";
|
4
4
|
import { Address, Hash, Hex } from "viem";
|
5
5
|
import { SafeContractSuite } from "./lib/safe";
|
6
|
-
import {
|
6
|
+
import { EncodedTxData, MetaTransaction, SponsorshipPolicyData, UserOperation, UserOperationReceipt } from "./types";
|
7
7
|
export interface NearSafeConfig {
|
8
8
|
accountId: string;
|
9
9
|
mpcContractId: string;
|
@@ -149,6 +149,14 @@ export declare class NearSafe {
|
|
149
149
|
* @returns {MetaTransaction} - A meta-transaction object for adding the new owner.
|
150
150
|
*/
|
151
151
|
addOwnerTx(address: Address): MetaTransaction;
|
152
|
+
/**
|
153
|
+
* Creates a meta-transaction for adding a new owner to the Safe contract.
|
154
|
+
*
|
155
|
+
* @param {number} chainId - the chainId to build the transaction for.
|
156
|
+
* @param {Address} address - The address of the new owner to be removed.
|
157
|
+
* @returns {Promise<MetaTransaction>} - A meta-transaction object for adding the new owner.
|
158
|
+
*/
|
159
|
+
removeOwnerTx(chainId: number, address: Address): Promise<MetaTransaction>;
|
152
160
|
/**
|
153
161
|
* Creates and returns a new `Erc4337Bundler` instance for the specified chain.
|
154
162
|
*
|
@@ -156,13 +164,6 @@ export declare class NearSafe {
|
|
156
164
|
* @returns {Erc4337Bundler} - A new instance of the `Erc4337Bundler` class configured for the specified chain.
|
157
165
|
*/
|
158
166
|
private bundlerForChainId;
|
159
|
-
/**
|
160
|
-
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
161
|
-
*
|
162
|
-
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
163
|
-
* @returns {DecodedMultisend} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
164
|
-
*/
|
165
|
-
decodeTxData(data: EvmTransactionData): DecodedMultisend;
|
166
167
|
/**
|
167
168
|
* Handles routing of signature requests based on the provided method, chain ID, and parameters.
|
168
169
|
*
|
@@ -173,10 +174,7 @@ export declare class NearSafe {
|
|
173
174
|
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
174
175
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
175
176
|
*/
|
176
|
-
requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<
|
177
|
-
|
178
|
-
payload: number[];
|
179
|
-
hash: Hash;
|
180
|
-
}>;
|
177
|
+
requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<EncodedSignRequest>;
|
178
|
+
encodeForSafe(from: string): boolean;
|
181
179
|
policyForChainId(chainId: number): Promise<SponsorshipPolicyData[]>;
|
182
180
|
}
|
package/dist/cjs/near-safe.js
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.NearSafe = void 0;
|
4
|
-
const ethers_multisend_1 = require("ethers-multisend");
|
5
4
|
const near_ca_1 = require("near-ca");
|
6
5
|
const viem_1 = require("viem");
|
7
6
|
const constants_1 = require("./constants");
|
@@ -132,17 +131,17 @@ class NearSafe {
|
|
132
131
|
* @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
|
133
132
|
*/
|
134
133
|
async encodeSignRequest(signRequest, sponsorshipPolicy) {
|
135
|
-
const {
|
134
|
+
const { evmMessage, hashToSign } = await this.requestRouter(signRequest, sponsorshipPolicy);
|
136
135
|
return {
|
137
136
|
nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
|
138
137
|
path: this.nearAdapter.derivationPath,
|
139
|
-
payload,
|
138
|
+
payload: (0, near_ca_1.toPayload)(hashToSign),
|
140
139
|
key_version: 0,
|
141
140
|
}),
|
142
141
|
evmData: {
|
143
142
|
chainId: signRequest.chainId,
|
144
|
-
|
145
|
-
|
143
|
+
evmMessage,
|
144
|
+
hashToSign,
|
146
145
|
},
|
147
146
|
};
|
148
147
|
}
|
@@ -231,6 +230,20 @@ class NearSafe {
|
|
231
230
|
data: this.safePack.addOwnerData(address),
|
232
231
|
};
|
233
232
|
}
|
233
|
+
/**
|
234
|
+
* Creates a meta-transaction for adding a new owner to the Safe contract.
|
235
|
+
*
|
236
|
+
* @param {number} chainId - the chainId to build the transaction for.
|
237
|
+
* @param {Address} address - The address of the new owner to be removed.
|
238
|
+
* @returns {Promise<MetaTransaction>} - A meta-transaction object for adding the new owner.
|
239
|
+
*/
|
240
|
+
async removeOwnerTx(chainId, address) {
|
241
|
+
return {
|
242
|
+
to: this.address,
|
243
|
+
value: "0",
|
244
|
+
data: await this.safePack.removeOwnerData(chainId, this.address, address),
|
245
|
+
};
|
246
|
+
}
|
234
247
|
/**
|
235
248
|
* Creates and returns a new `Erc4337Bundler` instance for the specified chain.
|
236
249
|
*
|
@@ -240,54 +253,6 @@ class NearSafe {
|
|
240
253
|
bundlerForChainId(chainId) {
|
241
254
|
return new bundler_1.Erc4337Bundler(this.safePack.entryPoint.address, this.pimlicoKey, chainId);
|
242
255
|
}
|
243
|
-
/**
|
244
|
-
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
245
|
-
*
|
246
|
-
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
247
|
-
* @returns {DecodedMultisend} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
248
|
-
*/
|
249
|
-
decodeTxData(data) {
|
250
|
-
try {
|
251
|
-
const userOp = JSON.parse(data.data);
|
252
|
-
const { callGasLimit, maxFeePerGas, maxPriorityFeePerGas } = userOp;
|
253
|
-
const maxGasPrice = BigInt(maxFeePerGas) + BigInt(maxPriorityFeePerGas);
|
254
|
-
const { args } = (0, viem_1.decodeFunctionData)({
|
255
|
-
abi: this.safePack.m4337.abi,
|
256
|
-
data: userOp.callData,
|
257
|
-
});
|
258
|
-
// Determine if singular or double!
|
259
|
-
const transactions = (0, multisend_1.isMultisendTx)(args)
|
260
|
-
? (0, ethers_multisend_1.decodeMulti)(args[2])
|
261
|
-
: [
|
262
|
-
{
|
263
|
-
to: args[0],
|
264
|
-
value: args[1],
|
265
|
-
data: args[2],
|
266
|
-
operation: args[3],
|
267
|
-
},
|
268
|
-
];
|
269
|
-
return {
|
270
|
-
chainId: data.chainId,
|
271
|
-
// This is an upper bound on the gas fees (could be lower)
|
272
|
-
costEstimate: (0, viem_1.formatEther)(BigInt(callGasLimit) * maxGasPrice),
|
273
|
-
transactions,
|
274
|
-
};
|
275
|
-
}
|
276
|
-
catch (error) {
|
277
|
-
if (error instanceof SyntaxError) {
|
278
|
-
return {
|
279
|
-
chainId: data.chainId,
|
280
|
-
costEstimate: "0",
|
281
|
-
transactions: [],
|
282
|
-
message: data.data,
|
283
|
-
};
|
284
|
-
}
|
285
|
-
else {
|
286
|
-
const message = error instanceof Error ? error.message : String(error);
|
287
|
-
throw new Error(`decodeTxData: Unexpected error - ${message}`);
|
288
|
-
}
|
289
|
-
}
|
290
|
-
}
|
291
256
|
/**
|
292
257
|
* Handles routing of signature requests based on the provided method, chain ID, and parameters.
|
293
258
|
*
|
@@ -299,6 +264,27 @@ class NearSafe {
|
|
299
264
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
300
265
|
*/
|
301
266
|
async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
|
267
|
+
// Extract `from` based on the method and check uniqueness
|
268
|
+
const fromAddresses = (() => {
|
269
|
+
switch (method) {
|
270
|
+
case "eth_signTypedData":
|
271
|
+
case "eth_signTypedData_v4":
|
272
|
+
case "eth_sign":
|
273
|
+
return [params[0]];
|
274
|
+
case "personal_sign":
|
275
|
+
return [params[1]];
|
276
|
+
case "eth_sendTransaction":
|
277
|
+
return params.map((p) => p.from);
|
278
|
+
default:
|
279
|
+
return [];
|
280
|
+
}
|
281
|
+
})();
|
282
|
+
// Assert uniqueness
|
283
|
+
(0, util_1.assertUnique)(fromAddresses);
|
284
|
+
// Early return with eoaEncoding if `from` is not the Safe
|
285
|
+
if (!this.encodeForSafe(fromAddresses[0])) {
|
286
|
+
return (0, near_ca_1.requestRouter)({ method, chainId, params });
|
287
|
+
}
|
302
288
|
const safeInfo = {
|
303
289
|
address: { value: this.address },
|
304
290
|
chainId: chainId.toString(),
|
@@ -314,23 +300,21 @@ class NearSafe {
|
|
314
300
|
const [_, messageOrData] = params;
|
315
301
|
const message = (0, safe_message_1.decodeSafeMessage)(messageOrData, safeInfo);
|
316
302
|
return {
|
317
|
-
evmMessage: message.
|
318
|
-
|
319
|
-
hash: message.safeMessageHash,
|
303
|
+
evmMessage: message.decodedMessage,
|
304
|
+
hashToSign: message.safeMessageHash,
|
320
305
|
};
|
321
306
|
}
|
322
307
|
case "personal_sign": {
|
323
308
|
const [messageHash, _] = params;
|
324
309
|
const message = (0, safe_message_1.decodeSafeMessage)(messageHash, safeInfo);
|
325
310
|
return {
|
326
|
-
// TODO(bh2smith) this is a bit of a hack.
|
327
311
|
evmMessage: message.decodedMessage,
|
328
|
-
|
329
|
-
hash: message.safeMessageHash,
|
312
|
+
hashToSign: message.safeMessageHash,
|
330
313
|
};
|
331
314
|
}
|
332
315
|
case "eth_sendTransaction": {
|
333
|
-
const
|
316
|
+
const castParams = params;
|
317
|
+
const transactions = (0, util_1.metaTransactionsFromRequest)(castParams);
|
334
318
|
const userOp = await this.buildTransaction({
|
335
319
|
chainId,
|
336
320
|
transactions,
|
@@ -338,13 +322,21 @@ class NearSafe {
|
|
338
322
|
});
|
339
323
|
const opHash = await this.opHash(chainId, userOp);
|
340
324
|
return {
|
341
|
-
payload: (0, near_ca_1.toPayload)(opHash),
|
342
325
|
evmMessage: JSON.stringify(userOp),
|
343
|
-
|
326
|
+
hashToSign: opHash,
|
344
327
|
};
|
345
328
|
}
|
346
329
|
}
|
347
330
|
}
|
331
|
+
encodeForSafe(from) {
|
332
|
+
const fromLower = from.toLowerCase();
|
333
|
+
if (![this.address, this.mpcAddress]
|
334
|
+
.map((t) => t.toLowerCase())
|
335
|
+
.includes(fromLower)) {
|
336
|
+
throw new Error(`Unexpected from address ${from}`);
|
337
|
+
}
|
338
|
+
return this.address.toLowerCase() === fromLower;
|
339
|
+
}
|
348
340
|
async policyForChainId(chainId) {
|
349
341
|
const bundler = this.bundlerForChainId(chainId);
|
350
342
|
return bundler.getSponsorshipPolicies();
|
package/dist/cjs/types.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { FunctionCallTransaction, SignArgs } from "near-ca";
|
1
|
+
import { EIP712TypedData, EncodedSignRequest, FunctionCallTransaction, SignArgs } from "near-ca";
|
2
2
|
import { Address, Hex, ParseAbi } from "viem";
|
3
3
|
/**
|
4
4
|
* Represents a collection of Safe contract deployments, each with its own address and ABI.
|
@@ -212,20 +212,15 @@ export interface MetaTransaction {
|
|
212
212
|
readonly operation?: OperationType;
|
213
213
|
}
|
214
214
|
/**
|
215
|
-
*
|
215
|
+
* Extends EncodedSignRequest to include a chain ID for cross-chain compatibility.
|
216
216
|
*/
|
217
|
-
export interface
|
218
|
-
/** The chain ID of the network where the transaction is being executed. */
|
217
|
+
export interface SafeEncodedSignRequest extends EncodedSignRequest {
|
219
218
|
chainId: number;
|
220
|
-
/** The raw data of the transaction. */
|
221
|
-
data: string;
|
222
|
-
/** The hash of the transaction. */
|
223
|
-
hash: string;
|
224
219
|
}
|
225
220
|
/**
|
226
221
|
* Represents the decoded details of a multisend transaction.
|
227
222
|
*/
|
228
|
-
export interface
|
223
|
+
export interface DecodedTxData {
|
229
224
|
/** The chain ID of the network where the multisend transaction is being executed. */
|
230
225
|
chainId: number;
|
231
226
|
/** The estimated cost of the multisend transaction in Ether.
|
@@ -235,14 +230,14 @@ export interface DecodedMultisend {
|
|
235
230
|
/** The list of meta-transactions included in the multisend. */
|
236
231
|
transactions: MetaTransaction[];
|
237
232
|
/** Raw Message to sign if no transactions present. */
|
238
|
-
message?: string;
|
233
|
+
message?: string | EIP712TypedData;
|
239
234
|
}
|
240
235
|
/**
|
241
236
|
* Represents encoded transaction data for both NEAR and EVM networks.
|
242
237
|
*/
|
243
238
|
export interface EncodedTxData {
|
244
239
|
/** The encoded transaction data for the EVM network. */
|
245
|
-
evmData:
|
240
|
+
evmData: SafeEncodedSignRequest;
|
246
241
|
/** The encoded payload for a NEAR function call, including the signing arguments. */
|
247
242
|
nearPayload: FunctionCallTransaction<{
|
248
243
|
request: SignArgs;
|
package/dist/cjs/util.d.ts
CHANGED
@@ -37,4 +37,5 @@ export declare function signatureFromTxHash(txHash: string, accountId?: string):
|
|
37
37
|
* @throws Will throw an error if all promises reject with the message "All promises rejected".
|
38
38
|
*/
|
39
39
|
export declare function raceToFirstResolve<T>(promises: Promise<T>[]): Promise<T>;
|
40
|
+
export declare function assertUnique<T>(iterable: Iterable<T>, errorMessage?: string): void;
|
40
41
|
export {};
|
package/dist/cjs/util.js
CHANGED
@@ -10,6 +10,7 @@ exports.metaTransactionsFromRequest = metaTransactionsFromRequest;
|
|
10
10
|
exports.saltNonceFromMessage = saltNonceFromMessage;
|
11
11
|
exports.signatureFromTxHash = signatureFromTxHash;
|
12
12
|
exports.raceToFirstResolve = raceToFirstResolve;
|
13
|
+
exports.assertUnique = assertUnique;
|
13
14
|
const near_ca_1 = require("near-ca");
|
14
15
|
const viem_1 = require("viem");
|
15
16
|
exports.PLACEHOLDER_SIG = (0, viem_1.encodePacked)(["uint48", "uint48"], [0, 0]);
|
@@ -40,6 +41,7 @@ function getClient(chainId) {
|
|
40
41
|
function metaTransactionsFromRequest(params) {
|
41
42
|
let transactions;
|
42
43
|
if ((0, viem_1.isHex)(params)) {
|
44
|
+
// TODO: Consider deprecating this route.
|
43
45
|
// If RLP hex is given, decode the transaction and build EthTransactionParams
|
44
46
|
const tx = (0, viem_1.parseTransaction)(params);
|
45
47
|
transactions = [
|
@@ -52,6 +54,7 @@ function metaTransactionsFromRequest(params) {
|
|
52
54
|
];
|
53
55
|
}
|
54
56
|
else {
|
57
|
+
// TODO: add type guard here.
|
55
58
|
transactions = params;
|
56
59
|
}
|
57
60
|
return transactions.map((tx) => ({
|
@@ -130,3 +133,9 @@ async function raceToFirstResolve(promises) {
|
|
130
133
|
});
|
131
134
|
});
|
132
135
|
}
|
136
|
+
function assertUnique(iterable, errorMessage = "The collection contains more than one distinct element.") {
|
137
|
+
const uniqueValues = new Set(iterable);
|
138
|
+
if (uniqueValues.size > 1) {
|
139
|
+
throw new Error(errorMessage);
|
140
|
+
}
|
141
|
+
}
|
package/dist/esm/constants.d.ts
CHANGED
package/dist/esm/constants.js
CHANGED
@@ -4,3 +4,4 @@ const DOMAIN_SEPARATOR = "bitte/near-safe";
|
|
4
4
|
export const USER_OP_IDENTIFIER = toHex(DOMAIN_SEPARATOR, { size: 16 });
|
5
5
|
// 130811896738364114529934864114944206080
|
6
6
|
export const DEFAULT_SAFE_SALT_NONCE = BigInt(USER_OP_IDENTIFIER).toString();
|
7
|
+
export const SENTINEL_OWNERS = "0x0000000000000000000000000000000000000001";
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { EIP712TypedData } from "near-ca";
|
2
|
+
import { Hex, TransactionSerializable } from "viem";
|
3
|
+
import { DecodedTxData, SafeEncodedSignRequest, UserOperation } from "./types";
|
4
|
+
/**
|
5
|
+
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
6
|
+
*
|
7
|
+
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
8
|
+
* @returns {DecodedTxData} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
9
|
+
*/
|
10
|
+
export declare function decodeTxData({ evmMessage, chainId, }: SafeEncodedSignRequest): DecodedTxData;
|
11
|
+
export declare function decodeTransactionSerializable(chainId: number, tx: TransactionSerializable): DecodedTxData;
|
12
|
+
export declare function decodeRlpHex(chainId: number, tx: Hex): DecodedTxData;
|
13
|
+
export declare function decodeTypedData(chainId: number, data: EIP712TypedData): DecodedTxData;
|
14
|
+
export declare function decodeUserOperation(chainId: number, userOp: UserOperation): DecodedTxData;
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import { decodeMulti } from "ethers-multisend";
|
2
|
+
import { decodeFunctionData, formatEther, parseTransaction, serializeTransaction, } from "viem";
|
3
|
+
import { SAFE_DEPLOYMENTS } from "./_gen/deployments";
|
4
|
+
import { isMultisendTx } from "./lib/multisend";
|
5
|
+
import { isRlpHex, isTransactionSerializable } from "./lib/safe-message";
|
6
|
+
/**
|
7
|
+
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
8
|
+
*
|
9
|
+
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
10
|
+
* @returns {DecodedTxData} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
11
|
+
*/
|
12
|
+
export function decodeTxData({ evmMessage, chainId, }) {
|
13
|
+
const data = evmMessage;
|
14
|
+
if (isRlpHex(evmMessage)) {
|
15
|
+
decodeRlpHex(chainId, evmMessage);
|
16
|
+
}
|
17
|
+
if (isTransactionSerializable(data)) {
|
18
|
+
return decodeTransactionSerializable(chainId, data);
|
19
|
+
}
|
20
|
+
if (typeof data !== "string") {
|
21
|
+
return decodeTypedData(chainId, data);
|
22
|
+
}
|
23
|
+
try {
|
24
|
+
// Stringified UserOperation.
|
25
|
+
const userOp = JSON.parse(data);
|
26
|
+
return decodeUserOperation(chainId, userOp);
|
27
|
+
}
|
28
|
+
catch (error) {
|
29
|
+
if (error instanceof SyntaxError) {
|
30
|
+
// Raw message string.
|
31
|
+
return {
|
32
|
+
chainId,
|
33
|
+
costEstimate: "0",
|
34
|
+
transactions: [],
|
35
|
+
message: data,
|
36
|
+
};
|
37
|
+
}
|
38
|
+
else {
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
40
|
+
throw new Error(`decodeTxData: Unexpected error - ${message}`);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
export function decodeTransactionSerializable(chainId, tx) {
|
45
|
+
const { gas, maxFeePerGas, maxPriorityFeePerGas, to } = tx;
|
46
|
+
if (chainId !== tx.chainId) {
|
47
|
+
throw Error(`Transaction chainId mismatch ${chainId} != ${tx.chainId}`);
|
48
|
+
}
|
49
|
+
if (!gas || !maxFeePerGas || !maxPriorityFeePerGas) {
|
50
|
+
throw Error(`Insufficient feeData for ${serializeTransaction(tx)}. Check https://rawtxdecode.in/`);
|
51
|
+
}
|
52
|
+
if (!to) {
|
53
|
+
throw Error(`Transaction is missing the 'to' in ${serializeTransaction(tx)}. Check https://rawtxdecode.in/`);
|
54
|
+
}
|
55
|
+
return {
|
56
|
+
chainId,
|
57
|
+
// This is an upper bound on the gas fees (could be lower)
|
58
|
+
costEstimate: formatEther(gas * (maxFeePerGas + maxPriorityFeePerGas)),
|
59
|
+
transactions: [
|
60
|
+
{
|
61
|
+
to,
|
62
|
+
value: (tx.value || 0n).toString(),
|
63
|
+
data: tx.data || "0x",
|
64
|
+
},
|
65
|
+
],
|
66
|
+
};
|
67
|
+
}
|
68
|
+
export function decodeRlpHex(chainId, tx) {
|
69
|
+
return decodeTransactionSerializable(chainId, parseTransaction(tx));
|
70
|
+
}
|
71
|
+
export function decodeTypedData(chainId, data) {
|
72
|
+
return {
|
73
|
+
chainId,
|
74
|
+
costEstimate: "0",
|
75
|
+
transactions: [],
|
76
|
+
message: data,
|
77
|
+
};
|
78
|
+
}
|
79
|
+
export function decodeUserOperation(chainId, userOp) {
|
80
|
+
const { callGasLimit, maxFeePerGas, maxPriorityFeePerGas } = userOp;
|
81
|
+
const maxGasPrice = BigInt(maxFeePerGas) + BigInt(maxPriorityFeePerGas);
|
82
|
+
const { args } = decodeFunctionData({
|
83
|
+
abi: SAFE_DEPLOYMENTS.m4337.abi,
|
84
|
+
data: userOp.callData,
|
85
|
+
});
|
86
|
+
// Determine if singular or double!
|
87
|
+
const transactions = isMultisendTx(args)
|
88
|
+
? decodeMulti(args[2])
|
89
|
+
: [
|
90
|
+
{
|
91
|
+
to: args[0],
|
92
|
+
value: args[1],
|
93
|
+
data: args[2],
|
94
|
+
operation: args[3],
|
95
|
+
},
|
96
|
+
];
|
97
|
+
return {
|
98
|
+
chainId,
|
99
|
+
// This is an upper bound on the gas fees (could be lower)
|
100
|
+
costEstimate: formatEther(BigInt(callGasLimit) * maxGasPrice),
|
101
|
+
transactions,
|
102
|
+
};
|
103
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk";
|
2
2
|
import { EIP712TypedData } from "near-ca";
|
3
|
-
import { Hash } from "viem";
|
3
|
+
import { Hash, Hex, TransactionSerializable } from "viem";
|
4
4
|
export type DecodedSafeMessage = {
|
5
5
|
decodedMessage: string | EIP712TypedData;
|
6
6
|
safeMessageMessage: string;
|
@@ -20,3 +20,5 @@ export type MinimalSafeInfo = Pick<SafeInfo, "address" | "version" | "chainId">;
|
|
20
20
|
* }`
|
21
21
|
*/
|
22
22
|
export declare function decodeSafeMessage(message: string | EIP712TypedData, safe: MinimalSafeInfo): DecodedSafeMessage;
|
23
|
+
export declare function isTransactionSerializable(data: unknown): data is TransactionSerializable;
|
24
|
+
export declare function isRlpHex(data: unknown): data is Hex;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { gte } from "semver";
|
2
|
-
import { fromHex, hashMessage, hashTypedData, isHex, } from "viem";
|
2
|
+
import { fromHex, hashMessage, hashTypedData, isHex, parseTransaction, serializeTransaction, } from "viem";
|
3
3
|
/*
|
4
4
|
* From v1.3.0, EIP-1271 support was moved to the CompatibilityFallbackHandler.
|
5
5
|
* Also 1.3.0 introduces the chainId in the domain part of the SafeMessage
|
@@ -90,3 +90,22 @@ export function decodeSafeMessage(message, safe) {
|
|
90
90
|
// };
|
91
91
|
// export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean =>
|
92
92
|
// !isEIP712TypedData(obj) && isHash(obj);
|
93
|
+
// Cheeky attempt to serialize. return true if successful!
|
94
|
+
export function isTransactionSerializable(data) {
|
95
|
+
try {
|
96
|
+
serializeTransaction(data);
|
97
|
+
return true;
|
98
|
+
}
|
99
|
+
catch (error) {
|
100
|
+
return false;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
export function isRlpHex(data) {
|
104
|
+
try {
|
105
|
+
parseTransaction(data);
|
106
|
+
return true;
|
107
|
+
}
|
108
|
+
catch (error) {
|
109
|
+
return false;
|
110
|
+
}
|
111
|
+
}
|
package/dist/esm/lib/safe.d.ts
CHANGED
@@ -14,8 +14,10 @@ export declare class SafeContractSuite {
|
|
14
14
|
addressForSetup(setup: Hex, saltNonce: string): Promise<Address>;
|
15
15
|
getSetup(owners: string[]): Hex;
|
16
16
|
addOwnerData(newOwner: Address): Hex;
|
17
|
+
removeOwnerData(chainId: number, safeAddress: Address, owner: Address): Promise<Hex>;
|
17
18
|
getOpHash(chainId: number, unsignedUserOp: UserOperation): Promise<Hash>;
|
18
19
|
private factoryDataForSetup;
|
19
20
|
buildUserOp(nonce: bigint, txData: MetaTransaction, safeAddress: Address, feeData: GasPrice, setup: string, safeNotDeployed: boolean, safeSaltNonce: string): Promise<UnsignedUserOperation>;
|
20
21
|
getNonce(address: Address, chainId: number): Promise<bigint>;
|
22
|
+
prevOwner(chainId: number, safeAddress: Address, owner: Address): Promise<Address>;
|
21
23
|
}
|
package/dist/esm/lib/safe.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import { concat, encodeFunctionData, encodePacked, getAddress, getCreate2Address, keccak256, toHex, zeroAddress, } from "viem";
|
1
|
+
import { concat, encodeFunctionData, encodePacked, getAddress, getCreate2Address, keccak256, parseAbi, toHex, zeroAddress, } from "viem";
|
2
2
|
import { SAFE_DEPLOYMENTS } from "../_gen/deployments";
|
3
|
-
import { USER_OP_IDENTIFIER } from "../constants";
|
3
|
+
import { SENTINEL_OWNERS, USER_OP_IDENTIFIER } from "../constants";
|
4
4
|
import { PLACEHOLDER_SIG, getClient, packGas, packPaymasterData, } from "../util";
|
5
5
|
/**
|
6
6
|
* All contracts used in account creation & execution
|
@@ -69,6 +69,15 @@ export class SafeContractSuite {
|
|
69
69
|
args: [newOwner, 1],
|
70
70
|
});
|
71
71
|
}
|
72
|
+
async removeOwnerData(chainId, safeAddress, owner) {
|
73
|
+
const prevOwner = await this.prevOwner(chainId, safeAddress, owner);
|
74
|
+
return encodeFunctionData({
|
75
|
+
abi: this.singleton.abi,
|
76
|
+
functionName: "removeOwner",
|
77
|
+
// Keep threshold at 1!
|
78
|
+
args: [prevOwner, owner, 1],
|
79
|
+
});
|
80
|
+
}
|
72
81
|
async getOpHash(chainId, unsignedUserOp) {
|
73
82
|
const { factory, factoryData, verificationGasLimit, callGasLimit, maxPriorityFeePerGas, maxFeePerGas, } = unsignedUserOp;
|
74
83
|
const client = await getClient(chainId);
|
@@ -135,4 +144,20 @@ export class SafeContractSuite {
|
|
135
144
|
}));
|
136
145
|
return nonce;
|
137
146
|
}
|
147
|
+
async prevOwner(chainId, safeAddress, owner) {
|
148
|
+
const client = getClient(chainId);
|
149
|
+
const currentOwners = await client.readContract({
|
150
|
+
address: safeAddress,
|
151
|
+
// abi: this.singleton.abi,
|
152
|
+
abi: parseAbi([
|
153
|
+
"function getOwners() public view returns (address[] memory)",
|
154
|
+
]),
|
155
|
+
functionName: "getOwners",
|
156
|
+
});
|
157
|
+
const ownerIndex = currentOwners.findIndex((t) => t === owner);
|
158
|
+
if (ownerIndex === -1) {
|
159
|
+
throw new Error(`Not a current owner: ${owner}`);
|
160
|
+
}
|
161
|
+
return ownerIndex > 0 ? currentOwners[ownerIndex - 1] : SENTINEL_OWNERS;
|
162
|
+
}
|
138
163
|
}
|
package/dist/esm/near-safe.d.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import { NearConfig } from "near-api-js/lib/near";
|
2
2
|
import { FinalExecutionOutcome } from "near-api-js/lib/providers";
|
3
|
-
import { NearEthAdapter, SignRequestData } from "near-ca";
|
3
|
+
import { NearEthAdapter, SignRequestData, EncodedSignRequest } from "near-ca";
|
4
4
|
import { Address, Hash, Hex } from "viem";
|
5
5
|
import { SafeContractSuite } from "./lib/safe";
|
6
|
-
import {
|
6
|
+
import { EncodedTxData, MetaTransaction, SponsorshipPolicyData, UserOperation, UserOperationReceipt } from "./types";
|
7
7
|
export interface NearSafeConfig {
|
8
8
|
accountId: string;
|
9
9
|
mpcContractId: string;
|
@@ -149,6 +149,14 @@ export declare class NearSafe {
|
|
149
149
|
* @returns {MetaTransaction} - A meta-transaction object for adding the new owner.
|
150
150
|
*/
|
151
151
|
addOwnerTx(address: Address): MetaTransaction;
|
152
|
+
/**
|
153
|
+
* Creates a meta-transaction for adding a new owner to the Safe contract.
|
154
|
+
*
|
155
|
+
* @param {number} chainId - the chainId to build the transaction for.
|
156
|
+
* @param {Address} address - The address of the new owner to be removed.
|
157
|
+
* @returns {Promise<MetaTransaction>} - A meta-transaction object for adding the new owner.
|
158
|
+
*/
|
159
|
+
removeOwnerTx(chainId: number, address: Address): Promise<MetaTransaction>;
|
152
160
|
/**
|
153
161
|
* Creates and returns a new `Erc4337Bundler` instance for the specified chain.
|
154
162
|
*
|
@@ -156,13 +164,6 @@ export declare class NearSafe {
|
|
156
164
|
* @returns {Erc4337Bundler} - A new instance of the `Erc4337Bundler` class configured for the specified chain.
|
157
165
|
*/
|
158
166
|
private bundlerForChainId;
|
159
|
-
/**
|
160
|
-
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
161
|
-
*
|
162
|
-
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
163
|
-
* @returns {DecodedMultisend} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
164
|
-
*/
|
165
|
-
decodeTxData(data: EvmTransactionData): DecodedMultisend;
|
166
167
|
/**
|
167
168
|
* Handles routing of signature requests based on the provided method, chain ID, and parameters.
|
168
169
|
*
|
@@ -173,10 +174,7 @@ export declare class NearSafe {
|
|
173
174
|
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
174
175
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
175
176
|
*/
|
176
|
-
requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<
|
177
|
-
|
178
|
-
payload: number[];
|
179
|
-
hash: Hash;
|
180
|
-
}>;
|
177
|
+
requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<EncodedSignRequest>;
|
178
|
+
encodeForSafe(from: string): boolean;
|
181
179
|
policyForChainId(chainId: number): Promise<SponsorshipPolicyData[]>;
|
182
180
|
}
|
package/dist/esm/near-safe.js
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
3
|
-
import { decodeFunctionData, formatEther, serializeSignature, } from "viem";
|
1
|
+
import { setupAdapter, signatureFromOutcome, toPayload, requestRouter as mpcRequestRouter, } from "near-ca";
|
2
|
+
import { serializeSignature } from "viem";
|
4
3
|
import { DEFAULT_SAFE_SALT_NONCE } from "./constants";
|
5
4
|
import { Erc4337Bundler } from "./lib/bundler";
|
6
|
-
import { encodeMulti
|
5
|
+
import { encodeMulti } from "./lib/multisend";
|
7
6
|
import { SafeContractSuite } from "./lib/safe";
|
8
7
|
import { decodeSafeMessage } from "./lib/safe-message";
|
9
|
-
import { getClient, isContract, metaTransactionsFromRequest, packSignature, } from "./util";
|
8
|
+
import { assertUnique, getClient, isContract, metaTransactionsFromRequest, packSignature, } from "./util";
|
10
9
|
export class NearSafe {
|
11
10
|
nearAdapter;
|
12
11
|
address;
|
@@ -135,17 +134,17 @@ export class NearSafe {
|
|
135
134
|
* @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
|
136
135
|
*/
|
137
136
|
async encodeSignRequest(signRequest, sponsorshipPolicy) {
|
138
|
-
const {
|
137
|
+
const { evmMessage, hashToSign } = await this.requestRouter(signRequest, sponsorshipPolicy);
|
139
138
|
return {
|
140
139
|
nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
|
141
140
|
path: this.nearAdapter.derivationPath,
|
142
|
-
payload,
|
141
|
+
payload: toPayload(hashToSign),
|
143
142
|
key_version: 0,
|
144
143
|
}),
|
145
144
|
evmData: {
|
146
145
|
chainId: signRequest.chainId,
|
147
|
-
|
148
|
-
|
146
|
+
evmMessage,
|
147
|
+
hashToSign,
|
149
148
|
},
|
150
149
|
};
|
151
150
|
}
|
@@ -234,6 +233,20 @@ export class NearSafe {
|
|
234
233
|
data: this.safePack.addOwnerData(address),
|
235
234
|
};
|
236
235
|
}
|
236
|
+
/**
|
237
|
+
* Creates a meta-transaction for adding a new owner to the Safe contract.
|
238
|
+
*
|
239
|
+
* @param {number} chainId - the chainId to build the transaction for.
|
240
|
+
* @param {Address} address - The address of the new owner to be removed.
|
241
|
+
* @returns {Promise<MetaTransaction>} - A meta-transaction object for adding the new owner.
|
242
|
+
*/
|
243
|
+
async removeOwnerTx(chainId, address) {
|
244
|
+
return {
|
245
|
+
to: this.address,
|
246
|
+
value: "0",
|
247
|
+
data: await this.safePack.removeOwnerData(chainId, this.address, address),
|
248
|
+
};
|
249
|
+
}
|
237
250
|
/**
|
238
251
|
* Creates and returns a new `Erc4337Bundler` instance for the specified chain.
|
239
252
|
*
|
@@ -243,54 +256,6 @@ export class NearSafe {
|
|
243
256
|
bundlerForChainId(chainId) {
|
244
257
|
return new Erc4337Bundler(this.safePack.entryPoint.address, this.pimlicoKey, chainId);
|
245
258
|
}
|
246
|
-
/**
|
247
|
-
* Decodes transaction data for a given EVM transaction and extracts relevant details.
|
248
|
-
*
|
249
|
-
* @param {EvmTransactionData} data - The raw transaction data to be decoded.
|
250
|
-
* @returns {DecodedMultisend} - An object containing the chain ID, estimated cost, and a list of decoded meta-transactions.
|
251
|
-
*/
|
252
|
-
decodeTxData(data) {
|
253
|
-
try {
|
254
|
-
const userOp = JSON.parse(data.data);
|
255
|
-
const { callGasLimit, maxFeePerGas, maxPriorityFeePerGas } = userOp;
|
256
|
-
const maxGasPrice = BigInt(maxFeePerGas) + BigInt(maxPriorityFeePerGas);
|
257
|
-
const { args } = decodeFunctionData({
|
258
|
-
abi: this.safePack.m4337.abi,
|
259
|
-
data: userOp.callData,
|
260
|
-
});
|
261
|
-
// Determine if singular or double!
|
262
|
-
const transactions = isMultisendTx(args)
|
263
|
-
? decodeMulti(args[2])
|
264
|
-
: [
|
265
|
-
{
|
266
|
-
to: args[0],
|
267
|
-
value: args[1],
|
268
|
-
data: args[2],
|
269
|
-
operation: args[3],
|
270
|
-
},
|
271
|
-
];
|
272
|
-
return {
|
273
|
-
chainId: data.chainId,
|
274
|
-
// This is an upper bound on the gas fees (could be lower)
|
275
|
-
costEstimate: formatEther(BigInt(callGasLimit) * maxGasPrice),
|
276
|
-
transactions,
|
277
|
-
};
|
278
|
-
}
|
279
|
-
catch (error) {
|
280
|
-
if (error instanceof SyntaxError) {
|
281
|
-
return {
|
282
|
-
chainId: data.chainId,
|
283
|
-
costEstimate: "0",
|
284
|
-
transactions: [],
|
285
|
-
message: data.data,
|
286
|
-
};
|
287
|
-
}
|
288
|
-
else {
|
289
|
-
const message = error instanceof Error ? error.message : String(error);
|
290
|
-
throw new Error(`decodeTxData: Unexpected error - ${message}`);
|
291
|
-
}
|
292
|
-
}
|
293
|
-
}
|
294
259
|
/**
|
295
260
|
* Handles routing of signature requests based on the provided method, chain ID, and parameters.
|
296
261
|
*
|
@@ -302,6 +267,27 @@ export class NearSafe {
|
|
302
267
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
303
268
|
*/
|
304
269
|
async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
|
270
|
+
// Extract `from` based on the method and check uniqueness
|
271
|
+
const fromAddresses = (() => {
|
272
|
+
switch (method) {
|
273
|
+
case "eth_signTypedData":
|
274
|
+
case "eth_signTypedData_v4":
|
275
|
+
case "eth_sign":
|
276
|
+
return [params[0]];
|
277
|
+
case "personal_sign":
|
278
|
+
return [params[1]];
|
279
|
+
case "eth_sendTransaction":
|
280
|
+
return params.map((p) => p.from);
|
281
|
+
default:
|
282
|
+
return [];
|
283
|
+
}
|
284
|
+
})();
|
285
|
+
// Assert uniqueness
|
286
|
+
assertUnique(fromAddresses);
|
287
|
+
// Early return with eoaEncoding if `from` is not the Safe
|
288
|
+
if (!this.encodeForSafe(fromAddresses[0])) {
|
289
|
+
return mpcRequestRouter({ method, chainId, params });
|
290
|
+
}
|
305
291
|
const safeInfo = {
|
306
292
|
address: { value: this.address },
|
307
293
|
chainId: chainId.toString(),
|
@@ -317,23 +303,21 @@ export class NearSafe {
|
|
317
303
|
const [_, messageOrData] = params;
|
318
304
|
const message = decodeSafeMessage(messageOrData, safeInfo);
|
319
305
|
return {
|
320
|
-
evmMessage: message.
|
321
|
-
|
322
|
-
hash: message.safeMessageHash,
|
306
|
+
evmMessage: message.decodedMessage,
|
307
|
+
hashToSign: message.safeMessageHash,
|
323
308
|
};
|
324
309
|
}
|
325
310
|
case "personal_sign": {
|
326
311
|
const [messageHash, _] = params;
|
327
312
|
const message = decodeSafeMessage(messageHash, safeInfo);
|
328
313
|
return {
|
329
|
-
// TODO(bh2smith) this is a bit of a hack.
|
330
314
|
evmMessage: message.decodedMessage,
|
331
|
-
|
332
|
-
hash: message.safeMessageHash,
|
315
|
+
hashToSign: message.safeMessageHash,
|
333
316
|
};
|
334
317
|
}
|
335
318
|
case "eth_sendTransaction": {
|
336
|
-
const
|
319
|
+
const castParams = params;
|
320
|
+
const transactions = metaTransactionsFromRequest(castParams);
|
337
321
|
const userOp = await this.buildTransaction({
|
338
322
|
chainId,
|
339
323
|
transactions,
|
@@ -341,13 +325,21 @@ export class NearSafe {
|
|
341
325
|
});
|
342
326
|
const opHash = await this.opHash(chainId, userOp);
|
343
327
|
return {
|
344
|
-
payload: toPayload(opHash),
|
345
328
|
evmMessage: JSON.stringify(userOp),
|
346
|
-
|
329
|
+
hashToSign: opHash,
|
347
330
|
};
|
348
331
|
}
|
349
332
|
}
|
350
333
|
}
|
334
|
+
encodeForSafe(from) {
|
335
|
+
const fromLower = from.toLowerCase();
|
336
|
+
if (![this.address, this.mpcAddress]
|
337
|
+
.map((t) => t.toLowerCase())
|
338
|
+
.includes(fromLower)) {
|
339
|
+
throw new Error(`Unexpected from address ${from}`);
|
340
|
+
}
|
341
|
+
return this.address.toLowerCase() === fromLower;
|
342
|
+
}
|
351
343
|
async policyForChainId(chainId) {
|
352
344
|
const bundler = this.bundlerForChainId(chainId);
|
353
345
|
return bundler.getSponsorshipPolicies();
|
package/dist/esm/types.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { FunctionCallTransaction, SignArgs } from "near-ca";
|
1
|
+
import { EIP712TypedData, EncodedSignRequest, FunctionCallTransaction, SignArgs } from "near-ca";
|
2
2
|
import { Address, Hex, ParseAbi } from "viem";
|
3
3
|
/**
|
4
4
|
* Represents a collection of Safe contract deployments, each with its own address and ABI.
|
@@ -212,20 +212,15 @@ export interface MetaTransaction {
|
|
212
212
|
readonly operation?: OperationType;
|
213
213
|
}
|
214
214
|
/**
|
215
|
-
*
|
215
|
+
* Extends EncodedSignRequest to include a chain ID for cross-chain compatibility.
|
216
216
|
*/
|
217
|
-
export interface
|
218
|
-
/** The chain ID of the network where the transaction is being executed. */
|
217
|
+
export interface SafeEncodedSignRequest extends EncodedSignRequest {
|
219
218
|
chainId: number;
|
220
|
-
/** The raw data of the transaction. */
|
221
|
-
data: string;
|
222
|
-
/** The hash of the transaction. */
|
223
|
-
hash: string;
|
224
219
|
}
|
225
220
|
/**
|
226
221
|
* Represents the decoded details of a multisend transaction.
|
227
222
|
*/
|
228
|
-
export interface
|
223
|
+
export interface DecodedTxData {
|
229
224
|
/** The chain ID of the network where the multisend transaction is being executed. */
|
230
225
|
chainId: number;
|
231
226
|
/** The estimated cost of the multisend transaction in Ether.
|
@@ -235,14 +230,14 @@ export interface DecodedMultisend {
|
|
235
230
|
/** The list of meta-transactions included in the multisend. */
|
236
231
|
transactions: MetaTransaction[];
|
237
232
|
/** Raw Message to sign if no transactions present. */
|
238
|
-
message?: string;
|
233
|
+
message?: string | EIP712TypedData;
|
239
234
|
}
|
240
235
|
/**
|
241
236
|
* Represents encoded transaction data for both NEAR and EVM networks.
|
242
237
|
*/
|
243
238
|
export interface EncodedTxData {
|
244
239
|
/** The encoded transaction data for the EVM network. */
|
245
|
-
evmData:
|
240
|
+
evmData: SafeEncodedSignRequest;
|
246
241
|
/** The encoded payload for a NEAR function call, including the signing arguments. */
|
247
242
|
nearPayload: FunctionCallTransaction<{
|
248
243
|
request: SignArgs;
|
package/dist/esm/util.d.ts
CHANGED
@@ -37,4 +37,5 @@ export declare function signatureFromTxHash(txHash: string, accountId?: string):
|
|
37
37
|
* @throws Will throw an error if all promises reject with the message "All promises rejected".
|
38
38
|
*/
|
39
39
|
export declare function raceToFirstResolve<T>(promises: Promise<T>[]): Promise<T>;
|
40
|
+
export declare function assertUnique<T>(iterable: Iterable<T>, errorMessage?: string): void;
|
40
41
|
export {};
|
package/dist/esm/util.js
CHANGED
@@ -27,6 +27,7 @@ export function getClient(chainId) {
|
|
27
27
|
export function metaTransactionsFromRequest(params) {
|
28
28
|
let transactions;
|
29
29
|
if (isHex(params)) {
|
30
|
+
// TODO: Consider deprecating this route.
|
30
31
|
// If RLP hex is given, decode the transaction and build EthTransactionParams
|
31
32
|
const tx = parseTransaction(params);
|
32
33
|
transactions = [
|
@@ -39,6 +40,7 @@ export function metaTransactionsFromRequest(params) {
|
|
39
40
|
];
|
40
41
|
}
|
41
42
|
else {
|
43
|
+
// TODO: add type guard here.
|
42
44
|
transactions = params;
|
43
45
|
}
|
44
46
|
return transactions.map((tx) => ({
|
@@ -117,3 +119,9 @@ export async function raceToFirstResolve(promises) {
|
|
117
119
|
});
|
118
120
|
});
|
119
121
|
}
|
122
|
+
export function assertUnique(iterable, errorMessage = "The collection contains more than one distinct element.") {
|
123
|
+
const uniqueValues = new Set(iterable);
|
124
|
+
if (uniqueValues.size > 1) {
|
125
|
+
throw new Error(errorMessage);
|
126
|
+
}
|
127
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "near-safe",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.8.0",
|
4
4
|
"license": "MIT",
|
5
5
|
"description": "An SDK for controlling Ethereum Smart Accounts via ERC4337 from a Near Account.",
|
6
6
|
"author": "bh2smith",
|
@@ -44,7 +44,7 @@
|
|
44
44
|
"@safe-global/safe-gateway-typescript-sdk": "^3.22.2",
|
45
45
|
"ethers-multisend": "^3.1.0",
|
46
46
|
"near-api-js": "^5.0.0",
|
47
|
-
"near-ca": "^0.
|
47
|
+
"near-ca": "^0.6.0",
|
48
48
|
"semver": "^7.6.3",
|
49
49
|
"viem": "^2.16.5"
|
50
50
|
},
|