near-safe 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/cjs/constants.d.ts +2 -0
- package/dist/cjs/constants.js +9 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/lib/bundler.d.ts +5 -2
- package/dist/cjs/lib/bundler.js +3 -2
- package/dist/cjs/lib/safe.js +15 -10
- package/dist/cjs/near-safe.d.ts +4 -4
- package/dist/cjs/near-safe.js +11 -8
- package/dist/cjs/types.d.ts +1 -1
- package/dist/cjs/util.d.ts +27 -0
- package/dist/cjs/util.js +73 -1
- package/dist/esm/constants.d.ts +2 -0
- package/dist/esm/constants.js +6 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/lib/bundler.d.ts +5 -2
- package/dist/esm/lib/bundler.js +3 -2
- package/dist/esm/lib/safe.js +16 -11
- package/dist/esm/near-safe.d.ts +4 -4
- package/dist/esm/near-safe.js +11 -8
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/util.d.ts +27 -0
- package/dist/esm/util.js +72 -3
- package/package.json +3 -3
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.DEFAULT_SAFE_SALT_NONCE = exports.USER_OP_IDENTIFIER = void 0;
|
4
|
+
const viem_1 = require("viem");
|
5
|
+
const DOMAIN_SEPARATOR = "bitte/near-safe";
|
6
|
+
// 0x62697474652f6e6561722d7361666500
|
7
|
+
exports.USER_OP_IDENTIFIER = (0, viem_1.toHex)(DOMAIN_SEPARATOR, { size: 16 });
|
8
|
+
// 130811896738364114529934864114944206080
|
9
|
+
exports.DEFAULT_SAFE_SALT_NONCE = BigInt(exports.USER_OP_IDENTIFIER).toString();
|
package/dist/cjs/index.d.ts
CHANGED
package/dist/cjs/index.js
CHANGED
@@ -18,6 +18,7 @@ exports.populateTx = exports.Network = void 0;
|
|
18
18
|
__exportStar(require("./near-safe"), exports);
|
19
19
|
__exportStar(require("./types"), exports);
|
20
20
|
__exportStar(require("./util"), exports);
|
21
|
+
__exportStar(require("./constants"), exports);
|
21
22
|
var near_ca_1 = require("near-ca");
|
22
23
|
Object.defineProperty(exports, "Network", { enumerable: true, get: function () { return near_ca_1.Network; } });
|
23
24
|
Object.defineProperty(exports, "populateTx", { enumerable: true, get: function () { return near_ca_1.populateTx; } });
|
@@ -1,9 +1,12 @@
|
|
1
1
|
import { Address, Hash, PublicClient, Transport } from "viem";
|
2
2
|
import { GasPrices, PaymasterData, UnsignedUserOperation, UserOperation, UserOperationReceipt } from "../types";
|
3
|
+
type SponsorshipPolicy = {
|
4
|
+
sponsorshipPolicyId: string;
|
5
|
+
};
|
3
6
|
type BundlerRpcSchema = [
|
4
7
|
{
|
5
8
|
Method: "pm_sponsorUserOperation";
|
6
|
-
Parameters: [UnsignedUserOperation, Address];
|
9
|
+
Parameters: [UnsignedUserOperation, Address, SponsorshipPolicy];
|
7
10
|
ReturnType: PaymasterData;
|
8
11
|
},
|
9
12
|
{
|
@@ -28,7 +31,7 @@ export declare class Erc4337Bundler {
|
|
28
31
|
apiKey: string;
|
29
32
|
chainId: number;
|
30
33
|
constructor(entryPointAddress: Address, apiKey: string, chainId: number);
|
31
|
-
getPaymasterData(rawUserOp: UnsignedUserOperation,
|
34
|
+
getPaymasterData(rawUserOp: UnsignedUserOperation, safeNotDeployed: boolean, sponsorshipPolicy?: string): Promise<PaymasterData>;
|
32
35
|
sendUserOperation(userOp: UserOperation): Promise<Hash>;
|
33
36
|
getGasPrice(): Promise<GasPrices>;
|
34
37
|
getUserOpReceipt(userOpHash: Hash): Promise<UserOperationReceipt>;
|
package/dist/cjs/lib/bundler.js
CHANGED
@@ -17,15 +17,16 @@ class Erc4337Bundler {
|
|
17
17
|
rpcSchema: (0, viem_1.rpcSchema)(),
|
18
18
|
});
|
19
19
|
}
|
20
|
-
async getPaymasterData(rawUserOp,
|
20
|
+
async getPaymasterData(rawUserOp, safeNotDeployed, sponsorshipPolicy) {
|
21
21
|
// TODO: Keep this option out of the bundler
|
22
|
-
if (
|
22
|
+
if (sponsorshipPolicy) {
|
23
23
|
console.log("Requesting paymaster data...");
|
24
24
|
return handleRequest(() => this.client.request({
|
25
25
|
method: "pm_sponsorUserOperation",
|
26
26
|
params: [
|
27
27
|
{ ...rawUserOp, signature: util_1.PLACEHOLDER_SIG },
|
28
28
|
this.entryPointAddress,
|
29
|
+
{ sponsorshipPolicyId: sponsorshipPolicy },
|
29
30
|
],
|
30
31
|
}));
|
31
32
|
}
|
package/dist/cjs/lib/safe.js
CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SafeContractSuite = void 0;
|
4
4
|
const viem_1 = require("viem");
|
5
5
|
const deployments_1 = require("../_gen/deployments");
|
6
|
+
const constants_1 = require("../constants");
|
6
7
|
const util_1 = require("../util");
|
7
8
|
/**
|
8
9
|
* All contracts used in account creation & execution
|
@@ -104,16 +105,20 @@ class SafeContractSuite {
|
|
104
105
|
nonce: (0, viem_1.toHex)(nonce),
|
105
106
|
...this.factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce),
|
106
107
|
// <https://github.com/safe-global/safe-modules/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/4337/contracts/Safe4337Module.sol#L172>
|
107
|
-
callData: (0, viem_1.
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
108
|
+
callData: (0, viem_1.concat)([
|
109
|
+
(0, viem_1.encodeFunctionData)({
|
110
|
+
abi: this.m4337.abi,
|
111
|
+
functionName: "executeUserOp",
|
112
|
+
args: [
|
113
|
+
txData.to,
|
114
|
+
BigInt(txData.value),
|
115
|
+
txData.data,
|
116
|
+
txData.operation || 0,
|
117
|
+
],
|
118
|
+
}),
|
119
|
+
// Append On-Chain Identifier:
|
120
|
+
constants_1.USER_OP_IDENTIFIER,
|
121
|
+
]),
|
117
122
|
...feeData,
|
118
123
|
};
|
119
124
|
}
|
package/dist/cjs/near-safe.d.ts
CHANGED
@@ -7,9 +7,9 @@ import { DecodedMultisend, EncodedTxData, EvmTransactionData, MetaTransaction, U
|
|
7
7
|
export interface NearSafeConfig {
|
8
8
|
accountId: string;
|
9
9
|
mpcContractId: string;
|
10
|
-
pimlicoKey: string;
|
11
10
|
nearConfig?: NearConfig;
|
12
11
|
privateKey?: string;
|
12
|
+
pimlicoKey: string;
|
13
13
|
safeSaltNonce?: string;
|
14
14
|
}
|
15
15
|
export declare class NearSafe {
|
@@ -70,7 +70,7 @@ export declare class NearSafe {
|
|
70
70
|
buildTransaction(args: {
|
71
71
|
chainId: number;
|
72
72
|
transactions: MetaTransaction[];
|
73
|
-
|
73
|
+
sponsorshipPolicy?: string;
|
74
74
|
}): Promise<UserOperation>;
|
75
75
|
/**
|
76
76
|
* Signs a transaction with the NEAR adapter using the provided operation hash.
|
@@ -93,7 +93,7 @@ export declare class NearSafe {
|
|
93
93
|
* @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
|
94
94
|
* @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
|
95
95
|
*/
|
96
|
-
encodeSignRequest(signRequest: SignRequestData,
|
96
|
+
encodeSignRequest(signRequest: SignRequestData, sponsorshipPolicy?: string): Promise<EncodedTxData>;
|
97
97
|
/**
|
98
98
|
* Broadcasts a user operation to the EVM network with a provided signature.
|
99
99
|
* Warning: Uses a private ethRPC with sensitive Pimlico API key (should be run server side).
|
@@ -173,7 +173,7 @@ export declare class NearSafe {
|
|
173
173
|
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
174
174
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
175
175
|
*/
|
176
|
-
requestRouter({ method, chainId, params }: SignRequestData,
|
176
|
+
requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<{
|
177
177
|
evmMessage: string;
|
178
178
|
payload: number[];
|
179
179
|
hash: Hash;
|
package/dist/cjs/near-safe.js
CHANGED
@@ -4,6 +4,7 @@ exports.NearSafe = void 0;
|
|
4
4
|
const ethers_multisend_1 = require("ethers-multisend");
|
5
5
|
const near_ca_1 = require("near-ca");
|
6
6
|
const viem_1 = require("viem");
|
7
|
+
const constants_1 = require("./constants");
|
7
8
|
const bundler_1 = require("./lib/bundler");
|
8
9
|
const multisend_1 = require("./lib/multisend");
|
9
10
|
const safe_1 = require("./lib/safe");
|
@@ -18,6 +19,7 @@ class NearSafe {
|
|
18
19
|
*/
|
19
20
|
static async create(config) {
|
20
21
|
const { pimlicoKey, safeSaltNonce } = config;
|
22
|
+
// const nearAdapter = await mockAdapter();
|
21
23
|
const nearAdapter = await (0, near_ca_1.setupAdapter)({ ...config });
|
22
24
|
const safePack = new safe_1.SafeContractSuite();
|
23
25
|
const setup = safePack.getSetup([nearAdapter.address]);
|
@@ -28,7 +30,7 @@ class NearSafe {
|
|
28
30
|
MPC EOA: ${nearAdapter.address}
|
29
31
|
Safe: ${safeAddress}
|
30
32
|
`);
|
31
|
-
return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce ||
|
33
|
+
return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce || constants_1.DEFAULT_SAFE_SALT_NONCE);
|
32
34
|
}
|
33
35
|
/**
|
34
36
|
* Constructs a new `NearSafe` object with the provided parameters.
|
@@ -62,7 +64,7 @@ class NearSafe {
|
|
62
64
|
* @returns {string} - The contract ID of the MPC contract.
|
63
65
|
*/
|
64
66
|
get mpcContractId() {
|
65
|
-
return this.nearAdapter.mpcContract.
|
67
|
+
return this.nearAdapter.mpcContract.accountId();
|
66
68
|
}
|
67
69
|
/**
|
68
70
|
* Retrieves the balance of the Safe account on the specified EVM chain.
|
@@ -85,10 +87,11 @@ class NearSafe {
|
|
85
87
|
* @throws {Error} - Throws an error if the transaction set is empty or if any operation fails during the building process.
|
86
88
|
*/
|
87
89
|
async buildTransaction(args) {
|
88
|
-
const { transactions,
|
90
|
+
const { transactions, sponsorshipPolicy, chainId } = args;
|
89
91
|
if (transactions.length === 0) {
|
90
92
|
throw new Error("Empty transaction set!");
|
91
93
|
}
|
94
|
+
console.log(`Building UserOp on chainId ${chainId} with ${transactions.length} transaction(s)`);
|
92
95
|
const bundler = this.bundlerForChainId(chainId);
|
93
96
|
const [gasFees, nonce, safeDeployed] = await Promise.all([
|
94
97
|
bundler.getGasPrice(),
|
@@ -98,7 +101,7 @@ class NearSafe {
|
|
98
101
|
// Build Singular MetaTransaction for Multisend from transaction list.
|
99
102
|
const tx = transactions.length > 1 ? (0, multisend_1.encodeMulti)(transactions) : transactions[0];
|
100
103
|
const rawUserOp = await this.safePack.buildUserOp(nonce, tx, this.address, gasFees.fast, this.setup, !safeDeployed, this.safeSaltNonce);
|
101
|
-
const paymasterData = await bundler.getPaymasterData(rawUserOp,
|
104
|
+
const paymasterData = await bundler.getPaymasterData(rawUserOp, !safeDeployed, sponsorshipPolicy);
|
102
105
|
const unsignedUserOp = { ...rawUserOp, ...paymasterData };
|
103
106
|
return unsignedUserOp;
|
104
107
|
}
|
@@ -128,8 +131,8 @@ class NearSafe {
|
|
128
131
|
* @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
|
129
132
|
* @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
|
130
133
|
*/
|
131
|
-
async encodeSignRequest(signRequest,
|
132
|
-
const { payload, evmMessage, hash } = await this.requestRouter(signRequest,
|
134
|
+
async encodeSignRequest(signRequest, sponsorshipPolicy) {
|
135
|
+
const { payload, evmMessage, hash } = await this.requestRouter(signRequest, sponsorshipPolicy);
|
133
136
|
return {
|
134
137
|
nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
|
135
138
|
path: this.nearAdapter.derivationPath,
|
@@ -280,7 +283,7 @@ class NearSafe {
|
|
280
283
|
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
281
284
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
282
285
|
*/
|
283
|
-
async requestRouter({ method, chainId, params },
|
286
|
+
async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
|
284
287
|
const safeInfo = {
|
285
288
|
address: { value: this.address },
|
286
289
|
chainId: chainId.toString(),
|
@@ -315,7 +318,7 @@ class NearSafe {
|
|
315
318
|
const userOp = await this.buildTransaction({
|
316
319
|
chainId,
|
317
320
|
transactions,
|
318
|
-
|
321
|
+
...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
|
319
322
|
});
|
320
323
|
const opHash = await this.opHash(chainId, userOp);
|
321
324
|
return {
|
package/dist/cjs/types.d.ts
CHANGED
@@ -80,7 +80,7 @@ export interface PaymasterData {
|
|
80
80
|
*/
|
81
81
|
export interface UserOptions {
|
82
82
|
/** Whether to use a paymaster for gas fee coverage. */
|
83
|
-
|
83
|
+
sponsorshipPolicy?: string;
|
84
84
|
/** The unique nonce used to differentiate multiple Safe setups. */
|
85
85
|
safeSaltNonce: string;
|
86
86
|
/** The NEAR contract ID for the MPC contract. */
|
package/dist/cjs/util.d.ts
CHANGED
@@ -10,4 +10,31 @@ export declare function containsValue(transactions: MetaTransaction[]): boolean;
|
|
10
10
|
export declare function isContract(address: Address, chainId: number): Promise<boolean>;
|
11
11
|
export declare function getClient(chainId: number): PublicClient;
|
12
12
|
export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
|
13
|
+
export declare function saltNonceFromMessage(input: string): string;
|
14
|
+
/**
|
15
|
+
* Fetches the signature for a NEAR transaction hash. If an `accountId` is provided,
|
16
|
+
* it fetches the signature from the appropriate network. Otherwise, it races across
|
17
|
+
* both `testnet` and `mainnet`.
|
18
|
+
*
|
19
|
+
* @param {string} txHash - The NEAR transaction hash for which to fetch the signature.
|
20
|
+
* @param {string} [accountId] - (Optional) The account ID associated with the transaction.
|
21
|
+
* Providing this will reduce dangling promises as the network is determined by the account.
|
22
|
+
*
|
23
|
+
* @returns {Promise<Hex>} A promise that resolves to the hex-encoded signature.
|
24
|
+
*
|
25
|
+
* @throws Will throw an error if no signature is found for the given transaction hash.
|
26
|
+
*/
|
27
|
+
export declare function signatureFromTxHash(txHash: string, accountId?: string): Promise<Hex>;
|
28
|
+
/**
|
29
|
+
* Races an array of promises and resolves with the first promise that fulfills.
|
30
|
+
* If all promises reject, the function will reject with an error.
|
31
|
+
*
|
32
|
+
* @template T
|
33
|
+
* @param {Promise<T>[]} promises - An array of promises to race. Each promise should resolve to type `T`.
|
34
|
+
*
|
35
|
+
* @returns {Promise<T>} A promise that resolves to the value of the first successfully resolved promise.
|
36
|
+
*
|
37
|
+
* @throws Will throw an error if all promises reject with the message "All promises rejected".
|
38
|
+
*/
|
39
|
+
export declare function raceToFirstResolve<T>(promises: Promise<T>[]): Promise<T>;
|
13
40
|
export {};
|
package/dist/cjs/util.js
CHANGED
@@ -7,9 +7,11 @@ exports.containsValue = containsValue;
|
|
7
7
|
exports.isContract = isContract;
|
8
8
|
exports.getClient = getClient;
|
9
9
|
exports.metaTransactionsFromRequest = metaTransactionsFromRequest;
|
10
|
+
exports.saltNonceFromMessage = saltNonceFromMessage;
|
11
|
+
exports.signatureFromTxHash = signatureFromTxHash;
|
12
|
+
exports.raceToFirstResolve = raceToFirstResolve;
|
10
13
|
const near_ca_1 = require("near-ca");
|
11
14
|
const viem_1 = require("viem");
|
12
|
-
//
|
13
15
|
exports.PLACEHOLDER_SIG = (0, viem_1.encodePacked)(["uint48", "uint48"], [0, 0]);
|
14
16
|
const packGas = (hi, lo) => (0, viem_1.encodePacked)(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
|
15
17
|
exports.packGas = packGas;
|
@@ -58,3 +60,73 @@ function metaTransactionsFromRequest(params) {
|
|
58
60
|
data: tx.data || "0x",
|
59
61
|
}));
|
60
62
|
}
|
63
|
+
function saltNonceFromMessage(input) {
|
64
|
+
// Convert the string to bytes (UTF-8 encoding)
|
65
|
+
// Compute the keccak256 hash of the input bytes
|
66
|
+
// Convert the resulting hash (which is in hex) to a BigInt
|
67
|
+
// Return string for readability and transport.
|
68
|
+
return BigInt((0, viem_1.keccak256)((0, viem_1.toBytes)(input))).toString();
|
69
|
+
}
|
70
|
+
/**
|
71
|
+
* Fetches the signature for a NEAR transaction hash. If an `accountId` is provided,
|
72
|
+
* it fetches the signature from the appropriate network. Otherwise, it races across
|
73
|
+
* both `testnet` and `mainnet`.
|
74
|
+
*
|
75
|
+
* @param {string} txHash - The NEAR transaction hash for which to fetch the signature.
|
76
|
+
* @param {string} [accountId] - (Optional) The account ID associated with the transaction.
|
77
|
+
* Providing this will reduce dangling promises as the network is determined by the account.
|
78
|
+
*
|
79
|
+
* @returns {Promise<Hex>} A promise that resolves to the hex-encoded signature.
|
80
|
+
*
|
81
|
+
* @throws Will throw an error if no signature is found for the given transaction hash.
|
82
|
+
*/
|
83
|
+
async function signatureFromTxHash(txHash, accountId) {
|
84
|
+
if (accountId) {
|
85
|
+
const signature = await (0, near_ca_1.signatureFromTxHash)(`https://archival-rpc.${(0, near_ca_1.getNetworkId)(accountId)}.near.org`, txHash, accountId);
|
86
|
+
return packSignature((0, viem_1.serializeSignature)(signature));
|
87
|
+
}
|
88
|
+
try {
|
89
|
+
const signature = await raceToFirstResolve(["testnet", "mainnet"].map((network) => (0, near_ca_1.signatureFromTxHash)(archiveNode(network), txHash)));
|
90
|
+
return packSignature((0, viem_1.serializeSignature)(signature));
|
91
|
+
}
|
92
|
+
catch {
|
93
|
+
throw new Error(`No signature found for txHash ${txHash}`);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
/**
|
97
|
+
* Utility function to construct an archive node URL for a given NEAR network.
|
98
|
+
*
|
99
|
+
* @param {string} networkId - The ID of the NEAR network (e.g., 'testnet', 'mainnet').
|
100
|
+
*
|
101
|
+
* @returns {string} The full URL of the archival RPC node for the specified network.
|
102
|
+
*/
|
103
|
+
const archiveNode = (networkId) => `https://archival-rpc.${networkId}.near.org`;
|
104
|
+
/**
|
105
|
+
* Races an array of promises and resolves with the first promise that fulfills.
|
106
|
+
* If all promises reject, the function will reject with an error.
|
107
|
+
*
|
108
|
+
* @template T
|
109
|
+
* @param {Promise<T>[]} promises - An array of promises to race. Each promise should resolve to type `T`.
|
110
|
+
*
|
111
|
+
* @returns {Promise<T>} A promise that resolves to the value of the first successfully resolved promise.
|
112
|
+
*
|
113
|
+
* @throws Will throw an error if all promises reject with the message "All promises rejected".
|
114
|
+
*/
|
115
|
+
async function raceToFirstResolve(promises) {
|
116
|
+
return new Promise((resolve, reject) => {
|
117
|
+
let rejectionCount = 0;
|
118
|
+
const totalPromises = promises.length;
|
119
|
+
promises.forEach((promise) => {
|
120
|
+
// Wrap each promise so it only resolves when fulfilled
|
121
|
+
Promise.resolve(promise)
|
122
|
+
.then(resolve) // Resolve when any promise resolves
|
123
|
+
.catch(() => {
|
124
|
+
rejectionCount++;
|
125
|
+
// If all promises reject, reject the race with an error
|
126
|
+
if (rejectionCount === totalPromises) {
|
127
|
+
reject(new Error("All promises rejected"));
|
128
|
+
}
|
129
|
+
});
|
130
|
+
});
|
131
|
+
});
|
132
|
+
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { toHex } from "viem";
|
2
|
+
const DOMAIN_SEPARATOR = "bitte/near-safe";
|
3
|
+
// 0x62697474652f6e6561722d7361666500
|
4
|
+
export const USER_OP_IDENTIFIER = toHex(DOMAIN_SEPARATOR, { size: 16 });
|
5
|
+
// 130811896738364114529934864114944206080
|
6
|
+
export const DEFAULT_SAFE_SALT_NONCE = BigInt(USER_OP_IDENTIFIER).toString();
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
import { Address, Hash, PublicClient, Transport } from "viem";
|
2
2
|
import { GasPrices, PaymasterData, UnsignedUserOperation, UserOperation, UserOperationReceipt } from "../types";
|
3
|
+
type SponsorshipPolicy = {
|
4
|
+
sponsorshipPolicyId: string;
|
5
|
+
};
|
3
6
|
type BundlerRpcSchema = [
|
4
7
|
{
|
5
8
|
Method: "pm_sponsorUserOperation";
|
6
|
-
Parameters: [UnsignedUserOperation, Address];
|
9
|
+
Parameters: [UnsignedUserOperation, Address, SponsorshipPolicy];
|
7
10
|
ReturnType: PaymasterData;
|
8
11
|
},
|
9
12
|
{
|
@@ -28,7 +31,7 @@ export declare class Erc4337Bundler {
|
|
28
31
|
apiKey: string;
|
29
32
|
chainId: number;
|
30
33
|
constructor(entryPointAddress: Address, apiKey: string, chainId: number);
|
31
|
-
getPaymasterData(rawUserOp: UnsignedUserOperation,
|
34
|
+
getPaymasterData(rawUserOp: UnsignedUserOperation, safeNotDeployed: boolean, sponsorshipPolicy?: string): Promise<PaymasterData>;
|
32
35
|
sendUserOperation(userOp: UserOperation): Promise<Hash>;
|
33
36
|
getGasPrice(): Promise<GasPrices>;
|
34
37
|
getUserOpReceipt(userOpHash: Hash): Promise<UserOperationReceipt>;
|
package/dist/esm/lib/bundler.js
CHANGED
@@ -17,15 +17,16 @@ export class Erc4337Bundler {
|
|
17
17
|
rpcSchema: rpcSchema(),
|
18
18
|
});
|
19
19
|
}
|
20
|
-
async getPaymasterData(rawUserOp,
|
20
|
+
async getPaymasterData(rawUserOp, safeNotDeployed, sponsorshipPolicy) {
|
21
21
|
// TODO: Keep this option out of the bundler
|
22
|
-
if (
|
22
|
+
if (sponsorshipPolicy) {
|
23
23
|
console.log("Requesting paymaster data...");
|
24
24
|
return handleRequest(() => this.client.request({
|
25
25
|
method: "pm_sponsorUserOperation",
|
26
26
|
params: [
|
27
27
|
{ ...rawUserOp, signature: PLACEHOLDER_SIG },
|
28
28
|
this.entryPointAddress,
|
29
|
+
{ sponsorshipPolicyId: sponsorshipPolicy },
|
29
30
|
],
|
30
31
|
}));
|
31
32
|
}
|
package/dist/esm/lib/safe.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
import { encodeFunctionData, encodePacked, getCreate2Address, keccak256, toHex, zeroAddress, } from "viem";
|
1
|
+
import { concat, encodeFunctionData, encodePacked, getCreate2Address, keccak256, toHex, zeroAddress, } from "viem";
|
2
2
|
import { SAFE_DEPLOYMENTS } from "../_gen/deployments";
|
3
|
+
import { USER_OP_IDENTIFIER } from "../constants";
|
3
4
|
import { PLACEHOLDER_SIG, getClient, packGas, packPaymasterData, } from "../util";
|
4
5
|
/**
|
5
6
|
* All contracts used in account creation & execution
|
@@ -108,16 +109,20 @@ export class SafeContractSuite {
|
|
108
109
|
nonce: toHex(nonce),
|
109
110
|
...this.factoryDataForSetup(safeNotDeployed, setup, safeSaltNonce),
|
110
111
|
// <https://github.com/safe-global/safe-modules/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/4337/contracts/Safe4337Module.sol#L172>
|
111
|
-
callData:
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
112
|
+
callData: concat([
|
113
|
+
encodeFunctionData({
|
114
|
+
abi: this.m4337.abi,
|
115
|
+
functionName: "executeUserOp",
|
116
|
+
args: [
|
117
|
+
txData.to,
|
118
|
+
BigInt(txData.value),
|
119
|
+
txData.data,
|
120
|
+
txData.operation || 0,
|
121
|
+
],
|
122
|
+
}),
|
123
|
+
// Append On-Chain Identifier:
|
124
|
+
USER_OP_IDENTIFIER,
|
125
|
+
]),
|
121
126
|
...feeData,
|
122
127
|
};
|
123
128
|
}
|
package/dist/esm/near-safe.d.ts
CHANGED
@@ -7,9 +7,9 @@ import { DecodedMultisend, EncodedTxData, EvmTransactionData, MetaTransaction, U
|
|
7
7
|
export interface NearSafeConfig {
|
8
8
|
accountId: string;
|
9
9
|
mpcContractId: string;
|
10
|
-
pimlicoKey: string;
|
11
10
|
nearConfig?: NearConfig;
|
12
11
|
privateKey?: string;
|
12
|
+
pimlicoKey: string;
|
13
13
|
safeSaltNonce?: string;
|
14
14
|
}
|
15
15
|
export declare class NearSafe {
|
@@ -70,7 +70,7 @@ export declare class NearSafe {
|
|
70
70
|
buildTransaction(args: {
|
71
71
|
chainId: number;
|
72
72
|
transactions: MetaTransaction[];
|
73
|
-
|
73
|
+
sponsorshipPolicy?: string;
|
74
74
|
}): Promise<UserOperation>;
|
75
75
|
/**
|
76
76
|
* Signs a transaction with the NEAR adapter using the provided operation hash.
|
@@ -93,7 +93,7 @@ export declare class NearSafe {
|
|
93
93
|
* @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
|
94
94
|
* @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
|
95
95
|
*/
|
96
|
-
encodeSignRequest(signRequest: SignRequestData,
|
96
|
+
encodeSignRequest(signRequest: SignRequestData, sponsorshipPolicy?: string): Promise<EncodedTxData>;
|
97
97
|
/**
|
98
98
|
* Broadcasts a user operation to the EVM network with a provided signature.
|
99
99
|
* Warning: Uses a private ethRPC with sensitive Pimlico API key (should be run server side).
|
@@ -173,7 +173,7 @@ export declare class NearSafe {
|
|
173
173
|
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
174
174
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
175
175
|
*/
|
176
|
-
requestRouter({ method, chainId, params }: SignRequestData,
|
176
|
+
requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<{
|
177
177
|
evmMessage: string;
|
178
178
|
payload: number[];
|
179
179
|
hash: Hash;
|
package/dist/esm/near-safe.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { decodeMulti } from "ethers-multisend";
|
2
2
|
import { setupAdapter, signatureFromOutcome, toPayload, } from "near-ca";
|
3
3
|
import { decodeFunctionData, formatEther, serializeSignature, } from "viem";
|
4
|
+
import { DEFAULT_SAFE_SALT_NONCE } from "./constants";
|
4
5
|
import { Erc4337Bundler } from "./lib/bundler";
|
5
6
|
import { encodeMulti, isMultisendTx } from "./lib/multisend";
|
6
7
|
import { SafeContractSuite } from "./lib/safe";
|
@@ -21,6 +22,7 @@ export class NearSafe {
|
|
21
22
|
*/
|
22
23
|
static async create(config) {
|
23
24
|
const { pimlicoKey, safeSaltNonce } = config;
|
25
|
+
// const nearAdapter = await mockAdapter();
|
24
26
|
const nearAdapter = await setupAdapter({ ...config });
|
25
27
|
const safePack = new SafeContractSuite();
|
26
28
|
const setup = safePack.getSetup([nearAdapter.address]);
|
@@ -31,7 +33,7 @@ export class NearSafe {
|
|
31
33
|
MPC EOA: ${nearAdapter.address}
|
32
34
|
Safe: ${safeAddress}
|
33
35
|
`);
|
34
|
-
return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce ||
|
36
|
+
return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce || DEFAULT_SAFE_SALT_NONCE);
|
35
37
|
}
|
36
38
|
/**
|
37
39
|
* Constructs a new `NearSafe` object with the provided parameters.
|
@@ -65,7 +67,7 @@ export class NearSafe {
|
|
65
67
|
* @returns {string} - The contract ID of the MPC contract.
|
66
68
|
*/
|
67
69
|
get mpcContractId() {
|
68
|
-
return this.nearAdapter.mpcContract.
|
70
|
+
return this.nearAdapter.mpcContract.accountId();
|
69
71
|
}
|
70
72
|
/**
|
71
73
|
* Retrieves the balance of the Safe account on the specified EVM chain.
|
@@ -88,10 +90,11 @@ export class NearSafe {
|
|
88
90
|
* @throws {Error} - Throws an error if the transaction set is empty or if any operation fails during the building process.
|
89
91
|
*/
|
90
92
|
async buildTransaction(args) {
|
91
|
-
const { transactions,
|
93
|
+
const { transactions, sponsorshipPolicy, chainId } = args;
|
92
94
|
if (transactions.length === 0) {
|
93
95
|
throw new Error("Empty transaction set!");
|
94
96
|
}
|
97
|
+
console.log(`Building UserOp on chainId ${chainId} with ${transactions.length} transaction(s)`);
|
95
98
|
const bundler = this.bundlerForChainId(chainId);
|
96
99
|
const [gasFees, nonce, safeDeployed] = await Promise.all([
|
97
100
|
bundler.getGasPrice(),
|
@@ -101,7 +104,7 @@ export class NearSafe {
|
|
101
104
|
// Build Singular MetaTransaction for Multisend from transaction list.
|
102
105
|
const tx = transactions.length > 1 ? encodeMulti(transactions) : transactions[0];
|
103
106
|
const rawUserOp = await this.safePack.buildUserOp(nonce, tx, this.address, gasFees.fast, this.setup, !safeDeployed, this.safeSaltNonce);
|
104
|
-
const paymasterData = await bundler.getPaymasterData(rawUserOp,
|
107
|
+
const paymasterData = await bundler.getPaymasterData(rawUserOp, !safeDeployed, sponsorshipPolicy);
|
105
108
|
const unsignedUserOp = { ...rawUserOp, ...paymasterData };
|
106
109
|
return unsignedUserOp;
|
107
110
|
}
|
@@ -131,8 +134,8 @@ export class NearSafe {
|
|
131
134
|
* @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
|
132
135
|
* @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
|
133
136
|
*/
|
134
|
-
async encodeSignRequest(signRequest,
|
135
|
-
const { payload, evmMessage, hash } = await this.requestRouter(signRequest,
|
137
|
+
async encodeSignRequest(signRequest, sponsorshipPolicy) {
|
138
|
+
const { payload, evmMessage, hash } = await this.requestRouter(signRequest, sponsorshipPolicy);
|
136
139
|
return {
|
137
140
|
nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
|
138
141
|
path: this.nearAdapter.derivationPath,
|
@@ -283,7 +286,7 @@ export class NearSafe {
|
|
283
286
|
* - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
|
284
287
|
* the payload (hashed data), and recovery data needed for reconstructing the signature request.
|
285
288
|
*/
|
286
|
-
async requestRouter({ method, chainId, params },
|
289
|
+
async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
|
287
290
|
const safeInfo = {
|
288
291
|
address: { value: this.address },
|
289
292
|
chainId: chainId.toString(),
|
@@ -318,7 +321,7 @@ export class NearSafe {
|
|
318
321
|
const userOp = await this.buildTransaction({
|
319
322
|
chainId,
|
320
323
|
transactions,
|
321
|
-
|
324
|
+
...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
|
322
325
|
});
|
323
326
|
const opHash = await this.opHash(chainId, userOp);
|
324
327
|
return {
|
package/dist/esm/types.d.ts
CHANGED
@@ -80,7 +80,7 @@ export interface PaymasterData {
|
|
80
80
|
*/
|
81
81
|
export interface UserOptions {
|
82
82
|
/** Whether to use a paymaster for gas fee coverage. */
|
83
|
-
|
83
|
+
sponsorshipPolicy?: string;
|
84
84
|
/** The unique nonce used to differentiate multiple Safe setups. */
|
85
85
|
safeSaltNonce: string;
|
86
86
|
/** The NEAR contract ID for the MPC contract. */
|
package/dist/esm/util.d.ts
CHANGED
@@ -10,4 +10,31 @@ export declare function containsValue(transactions: MetaTransaction[]): boolean;
|
|
10
10
|
export declare function isContract(address: Address, chainId: number): Promise<boolean>;
|
11
11
|
export declare function getClient(chainId: number): PublicClient;
|
12
12
|
export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
|
13
|
+
export declare function saltNonceFromMessage(input: string): string;
|
14
|
+
/**
|
15
|
+
* Fetches the signature for a NEAR transaction hash. If an `accountId` is provided,
|
16
|
+
* it fetches the signature from the appropriate network. Otherwise, it races across
|
17
|
+
* both `testnet` and `mainnet`.
|
18
|
+
*
|
19
|
+
* @param {string} txHash - The NEAR transaction hash for which to fetch the signature.
|
20
|
+
* @param {string} [accountId] - (Optional) The account ID associated with the transaction.
|
21
|
+
* Providing this will reduce dangling promises as the network is determined by the account.
|
22
|
+
*
|
23
|
+
* @returns {Promise<Hex>} A promise that resolves to the hex-encoded signature.
|
24
|
+
*
|
25
|
+
* @throws Will throw an error if no signature is found for the given transaction hash.
|
26
|
+
*/
|
27
|
+
export declare function signatureFromTxHash(txHash: string, accountId?: string): Promise<Hex>;
|
28
|
+
/**
|
29
|
+
* Races an array of promises and resolves with the first promise that fulfills.
|
30
|
+
* If all promises reject, the function will reject with an error.
|
31
|
+
*
|
32
|
+
* @template T
|
33
|
+
* @param {Promise<T>[]} promises - An array of promises to race. Each promise should resolve to type `T`.
|
34
|
+
*
|
35
|
+
* @returns {Promise<T>} A promise that resolves to the value of the first successfully resolved promise.
|
36
|
+
*
|
37
|
+
* @throws Will throw an error if all promises reject with the message "All promises rejected".
|
38
|
+
*/
|
39
|
+
export declare function raceToFirstResolve<T>(promises: Promise<T>[]): Promise<T>;
|
13
40
|
export {};
|
package/dist/esm/util.js
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
import { Network } from "near-ca";
|
2
|
-
import { concatHex, encodePacked, toHex, isHex, parseTransaction, zeroAddress, } from "viem";
|
3
|
-
//
|
1
|
+
import { getNetworkId, Network, signatureFromTxHash as sigFromHash, } from "near-ca";
|
2
|
+
import { concatHex, encodePacked, toHex, isHex, parseTransaction, zeroAddress, toBytes, keccak256, serializeSignature, } from "viem";
|
4
3
|
export const PLACEHOLDER_SIG = encodePacked(["uint48", "uint48"], [0, 0]);
|
5
4
|
export const packGas = (hi, lo) => encodePacked(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
|
6
5
|
export function packSignature(signature, validFrom = 0, validTo = 0) {
|
@@ -48,3 +47,73 @@ export function metaTransactionsFromRequest(params) {
|
|
48
47
|
data: tx.data || "0x",
|
49
48
|
}));
|
50
49
|
}
|
50
|
+
export function saltNonceFromMessage(input) {
|
51
|
+
// Convert the string to bytes (UTF-8 encoding)
|
52
|
+
// Compute the keccak256 hash of the input bytes
|
53
|
+
// Convert the resulting hash (which is in hex) to a BigInt
|
54
|
+
// Return string for readability and transport.
|
55
|
+
return BigInt(keccak256(toBytes(input))).toString();
|
56
|
+
}
|
57
|
+
/**
|
58
|
+
* Fetches the signature for a NEAR transaction hash. If an `accountId` is provided,
|
59
|
+
* it fetches the signature from the appropriate network. Otherwise, it races across
|
60
|
+
* both `testnet` and `mainnet`.
|
61
|
+
*
|
62
|
+
* @param {string} txHash - The NEAR transaction hash for which to fetch the signature.
|
63
|
+
* @param {string} [accountId] - (Optional) The account ID associated with the transaction.
|
64
|
+
* Providing this will reduce dangling promises as the network is determined by the account.
|
65
|
+
*
|
66
|
+
* @returns {Promise<Hex>} A promise that resolves to the hex-encoded signature.
|
67
|
+
*
|
68
|
+
* @throws Will throw an error if no signature is found for the given transaction hash.
|
69
|
+
*/
|
70
|
+
export async function signatureFromTxHash(txHash, accountId) {
|
71
|
+
if (accountId) {
|
72
|
+
const signature = await sigFromHash(`https://archival-rpc.${getNetworkId(accountId)}.near.org`, txHash, accountId);
|
73
|
+
return packSignature(serializeSignature(signature));
|
74
|
+
}
|
75
|
+
try {
|
76
|
+
const signature = await raceToFirstResolve(["testnet", "mainnet"].map((network) => sigFromHash(archiveNode(network), txHash)));
|
77
|
+
return packSignature(serializeSignature(signature));
|
78
|
+
}
|
79
|
+
catch {
|
80
|
+
throw new Error(`No signature found for txHash ${txHash}`);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
/**
|
84
|
+
* Utility function to construct an archive node URL for a given NEAR network.
|
85
|
+
*
|
86
|
+
* @param {string} networkId - The ID of the NEAR network (e.g., 'testnet', 'mainnet').
|
87
|
+
*
|
88
|
+
* @returns {string} The full URL of the archival RPC node for the specified network.
|
89
|
+
*/
|
90
|
+
const archiveNode = (networkId) => `https://archival-rpc.${networkId}.near.org`;
|
91
|
+
/**
|
92
|
+
* Races an array of promises and resolves with the first promise that fulfills.
|
93
|
+
* If all promises reject, the function will reject with an error.
|
94
|
+
*
|
95
|
+
* @template T
|
96
|
+
* @param {Promise<T>[]} promises - An array of promises to race. Each promise should resolve to type `T`.
|
97
|
+
*
|
98
|
+
* @returns {Promise<T>} A promise that resolves to the value of the first successfully resolved promise.
|
99
|
+
*
|
100
|
+
* @throws Will throw an error if all promises reject with the message "All promises rejected".
|
101
|
+
*/
|
102
|
+
export async function raceToFirstResolve(promises) {
|
103
|
+
return new Promise((resolve, reject) => {
|
104
|
+
let rejectionCount = 0;
|
105
|
+
const totalPromises = promises.length;
|
106
|
+
promises.forEach((promise) => {
|
107
|
+
// Wrap each promise so it only resolves when fulfilled
|
108
|
+
Promise.resolve(promise)
|
109
|
+
.then(resolve) // Resolve when any promise resolves
|
110
|
+
.catch(() => {
|
111
|
+
rejectionCount++;
|
112
|
+
// If all promises reject, reject the race with an error
|
113
|
+
if (rejectionCount === totalPromises) {
|
114
|
+
reject(new Error("All promises rejected"));
|
115
|
+
}
|
116
|
+
});
|
117
|
+
});
|
118
|
+
});
|
119
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "near-safe",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.7.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",
|
@@ -36,15 +36,15 @@
|
|
36
36
|
"start": "yarn example",
|
37
37
|
"example": "tsx examples/send-tx.ts",
|
38
38
|
"lint": "eslint . --ignore-pattern dist/",
|
39
|
-
"test": "jest",
|
40
39
|
"fmt": "prettier --write '{src,examples,tests}/**/*.{js,jsx,ts,tsx}' && yarn lint --fix",
|
40
|
+
"test": "jest",
|
41
41
|
"all": "yarn fmt && yarn lint && yarn build"
|
42
42
|
},
|
43
43
|
"dependencies": {
|
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.5.
|
47
|
+
"near-ca": "^0.5.10",
|
48
48
|
"semver": "^7.6.3",
|
49
49
|
"viem": "^2.16.5"
|
50
50
|
},
|