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.
@@ -0,0 +1,2 @@
1
+ export declare const USER_OP_IDENTIFIER: `0x${string}`;
2
+ export declare const DEFAULT_SAFE_SALT_NONCE: string;
@@ -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();
@@ -1,4 +1,5 @@
1
1
  export * from "./near-safe";
2
2
  export * from "./types";
3
3
  export * from "./util";
4
+ export * from "./constants";
4
5
  export { Network, BaseTx, SignRequestData, populateTx } from "near-ca";
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, usePaymaster: boolean, safeNotDeployed: boolean): Promise<PaymasterData>;
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>;
@@ -17,15 +17,16 @@ class Erc4337Bundler {
17
17
  rpcSchema: (0, viem_1.rpcSchema)(),
18
18
  });
19
19
  }
20
- async getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed) {
20
+ async getPaymasterData(rawUserOp, safeNotDeployed, sponsorshipPolicy) {
21
21
  // TODO: Keep this option out of the bundler
22
- if (usePaymaster) {
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
  }
@@ -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.encodeFunctionData)({
108
- abi: this.m4337.abi,
109
- functionName: "executeUserOp",
110
- args: [
111
- txData.to,
112
- BigInt(txData.value),
113
- txData.data,
114
- txData.operation || 0,
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
  }
@@ -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
- usePaymaster: boolean;
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, usePaymaster: boolean): Promise<EncodedTxData>;
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, usePaymaster: boolean): Promise<{
176
+ requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<{
177
177
  evmMessage: string;
178
178
  payload: number[];
179
179
  hash: Hash;
@@ -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 || "0");
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.contract.contractId;
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, usePaymaster, chainId } = args;
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, usePaymaster, !safeDeployed);
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, usePaymaster) {
132
- const { payload, evmMessage, hash } = await this.requestRouter(signRequest, usePaymaster);
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 }, usePaymaster) {
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
- usePaymaster,
321
+ ...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
319
322
  });
320
323
  const opHash = await this.opHash(chainId, userOp);
321
324
  return {
@@ -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
- usePaymaster: boolean;
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. */
@@ -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,2 @@
1
+ export declare const USER_OP_IDENTIFIER: `0x${string}`;
2
+ export declare const DEFAULT_SAFE_SALT_NONCE: string;
@@ -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();
@@ -1,4 +1,5 @@
1
1
  export * from "./near-safe";
2
2
  export * from "./types";
3
3
  export * from "./util";
4
+ export * from "./constants";
4
5
  export { Network, BaseTx, SignRequestData, populateTx } from "near-ca";
package/dist/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./near-safe";
2
2
  export * from "./types";
3
3
  export * from "./util";
4
+ export * from "./constants";
4
5
  export { Network, populateTx } from "near-ca";
@@ -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, usePaymaster: boolean, safeNotDeployed: boolean): Promise<PaymasterData>;
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>;
@@ -17,15 +17,16 @@ export class Erc4337Bundler {
17
17
  rpcSchema: rpcSchema(),
18
18
  });
19
19
  }
20
- async getPaymasterData(rawUserOp, usePaymaster, safeNotDeployed) {
20
+ async getPaymasterData(rawUserOp, safeNotDeployed, sponsorshipPolicy) {
21
21
  // TODO: Keep this option out of the bundler
22
- if (usePaymaster) {
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
  }
@@ -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: encodeFunctionData({
112
- abi: this.m4337.abi,
113
- functionName: "executeUserOp",
114
- args: [
115
- txData.to,
116
- BigInt(txData.value),
117
- txData.data,
118
- txData.operation || 0,
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
  }
@@ -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
- usePaymaster: boolean;
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, usePaymaster: boolean): Promise<EncodedTxData>;
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, usePaymaster: boolean): Promise<{
176
+ requestRouter({ method, chainId, params }: SignRequestData, sponsorshipPolicy?: string): Promise<{
177
177
  evmMessage: string;
178
178
  payload: number[];
179
179
  hash: Hash;
@@ -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 || "0");
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.contract.contractId;
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, usePaymaster, chainId } = args;
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, usePaymaster, !safeDeployed);
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, usePaymaster) {
135
- const { payload, evmMessage, hash } = await this.requestRouter(signRequest, usePaymaster);
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 }, usePaymaster) {
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
- usePaymaster,
324
+ ...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
322
325
  });
323
326
  const opHash = await this.opHash(chainId, userOp);
324
327
  return {
@@ -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
- usePaymaster: boolean;
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. */
@@ -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.6.1",
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.7",
47
+ "near-ca": "^0.5.10",
48
48
  "semver": "^7.6.3",
49
49
  "viem": "^2.16.5"
50
50
  },