near-safe 0.6.2 → 0.7.0

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
  }
@@ -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;
@@ -87,7 +87,7 @@ class NearSafe {
87
87
  * @throws {Error} - Throws an error if the transaction set is empty or if any operation fails during the building process.
88
88
  */
89
89
  async buildTransaction(args) {
90
- const { transactions, usePaymaster, chainId } = args;
90
+ const { transactions, sponsorshipPolicy, chainId } = args;
91
91
  if (transactions.length === 0) {
92
92
  throw new Error("Empty transaction set!");
93
93
  }
@@ -101,7 +101,7 @@ class NearSafe {
101
101
  // Build Singular MetaTransaction for Multisend from transaction list.
102
102
  const tx = transactions.length > 1 ? (0, multisend_1.encodeMulti)(transactions) : transactions[0];
103
103
  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);
104
+ const paymasterData = await bundler.getPaymasterData(rawUserOp, !safeDeployed, sponsorshipPolicy);
105
105
  const unsignedUserOp = { ...rawUserOp, ...paymasterData };
106
106
  return unsignedUserOp;
107
107
  }
@@ -131,8 +131,8 @@ class NearSafe {
131
131
  * @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
132
132
  * @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
133
133
  */
134
- async encodeSignRequest(signRequest, usePaymaster) {
135
- const { payload, evmMessage, hash } = await this.requestRouter(signRequest, usePaymaster);
134
+ async encodeSignRequest(signRequest, sponsorshipPolicy) {
135
+ const { payload, evmMessage, hash } = await this.requestRouter(signRequest, sponsorshipPolicy);
136
136
  return {
137
137
  nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
138
138
  path: this.nearAdapter.derivationPath,
@@ -283,7 +283,7 @@ class NearSafe {
283
283
  * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
284
284
  * the payload (hashed data), and recovery data needed for reconstructing the signature request.
285
285
  */
286
- async requestRouter({ method, chainId, params }, usePaymaster) {
286
+ async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
287
287
  const safeInfo = {
288
288
  address: { value: this.address },
289
289
  chainId: chainId.toString(),
@@ -318,7 +318,7 @@ class NearSafe {
318
318
  const userOp = await this.buildTransaction({
319
319
  chainId,
320
320
  transactions,
321
- usePaymaster,
321
+ ...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
322
322
  });
323
323
  const opHash = await this.opHash(chainId, userOp);
324
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. */
@@ -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
  }
@@ -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;
@@ -90,7 +90,7 @@ export class NearSafe {
90
90
  * @throws {Error} - Throws an error if the transaction set is empty or if any operation fails during the building process.
91
91
  */
92
92
  async buildTransaction(args) {
93
- const { transactions, usePaymaster, chainId } = args;
93
+ const { transactions, sponsorshipPolicy, chainId } = args;
94
94
  if (transactions.length === 0) {
95
95
  throw new Error("Empty transaction set!");
96
96
  }
@@ -104,7 +104,7 @@ export class NearSafe {
104
104
  // Build Singular MetaTransaction for Multisend from transaction list.
105
105
  const tx = transactions.length > 1 ? encodeMulti(transactions) : transactions[0];
106
106
  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);
107
+ const paymasterData = await bundler.getPaymasterData(rawUserOp, !safeDeployed, sponsorshipPolicy);
108
108
  const unsignedUserOp = { ...rawUserOp, ...paymasterData };
109
109
  return unsignedUserOp;
110
110
  }
@@ -134,8 +134,8 @@ export class NearSafe {
134
134
  * @param {boolean} usePaymaster - Flag indicating whether to use a paymaster for gas fees. If true, the transaction will be sponsored by the paymaster.
135
135
  * @returns {Promise<EncodedTxData>} - A promise that resolves to the encoded transaction data for the NEAR and EVM networks.
136
136
  */
137
- async encodeSignRequest(signRequest, usePaymaster) {
138
- const { payload, evmMessage, hash } = await this.requestRouter(signRequest, usePaymaster);
137
+ async encodeSignRequest(signRequest, sponsorshipPolicy) {
138
+ const { payload, evmMessage, hash } = await this.requestRouter(signRequest, sponsorshipPolicy);
139
139
  return {
140
140
  nearPayload: await this.nearAdapter.mpcContract.encodeSignatureRequestTx({
141
141
  path: this.nearAdapter.derivationPath,
@@ -286,7 +286,7 @@ export class NearSafe {
286
286
  * - Returns a promise that resolves to an object containing the Ethereum Virtual Machine (EVM) message,
287
287
  * the payload (hashed data), and recovery data needed for reconstructing the signature request.
288
288
  */
289
- async requestRouter({ method, chainId, params }, usePaymaster) {
289
+ async requestRouter({ method, chainId, params }, sponsorshipPolicy) {
290
290
  const safeInfo = {
291
291
  address: { value: this.address },
292
292
  chainId: chainId.toString(),
@@ -321,7 +321,7 @@ export class NearSafe {
321
321
  const userOp = await this.buildTransaction({
322
322
  chainId,
323
323
  transactions,
324
- usePaymaster,
324
+ ...(sponsorshipPolicy ? { sponsorshipPolicy } : {}),
325
325
  });
326
326
  const opHash = await this.opHash(chainId, userOp);
327
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. */
@@ -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.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.9",
47
+ "near-ca": "^0.5.10",
48
48
  "semver": "^7.6.3",
49
49
  "viem": "^2.16.5"
50
50
  },