near-safe
Version:
An SDK for controlling Ethereum Smart Accounts via ERC4337 from a Near Account.
165 lines (164 loc) • 7.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.packGas = exports.PLACEHOLDER_SIG = void 0;
exports.packSignature = packSignature;
exports.packPaymasterData = packPaymasterData;
exports.containsValue = containsValue;
exports.isContract = isContract;
exports.getClient = getClient;
exports.metaTransactionsFromRequest = metaTransactionsFromRequest;
exports.saltNonceFromMessage = saltNonceFromMessage;
exports.signatureFromTxHash = signatureFromTxHash;
exports.raceToFirstResolve = raceToFirstResolve;
exports.assertUnique = assertUnique;
exports.userOpTransactionCost = userOpTransactionCost;
const near_ca_1 = require("near-ca");
const viem_1 = require("viem");
exports.PLACEHOLDER_SIG = (0, viem_1.encodePacked)(["uint48", "uint48"], [0, 0]);
const packGas = (hi, lo) => (0, viem_1.encodePacked)(["uint128", "uint128"], [BigInt(hi), BigInt(lo)]);
exports.packGas = packGas;
function packSignature(signature, validFrom = 0, validTo = 0) {
return (0, viem_1.encodePacked)(["uint48", "uint48", "bytes"], [validFrom, validTo, signature]);
}
function packPaymasterData(data) {
return (data.paymaster
? (0, viem_1.concatHex)([
data.paymaster,
(0, viem_1.toHex)(BigInt(data.paymasterVerificationGasLimit || 0n), { size: 16 }),
(0, viem_1.toHex)(BigInt(data.paymasterPostOpGasLimit || 0n), { size: 16 }),
data.paymasterData || "0x",
])
: "0x");
}
function containsValue(transactions) {
return transactions.some((tx) => BigInt(tx.value) !== 0n);
}
async function isContract(address, chainId) {
return (await getClient(chainId).getCode({ address })) !== undefined;
}
function getClient(chainId, rpcUrl) {
// Caution: rpcUrl might not be aligned with chainId!
const options = rpcUrl ? { rpcUrl } : {};
return near_ca_1.Network.fromChainId(chainId, options).client;
}
function metaTransactionsFromRequest(params) {
let transactions;
if ((0, viem_1.isHex)(params)) {
// TODO: Consider deprecating this route.
// If RLP hex is given, decode the transaction and build EthTransactionParams
const tx = (0, viem_1.parseTransaction)(params);
transactions = [
{
from: viem_1.zeroAddress, // TODO: This is a hack - but its unused.
to: tx.to,
value: tx.value ? (0, viem_1.toHex)(tx.value) : "0x00",
data: tx.data || "0x",
},
];
}
else {
// TODO: add type guard here.
transactions = params;
}
return transactions.map((tx) => ({
to: tx.to,
value: tx.value || "0x00",
data: tx.data || "0x",
}));
}
function saltNonceFromMessage(input) {
// Convert the string to bytes (UTF-8 encoding)
// Compute the keccak256 hash of the input bytes
// Convert the resulting hash (which is in hex) to a BigInt
// Return string for readability and transport.
return BigInt((0, viem_1.keccak256)((0, viem_1.toBytes)(input))).toString();
}
/**
* Fetches the signature for a NEAR transaction hash. If an `accountId` is provided,
* it fetches the signature from the appropriate network. Otherwise, it races across
* both `testnet` and `mainnet`.
*
* @param {string} txHash - The NEAR transaction hash for which to fetch the signature.
* @param {string} [accountId] - (Optional) The account ID associated with the transaction.
* Providing this will reduce dangling promises as the network is determined by the account.
*
* @returns {Promise<Hex>} A promise that resolves to the hex-encoded signature.
*
* @throws Will throw an error if no signature is found for the given transaction hash.
*/
async function signatureFromTxHash(txHash, accountId) {
if (accountId) {
const signature = await (0, near_ca_1.signatureFromTxHash)(`https://archival-rpc.${(0, near_ca_1.getNetworkId)(accountId)}.near.org`, txHash, accountId);
return packSignature((0, viem_1.serializeSignature)(signature));
}
try {
const signature = await raceToFirstResolve(["testnet", "mainnet"].map((network) => (0, near_ca_1.signatureFromTxHash)(archiveNode(network), txHash)));
return packSignature((0, viem_1.serializeSignature)(signature));
}
catch {
throw new Error(`No signature found for txHash ${txHash}`);
}
}
/**
* Utility function to construct an archive node URL for a given NEAR network.
*
* @param {string} networkId - The ID of the NEAR network (e.g., 'testnet', 'mainnet').
*
* @returns {string} The full URL of the archival RPC node for the specified network.
*/
const archiveNode = (networkId) => `https://archival-rpc.${networkId}.near.org`;
/**
* Races an array of promises and resolves with the first promise that fulfills.
* If all promises reject, the function will reject with an error.
*
* @template T
* @param {Promise<T>[]} promises - An array of promises to race. Each promise should resolve to type `T`.
*
* @returns {Promise<T>} A promise that resolves to the value of the first successfully resolved promise.
*
* @throws Will throw an error if all promises reject with the message "All promises rejected".
*/
async function raceToFirstResolve(promises) {
return new Promise((resolve, reject) => {
let rejectionCount = 0;
const totalPromises = promises.length;
promises.forEach((promise) => {
// Wrap each promise so it only resolves when fulfilled
Promise.resolve(promise)
.then(resolve) // Resolve when any promise resolves
.catch(() => {
rejectionCount++;
// If all promises reject, reject the race with an error
if (rejectionCount === totalPromises) {
reject(new Error("All promises rejected"));
}
});
});
});
}
function assertUnique(iterable, errorMessage = "The collection contains more than one distinct element.") {
const uniqueValues = new Set(iterable);
if (uniqueValues.size > 1) {
throw new Error(errorMessage);
}
}
function userOpTransactionCost(userOp) {
// Convert values from hex to decimal
const preVerificationGas = BigInt(userOp.preVerificationGas);
const verificationGasLimit = BigInt(userOp.verificationGasLimit);
const callGasLimit = BigInt(userOp.callGasLimit);
const paymasterVerificationGasLimit = BigInt(userOp.paymasterVerificationGasLimit || "0x0");
const paymasterPostOpGasLimit = BigInt(userOp.paymasterPostOpGasLimit || "0x0");
// Sum total gas
const totalGasUsed = preVerificationGas +
verificationGasLimit +
callGasLimit +
paymasterVerificationGasLimit +
paymasterPostOpGasLimit;
// Convert maxFeePerGas from hex to decimal
const maxFeePerGas = BigInt(userOp.maxFeePerGas);
// Calculate total cost in wei
const totalCostInWei = totalGasUsed * maxFeePerGas;
// Convert to Ether for a human-readable value
return totalCostInWei;
}