near-safe 0.6.1 → 0.7.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 +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
|
},
|