UNPKG

startale-aa-sdk

Version:

SDK for startale account integration with support for account abstraction, ERC-7579, ERC-4337.

319 lines 11.2 kB
import { concat, decodeFunctionResult, encodeAbiParameters, encodeFunctionData, erc20Abi, hexToBytes, keccak256, parseAbi, parseAbiParameters, publicActions, stringToBytes, toBytes, toHex } from "viem"; import { STARTALE_TOKEN_PAYMASTER, MOCK_MULTI_MODULE_ADDRESS, MODULE_ENABLE_MODE_TYPE_HASH, ACCOUNT_DOMAIN_NAME, ACCOUNT_DOMAIN_TYPEHASH, ACCOUNT_DOMAIN_VERSION } from "../../account/utils/Constants.js"; import { EIP1271Abi } from "../../constants/abi/index.js"; import { moduleTypeIds } from "../../modules/utils/Types.js"; /** * Type guard to check if a value is null or undefined. * * @param value - The value to check * @returns True if the value is null or undefined */ export const isNullOrUndefined = (value) => { return value === null || value === undefined; }; /** * Validates if a string is a valid RPC URL. * * @param url - The URL to validate * @returns True if the URL is a valid RPC endpoint */ export const isValidRpcUrl = (url) => { const regex = /^(http:\/\/|wss:\/\/|https:\/\/).*/; return regex.test(url); }; /** * Compares two addresses for equality, case-insensitive. * * @param a - First address * @param b - Second address * @returns True if addresses are equal */ export const addressEquals = (a, b) => !!a && !!b && a?.toLowerCase() === b.toLowerCase(); /** * Wraps a signature according to EIP-6492 specification. * * @param params - Parameters including factory address, calldata, and signature * @returns The wrapped signature */ export const wrapSignatureWith6492 = ({ factoryAddress, factoryCalldata, signature }) => { // wrap the signature as follows: https://eips.ethereum.org/EIPS/eip-6492 // concat( // abi.encode( // (create2Factory, factoryCalldata, originalERC1271Signature), // (address, bytes, bytes)), // magicBytes // ) return concat([ encodeAbiParameters(parseAbiParameters("address, bytes, bytes"), [ factoryAddress, factoryCalldata, signature ]), "0x6492649264926492649264926492649264926492649264926492649264926492" ]); }; /** * Calculates the percentage of a partial value relative to a total value. * * @param partialValue - The partial value * @param totalValue - The total value * @returns The percentage as a number */ export function percentage(partialValue, totalValue) { return (100 * partialValue) / totalValue; } /** * Converts a percentage to a factor (e.g., 50% -> 1.5). * * @param percentage - The percentage value (1-100) * @returns The converted factor * @throws If percentage is outside valid range */ export function convertToFactor(percentage) { // Check if the input is within the valid range if (percentage) { if (percentage < 1 || percentage > 100) { throw new Error("The percentage value should be between 1 and 100."); } // Calculate the factor const factor = percentage / 100 + 1; return factor; } return 1; } /** * Generates installation data and hash for module installation. * * @param accountOwner - The account owner address * @param modules - Array of modules with their types and configurations * @param domainName - Optional domain name * @param domainVersion - Optional domain version * @returns Tuple of [installData, hash] */ export function makeInstallDataAndHash(accountOwner, modules, domainName = ACCOUNT_DOMAIN_NAME, domainVersion = ACCOUNT_DOMAIN_VERSION) { const types = modules.map((module) => BigInt(moduleTypeIds[module.type])); const initDatas = modules.map((module) => toHex(concat([toBytes(BigInt(moduleTypeIds[module.type])), module.config]))); const multiInstallData = encodeAbiParameters([{ type: "uint256[]" }, { type: "bytes[]" }], [types, initDatas]); const structHash = keccak256(encodeAbiParameters([{ type: "bytes32" }, { type: "address" }, { type: "bytes32" }], [ MODULE_ENABLE_MODE_TYPE_HASH, MOCK_MULTI_MODULE_ADDRESS, keccak256(multiInstallData) ])); const hashToSign = _hashTypedData(structHash, domainName, domainVersion, accountOwner); return [multiInstallData, hashToSign]; } export function _hashTypedData(structHash, name, version, verifyingContract) { const DOMAIN_SEPARATOR = keccak256(encodeAbiParameters([ { type: "bytes32" }, { type: "bytes32" }, { type: "bytes32" }, { type: "address" } ], [ keccak256(stringToBytes(ACCOUNT_DOMAIN_TYPEHASH)), keccak256(stringToBytes(name)), keccak256(stringToBytes(version)), verifyingContract ])); return keccak256(concat([ stringToBytes("\x19\x01"), hexToBytes(DOMAIN_SEPARATOR), hexToBytes(structHash) ])); } export function getTypesForEIP712Domain({ domain }) { return [ typeof domain?.name === "string" && { name: "name", type: "string" }, domain?.version && { name: "version", type: "string" }, typeof domain?.chainId === "number" && { name: "chainId", type: "uint256" }, domain?.verifyingContract && { name: "verifyingContract", type: "address" }, domain?.salt && { name: "salt", type: "bytes32" } ].filter(Boolean); } /** * Retrieves account metadata including name, version, and chain ID. * * @param client - The viem Client instance * @param accountAddress - The account address to query * @returns Promise resolving to account metadata */ export const getAccountMeta = async (client, accountAddress) => { try { const domain = await client.request({ method: "eth_call", params: [ { to: accountAddress, data: encodeFunctionData({ abi: EIP1271Abi, functionName: "eip712Domain" }) }, "latest" ] }); if (domain !== "0x") { const decoded = decodeFunctionResult({ abi: EIP1271Abi, functionName: "eip712Domain", data: domain }); return { name: decoded?.[1], version: decoded?.[2], chainId: decoded?.[3] }; } } catch (error) { } return { name: ACCOUNT_DOMAIN_NAME, version: ACCOUNT_DOMAIN_VERSION, chainId: client.chain ? BigInt(client.chain.id) : BigInt(await client.extend(publicActions).getChainId()) }; }; /** * Wraps a typed data hash with EIP-712 domain separator. * * @param typedHash - The hash to wrap * @param appDomainSeparator - The domain separator * @returns The wrapped hash */ export const eip712WrapHash = (typedHash, appDomainSeparator) => keccak256(concat(["0x1901", appDomainSeparator, typedHash])); export function typeToString(typeDef) { return Object.entries(typeDef).map(([key, fields]) => { const fieldStrings = (fields ?? []) .map((field) => `${field.type} ${field.name}`) .join(","); return `${key}(${fieldStrings})`; }); } /** @ignore */ export function bigIntReplacer(_key, value) { return typeof value === "bigint" ? value.toString() : value; } export function numberTo3Bytes(key) { // todo: check range const buffer = new Uint8Array(3); buffer[0] = Number((key >> 16n) & 0xffn); buffer[1] = Number((key >> 8n) & 0xffn); buffer[2] = Number(key & 0xffn); return buffer; } export function toHexString(byteArray) { return Array.from(byteArray) .map((byte) => byte.toString(16).padStart(2, "0")) // Convert each byte to hex and pad to 2 digits .join(""); // Join all hex values together into a single string } export const getAccountDomainStructFields = async (publicClient, accountAddress) => { const accountDomainStructFields = (await publicClient.readContract({ address: accountAddress, abi: parseAbi([ "function eip712Domain() public view returns (bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions)" ]), functionName: "eip712Domain" })); const [, name, version, chainId, verifyingContract, salt] = accountDomainStructFields; const params = parseAbiParameters([ "bytes32", "bytes32", "uint256", "address", "bytes32" ]); return encodeAbiParameters(params, [ keccak256(toBytes(name)), keccak256(toBytes(version)), chainId, verifyingContract, salt ]); }; export const inProduction = () => { try { return process?.env?.environment === "production"; } catch (e) { return true; } }; export const playgroundTrue = () => { try { return process?.env?.RUN_PLAYGROUND === "true"; } catch (e) { return false; } }; export const getTenderlyDetails = () => { try { const accountSlug = process?.env?.TENDERLY_ACCOUNT_SLUG; const projectSlug = process?.env?.TENDERLY_PROJECT_SLUG; const apiKey = process?.env?.TENDERLY_API_KEY; if (!accountSlug || !projectSlug || !apiKey) { return null; } return { accountSlug, projectSlug, apiKey }; } catch (e) { return null; } }; /** * Safely multiplies a bigint by a number, rounding appropriately. * * @param bI - The bigint to multiply * @param multiplier - The multiplication factor * @returns The multiplied bigint */ export const safeMultiplier = (bI, multiplier) => BigInt(Math.round(Number(bI) * multiplier)); export const getAllowance = async (client, owner, tokenAddress, grantee = STARTALE_TOKEN_PAYMASTER) => { const approval = await client.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "allowance", args: [owner, grantee] }); return approval; }; export function parseRequestArguments(input) { const fieldsToOmit = [ "callGasLimit", "preVerificationGas", "maxFeePerGas", "maxPriorityFeePerGas", "paymasterAndData", "verificationGasLimit" ]; // Skip the first element which is just "Request Arguments:" const argsString = input.slice(1).join(""); // Split by newlines and filter out empty lines const lines = argsString.split("\n").filter((line) => line.trim()); // Create an object from the key-value pairs const result = lines.reduce((acc, line) => { // Remove extra spaces and split by ':' const [key, value] = line.split(":").map((s) => s.trim()); // Clean up the key (remove trailing spaces and colons) const cleanKey = key.trim(); // Clean up the value (remove 'gwei' and other units) const cleanValue = value.replace("gwei", "").trim(); if (fieldsToOmit.includes(cleanKey)) { return acc; } acc[cleanKey] = cleanValue; return acc; }, {}); return result; } //# sourceMappingURL=Utils.js.map