near-safe 0.7.5 → 0.8.0
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.
- 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
|
},
|