near-safe 0.6.2 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }
@@ -11,7 +11,7 @@ export declare class SafeContractSuite {
11
11
  moduleSetup: Deployment;
12
12
  entryPoint: Deployment;
13
13
  constructor();
14
- addressForSetup(setup: Hex, saltNonce?: string): Promise<Address>;
14
+ addressForSetup(setup: Hex, saltNonce: string): Promise<Address>;
15
15
  getSetup(owners: string[]): Hex;
16
16
  addOwnerData(newOwner: Address): Hex;
17
17
  getOpHash(chainId: number, unsignedUserOp: UserOperation): Promise<Hash>;
@@ -21,7 +21,7 @@ class SafeContractSuite {
21
21
  async addressForSetup(setup, saltNonce) {
22
22
  // bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
23
23
  // cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
24
- const salt = (0, viem_1.keccak256)((0, viem_1.encodePacked)(["bytes32", "uint256"], [(0, viem_1.keccak256)(setup), BigInt(saltNonce || "0")]));
24
+ const salt = (0, viem_1.keccak256)((0, viem_1.encodePacked)(["bytes32", "uint256"], [(0, viem_1.keccak256)(setup), BigInt(saltNonce)]));
25
25
  // abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
26
26
  // cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L29
27
27
  const initCode = (0, viem_1.encodePacked)(["bytes", "uint256"], [
@@ -43,18 +43,18 @@ class SafeContractSuite {
43
43
  abi: this.singleton.abi,
44
44
  functionName: "setup",
45
45
  args: [
46
- owners,
47
- 1, // We use sign threshold of 1.
48
- this.moduleSetup.address,
46
+ owners, // _owners
47
+ 1, // _threshold
48
+ this.moduleSetup.address, // to
49
49
  (0, viem_1.encodeFunctionData)({
50
50
  abi: this.moduleSetup.abi,
51
51
  functionName: "enableModules",
52
52
  args: [[this.m4337.address]],
53
- }),
54
- this.m4337.address,
55
- viem_1.zeroAddress,
56
- 0,
57
- viem_1.zeroAddress,
53
+ }), // data
54
+ this.m4337.address, // fallbackHandler
55
+ viem_1.zeroAddress, // paymentToken
56
+ 0, // payment
57
+ viem_1.zeroAddress, // paymentReceiver
58
58
  ],
59
59
  });
60
60
  }
@@ -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;
@@ -18,7 +18,8 @@ class NearSafe {
18
18
  * @returns {Promise<NearSafe>} - A promise that resolves to a new `NearSafe` instance.
19
19
  */
20
20
  static async create(config) {
21
- const { pimlicoKey, safeSaltNonce } = config;
21
+ const { pimlicoKey } = config;
22
+ const safeSaltNonce = config.safeSaltNonce || constants_1.DEFAULT_SAFE_SALT_NONCE;
22
23
  // const nearAdapter = await mockAdapter();
23
24
  const nearAdapter = await (0, near_ca_1.setupAdapter)({ ...config });
24
25
  const safePack = new safe_1.SafeContractSuite();
@@ -30,7 +31,7 @@ class NearSafe {
30
31
  MPC EOA: ${nearAdapter.address}
31
32
  Safe: ${safeAddress}
32
33
  `);
33
- return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce || constants_1.DEFAULT_SAFE_SALT_NONCE);
34
+ return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce);
34
35
  }
35
36
  /**
36
37
  * Constructs a new `NearSafe` object with the provided parameters.
@@ -87,7 +88,7 @@ class NearSafe {
87
88
  * @throws {Error} - Throws an error if the transaction set is empty or if any operation fails during the building process.
88
89
  */
89
90
  async buildTransaction(args) {
90
- const { transactions, usePaymaster, chainId } = args;
91
+ const { transactions, sponsorshipPolicy, chainId } = args;
91
92
  if (transactions.length === 0) {
92
93
  throw new Error("Empty transaction set!");
93
94
  }
@@ -101,7 +102,7 @@ class NearSafe {
101
102
  // Build Singular MetaTransaction for Multisend from transaction list.
102
103
  const tx = transactions.length > 1 ? (0, multisend_1.encodeMulti)(transactions) : transactions[0];
103
104
  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);
105
+ const paymasterData = await bundler.getPaymasterData(rawUserOp, !safeDeployed, sponsorshipPolicy);
105
106
  const unsignedUserOp = { ...rawUserOp, ...paymasterData };
106
107
  return unsignedUserOp;
107
108
  }
@@ -131,8 +132,8 @@ class NearSafe {
131
132
  * @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
132
133
  * @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
133
134
  */
134
- async encodeSignRequest(signRequest, usePaymaster) {
135
- const { payload, evmMessage, hash } = await this.requestRouter(signRequest, usePaymaster);
135
+ async encodeSignRequest(signRequest, sponsorshipPolicy) {
136
+ const { payload, evmMessage, hash } = await this.requestRouter(signRequest, sponsorshipPolicy);
136
137
  return {
137
138
  nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
138
139
  path: this.nearAdapter.derivationPath,
@@ -283,7 +284,7 @@ class NearSafe {
283
284
  * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
284
285
  * the payload (hashed data), and recovery data needed for reconstructing the signature request.
285
286
  */
286
- async requestRouter({ method, chainId, params }, usePaymaster) {
287
+ async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
287
288
  const safeInfo = {
288
289
  address: { value: this.address },
289
290
  chainId: chainId.toString(),
@@ -318,7 +319,7 @@ class NearSafe {
318
319
  const userOp = await this.buildTransaction({
319
320
  chainId,
320
321
  transactions,
321
- usePaymaster,
322
+ ...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
322
323
  });
323
324
  const opHash = await this.opHash(chainId, userOp);
324
325
  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. */
@@ -11,4 +11,30 @@ export declare function isContract(address: Address, chainId: number): Promise<b
11
11
  export declare function getClient(chainId: number): PublicClient;
12
12
  export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
13
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>;
14
40
  export {};
package/dist/cjs/util.js CHANGED
@@ -8,9 +8,10 @@ exports.isContract = isContract;
8
8
  exports.getClient = getClient;
9
9
  exports.metaTransactionsFromRequest = metaTransactionsFromRequest;
10
10
  exports.saltNonceFromMessage = saltNonceFromMessage;
11
+ exports.signatureFromTxHash = signatureFromTxHash;
12
+ exports.raceToFirstResolve = raceToFirstResolve;
11
13
  const near_ca_1 = require("near-ca");
12
14
  const viem_1 = require("viem");
13
- //
14
15
  exports.PLACEHOLDER_SIG = (0, viem_1.encodePacked)(["uint48", "uint48"], [0, 0]);
15
16
  const packGas = (hi, lo) => (0, viem_1.encodePacked)(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
16
17
  exports.packGas = packGas;
@@ -66,3 +67,66 @@ function saltNonceFromMessage(input) {
66
67
  // Return string for readability and transport.
67
68
  return BigInt((0, viem_1.keccak256)((0, viem_1.toBytes)(input))).toString();
68
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
+ }
@@ -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
  }
@@ -11,7 +11,7 @@ export declare class SafeContractSuite {
11
11
  moduleSetup: Deployment;
12
12
  entryPoint: Deployment;
13
13
  constructor();
14
- addressForSetup(setup: Hex, saltNonce?: string): Promise<Address>;
14
+ addressForSetup(setup: Hex, saltNonce: string): Promise<Address>;
15
15
  getSetup(owners: string[]): Hex;
16
16
  addOwnerData(newOwner: Address): Hex;
17
17
  getOpHash(chainId: number, unsignedUserOp: UserOperation): Promise<Hash>;
@@ -25,7 +25,7 @@ export class SafeContractSuite {
25
25
  async addressForSetup(setup, saltNonce) {
26
26
  // bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
27
27
  // cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L58
28
- const salt = keccak256(encodePacked(["bytes32", "uint256"], [keccak256(setup), BigInt(saltNonce || "0")]));
28
+ const salt = keccak256(encodePacked(["bytes32", "uint256"], [keccak256(setup), BigInt(saltNonce)]));
29
29
  // abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
30
30
  // cf: https://github.com/safe-global/safe-smart-account/blob/499b17ad0191b575fcadc5cb5b8e3faeae5391ae/contracts/proxies/SafeProxyFactory.sol#L29
31
31
  const initCode = encodePacked(["bytes", "uint256"], [
@@ -47,18 +47,18 @@ export class SafeContractSuite {
47
47
  abi: this.singleton.abi,
48
48
  functionName: "setup",
49
49
  args: [
50
- owners,
51
- 1, // We use sign threshold of 1.
52
- this.moduleSetup.address,
50
+ owners, // _owners
51
+ 1, // _threshold
52
+ this.moduleSetup.address, // to
53
53
  encodeFunctionData({
54
54
  abi: this.moduleSetup.abi,
55
55
  functionName: "enableModules",
56
56
  args: [[this.m4337.address]],
57
- }),
58
- this.m4337.address,
59
- zeroAddress,
60
- 0,
61
- zeroAddress,
57
+ }), // data
58
+ this.m4337.address, // fallbackHandler
59
+ zeroAddress, // paymentToken
60
+ 0, // payment
61
+ zeroAddress, // paymentReceiver
62
62
  ],
63
63
  });
64
64
  }
@@ -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;
@@ -21,7 +21,8 @@ export class NearSafe {
21
21
  * @returns {Promise<NearSafe>} - A promise that resolves to a new `NearSafe` instance.
22
22
  */
23
23
  static async create(config) {
24
- const { pimlicoKey, safeSaltNonce } = config;
24
+ const { pimlicoKey } = config;
25
+ const safeSaltNonce = config.safeSaltNonce || DEFAULT_SAFE_SALT_NONCE;
25
26
  // const nearAdapter = await mockAdapter();
26
27
  const nearAdapter = await setupAdapter({ ...config });
27
28
  const safePack = new SafeContractSuite();
@@ -33,7 +34,7 @@ export class NearSafe {
33
34
  MPC EOA: ${nearAdapter.address}
34
35
  Safe: ${safeAddress}
35
36
  `);
36
- return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce || DEFAULT_SAFE_SALT_NONCE);
37
+ return new NearSafe(nearAdapter, safePack, pimlicoKey, setup, safeAddress, safeSaltNonce);
37
38
  }
38
39
  /**
39
40
  * Constructs a new `NearSafe` object with the provided parameters.
@@ -90,7 +91,7 @@ export class NearSafe {
90
91
  * @throws {Error} - Throws an error if the transaction set is empty or if any operation fails during the building process.
91
92
  */
92
93
  async buildTransaction(args) {
93
- const { transactions, usePaymaster, chainId } = args;
94
+ const { transactions, sponsorshipPolicy, chainId } = args;
94
95
  if (transactions.length === 0) {
95
96
  throw new Error("Empty transaction set!");
96
97
  }
@@ -104,7 +105,7 @@ export class NearSafe {
104
105
  // Build Singular MetaTransaction for Multisend from transaction list.
105
106
  const tx = transactions.length > 1 ? encodeMulti(transactions) : transactions[0];
106
107
  const rawUserOp = await this.safePack.buildUserOp(nonce, tx, this.address, gasFees.fast, this.setup, !safeDeployed, this.safeSaltNonce);
107
- const paymasterData = await bundler.getPaymasterData(rawUserOp, usePaymaster, !safeDeployed);
108
+ const paymasterData = await bundler.getPaymasterData(rawUserOp, !safeDeployed, sponsorshipPolicy);
108
109
  const unsignedUserOp = { ...rawUserOp, ...paymasterData };
109
110
  return unsignedUserOp;
110
111
  }
@@ -134,8 +135,8 @@ export class NearSafe {
134
135
  * @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
135
136
  * @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
136
137
  */
137
- async encodeSignRequest(signRequest, usePaymaster) {
138
- const { payload, evmMessage, hash } = await this.requestRouter(signRequest, usePaymaster);
138
+ async encodeSignRequest(signRequest, sponsorshipPolicy) {
139
+ const { payload, evmMessage, hash } = await this.requestRouter(signRequest, sponsorshipPolicy);
139
140
  return {
140
141
  nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
141
142
  path: this.nearAdapter.derivationPath,
@@ -286,7 +287,7 @@ export class NearSafe {
286
287
  * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
287
288
  * the payload (hashed data), and recovery data needed for reconstructing the signature request.
288
289
  */
289
- async requestRouter({ method, chainId, params }, usePaymaster) {
290
+ async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
290
291
  const safeInfo = {
291
292
  address: { value: this.address },
292
293
  chainId: chainId.toString(),
@@ -321,7 +322,7 @@ export class NearSafe {
321
322
  const userOp = await this.buildTransaction({
322
323
  chainId,
323
324
  transactions,
324
- usePaymaster,
325
+ ...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
325
326
  });
326
327
  const opHash = await this.opHash(chainId, userOp);
327
328
  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. */
@@ -11,4 +11,30 @@ export declare function isContract(address: Address, chainId: number): Promise<b
11
11
  export declare function getClient(chainId: number): PublicClient;
12
12
  export declare function metaTransactionsFromRequest(params: SessionRequestParams): MetaTransaction[];
13
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>;
14
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, toBytes, keccak256, } 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) {
@@ -55,3 +54,66 @@ export function saltNonceFromMessage(input) {
55
54
  // Return string for readability and transport.
56
55
  return BigInt(keccak256(toBytes(input))).toString();
57
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.2",
3
+ "version": "0.7.1",
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.9",
47
+ "near-ca": "^0.5.10",
48
48
  "semver": "^7.6.3",
49
49
  "viem": "^2.16.5"
50
50
  },