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.
@@ -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
  },