UNPKG

@safe-global/api-kit

Version:

SDK that facilitates the interaction with the Safe Transaction Service API

1,000 lines (990 loc) 33.3 kB
// src/utils/httpRequests.ts async function sendRequest({ url, method, body }, apiKey) { const fetch = await (typeof window === "undefined" ? import("node-fetch").then((m) => m.default) : Promise.resolve(window.fetch)); const headers = { Accept: "application/json", "Content-Type": "application/json" }; if (apiKey) { headers["Authorization"] = `Bearer ${apiKey}`; } const response = await fetch(url, { method, headers, body: JSON.stringify(body) }); let jsonResponse; try { jsonResponse = await response.json(); } catch (error) { if (!response.ok) { throw new Error(response.statusText); } } if (response.ok) { return jsonResponse; } if (jsonResponse.data) { throw new Error(jsonResponse.data); } if (jsonResponse.detail) { throw new Error(jsonResponse.detail); } if (jsonResponse.message) { throw new Error(jsonResponse.message); } if (jsonResponse.nonFieldErrors) { throw new Error(jsonResponse.nonFieldErrors); } if (jsonResponse.delegate) { throw new Error(jsonResponse.delegate); } if (jsonResponse.safe) { throw new Error(jsonResponse.safe); } if (jsonResponse.delegator) { throw new Error(jsonResponse.delegator); } throw new Error(response.statusText); } // src/utils/signDelegate.ts async function signDelegate(walletClient, delegateAddress, chainId) { const domain = { name: "Safe Transaction Service", version: "1.0", chainId: Number(chainId) }; const types = { Delegate: [ { name: "delegateAddress", type: "address" }, { name: "totp", type: "uint256" } ] }; const totp = Math.floor(Date.now() / 1e3 / 3600); return walletClient.signTypedData({ domain, types, primaryType: "Delegate", message: { delegateAddress, totp } }); } // src/SafeApiKit.ts import { validateEip3770Address, validateEthereumAddress } from "@safe-global/protocol-kit"; // src/utils/config.ts var TRANSACTION_SERVICE_URL = "https://api.safe.global/tx-service"; var networks = [ { chainId: 1n, shortName: "eth" }, { chainId: 10n, shortName: "oeth" }, { chainId: 56n, shortName: "bnb" }, { chainId: 100n, shortName: "gno" }, { chainId: 130n, shortName: "unichain" }, { chainId: 137n, shortName: "pol" }, { chainId: 146n, shortName: "sonic" }, { chainId: 196n, shortName: "okb" }, { chainId: 232n, shortName: "lens" }, { chainId: 324n, shortName: "zksync" }, { chainId: 480n, shortName: "wc" }, { chainId: 1101n, shortName: "zkevm" }, { chainId: 5000n, shortName: "mantle" }, { chainId: 8453n, shortName: "base" }, { chainId: 10200n, shortName: "chi" }, { chainId: 42161n, shortName: "arb1" }, { chainId: 43111n, shortName: "hemi" }, { chainId: 57073n, shortName: "ink" }, { chainId: 80094n, shortName: "berachain" }, { chainId: 59144n, shortName: "linea" }, { chainId: 42220n, shortName: "celo" }, { chainId: 43114n, shortName: "avax" }, { chainId: 84532n, shortName: "basesep" }, { chainId: 534352n, shortName: "scr" }, { chainId: 11155111n, shortName: "sep" }, { chainId: 1313161554n, shortName: "aurora" } ]; var getNetworkShortName = (chainId) => { const network = networks.find((n) => n.chainId === chainId); if (!network) { throw new Error(`Network with chainId ${chainId} not found`); } return network.shortName; }; var getTransactionServiceUrl = (chainId) => { return `${TRANSACTION_SERVICE_URL}/${getNetworkShortName(chainId)}/api`; }; // src/utils/constants.ts var EMPTY_DATA = "0x"; // src/utils/index.ts var isEmptyData = (input) => !input || input === EMPTY_DATA; // src/utils/safeOperation.ts var getAddSafeOperationProps = async (safeOperation) => { const userOperation = safeOperation.getUserOperation(); userOperation.signature = safeOperation.encodedSignatures(); return { entryPoint: safeOperation.options.entryPoint, moduleAddress: safeOperation.options.moduleAddress, safeAddress: userOperation.sender, userOperation, options: { validAfter: safeOperation.options.validAfter, validUntil: safeOperation.options.validUntil } }; }; var isSafeOperation = (obj) => { return "signatures" in obj && "getUserOperation" in obj && "getHash" in obj; }; // src/utils/queryParamsMap.ts var QUERY_PARAMS_MAP = { from: "_from" }; // src/SafeApiKit.ts var SafeApiKit = class { #chainId; #apiKey; #txServiceBaseUrl; constructor({ chainId, txServiceUrl, apiKey }) { this.#chainId = chainId; if (txServiceUrl) { if ((txServiceUrl.includes("api.safe.global") || txServiceUrl.includes("api.5afe.dev")) && !apiKey) { throw new Error( "apiKey is mandatory when using api.safe.global or api.5afe.dev domains. Please obtain your API key at https://developer.safe.global." ); } this.#txServiceBaseUrl = txServiceUrl; } else { if (!apiKey) { throw new Error( "apiKey is mandatory when txServiceUrl is not defined. Please obtain your API key at https://developer.safe.global." ); } const url = getTransactionServiceUrl(chainId); if (!url) { throw new TypeError( `There is no transaction service available for chainId ${chainId}. Please set the txServiceUrl property to use a custom transaction service.` ); } this.#txServiceBaseUrl = url; } this.#apiKey = apiKey; } #isValidAddress(address) { try { validateEthereumAddress(address); return true; } catch { return false; } } #getEip3770Address(fullAddress) { return validateEip3770Address(fullAddress, this.#chainId); } /** * Adds query parameters from an options object to a given URL. * Converts parameter names to snake_case automatically. If a specific mapping exists in QUERY_PARAMS_MAP, * it will be used instead of the converted name. * * @param {URL} url - The URL object to which query parameters will be added. * @param {T} options - An object containing key-value pairs representing query parameters. * @returns {void} */ #addUrlQueryParams(url, options) { const camelToSnake = (str) => str.replace(/([A-Z])/g, "_$1").toLowerCase(); Object.entries(options || {}).forEach(([key, value]) => { if (value !== void 0) { const name = QUERY_PARAMS_MAP[key] ?? camelToSnake(key); url.searchParams.set(name, value.toString()); } }); } async #api(request) { return sendRequest(request, this.#apiKey); } /** * Returns the information and configuration of the service. * * @returns The information and configuration of the service */ async getServiceInfo() { return this.#api({ url: `${this.#txServiceBaseUrl}/v1/about`, method: "get" /* Get */ }); } /** * Returns the list of Safe singletons. * * @returns The list of Safe singletons */ async getServiceSingletonsInfo() { return this.#api({ url: `${this.#txServiceBaseUrl}/v1/about/singletons`, method: "get" /* Get */ }); } /** * Decodes the specified Safe transaction data. * * @param data - The Safe transaction data. '0x' prefixed hexadecimal string. * @param to - The address of the receiving contract. If provided, the decoded data will be more accurate, as in case of an ABI collision the Safe Transaction Service would know which ABI to use * @returns The transaction data decoded * @throws "Invalid data" * @throws "Not Found" * @throws "Ensure this field has at least 1 hexadecimal chars (not counting 0x)." */ async decodeData(data, to) { if (data === "") { throw new Error("Invalid data"); } const dataDecoderRequest = { data }; if (to) { dataDecoderRequest.to = to; } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/data-decoder/`, method: "post" /* Post */, body: dataDecoderRequest }); } /** * Returns the list of delegates. * * @param getSafeDelegateProps - Properties to filter the returned list of delegates * @returns The list of delegates * @throws "Checksum address validation failed" */ async getSafeDelegates({ safeAddress, delegateAddress, delegatorAddress, label, limit, offset }) { const url = new URL(`${this.#txServiceBaseUrl}/v2/delegates`); if (safeAddress) { const { address: safe } = this.#getEip3770Address(safeAddress); url.searchParams.set("safe", safe); } if (delegateAddress) { const { address: delegate } = this.#getEip3770Address(delegateAddress); url.searchParams.set("delegate", delegate); } if (delegatorAddress) { const { address: delegator } = this.#getEip3770Address(delegatorAddress); url.searchParams.set("delegator", delegator); } if (label) { url.searchParams.set("label", label); } if (limit != null) { url.searchParams.set("limit", limit.toString()); } if (offset != null) { url.searchParams.set("offset", offset.toString()); } return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Adds a new delegate for a given Safe address. * * @param addSafeDelegateProps - The configuration of the new delegate * @returns * @throws "Invalid Safe delegate address" * @throws "Invalid Safe delegator address" * @throws "Invalid label" * @throws "Checksum address validation failed" * @throws "Address <delegate_address> is not checksumed" * @throws "Safe=<safe_address> does not exist or it's still not indexed" * @throws "Signing owner is not an owner of the Safe" */ async addSafeDelegate({ safeAddress, delegateAddress, delegatorAddress, label, signer }) { if (delegateAddress === "") { throw new Error("Invalid Safe delegate address"); } if (delegatorAddress === "") { throw new Error("Invalid Safe delegator address"); } if (label === "") { throw new Error("Invalid label"); } const { address: delegate } = this.#getEip3770Address(delegateAddress); const { address: delegator } = this.#getEip3770Address(delegatorAddress); const signature = await signDelegate(signer, delegate, this.#chainId); const body = { safe: safeAddress ? this.#getEip3770Address(safeAddress).address : null, delegate, delegator, label, signature }; return this.#api({ url: `${this.#txServiceBaseUrl}/v2/delegates/`, method: "post" /* Post */, body }); } /** * Removes a delegate for a given Safe address. * * @param deleteSafeDelegateProps - The configuration for the delegate that will be removed * @returns * @throws "Invalid Safe delegate address" * @throws "Invalid Safe delegator address" * @throws "Checksum address validation failed" * @throws "Signing owner is not an owner of the Safe" * @throws "Not found" */ async removeSafeDelegate({ delegateAddress, delegatorAddress, signer }) { if (delegateAddress === "") { throw new Error("Invalid Safe delegate address"); } if (delegatorAddress === "") { throw new Error("Invalid Safe delegator address"); } const { address: delegate } = this.#getEip3770Address(delegateAddress); const { address: delegator } = this.#getEip3770Address(delegatorAddress); const signature = await signDelegate(signer, delegate, this.#chainId); return this.#api({ url: `${this.#txServiceBaseUrl}/v2/delegates/${delegate}`, method: "delete" /* Delete */, body: { delegator, signature } }); } /** * Get a message by its safe message hash * @param messageHash The Safe message hash * @returns The message */ async getMessage(messageHash) { if (!messageHash) { throw new Error("Invalid messageHash"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/messages/${messageHash}/`, method: "get" /* Get */ }); } /** * Get the list of messages associated to a Safe account * @param safeAddress The safe address * @param options The options to filter the list of messages * @returns The paginated list of messages */ async getMessages(safeAddress, options = {}) { if (!this.#isValidAddress(safeAddress)) { throw new Error("Invalid safeAddress"); } const url = new URL(`${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/messages/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Creates a new message with an initial signature * Add more signatures from other owners using addMessageSignature() * @param safeAddress The safe address * @param options The raw message to add, signature and safeAppId if any */ async addMessage(safeAddress, addMessageOptions) { if (!this.#isValidAddress(safeAddress)) { throw new Error("Invalid safeAddress"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/messages/`, method: "post" /* Post */, body: addMessageOptions }); } /** * Add a signature to an existing message * @param messageHash The safe message hash * @param signature The signature */ async addMessageSignature(messageHash, signature) { if (!messageHash || !signature) { throw new Error("Invalid messageHash or signature"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/messages/${messageHash}/signatures/`, method: "post" /* Post */, body: { signature } }); } /** * Returns the list of Safes where the address provided is an owner. * * @param ownerAddress - The owner address * @returns The list of Safes where the address provided is an owner * @throws "Invalid owner address" * @throws "Checksum address validation failed" */ async getSafesByOwner(ownerAddress) { if (ownerAddress === "") { throw new Error("Invalid owner address"); } const { address } = this.#getEip3770Address(ownerAddress); return this.#api({ url: `${this.#txServiceBaseUrl}/v1/owners/${address}/safes/`, method: "get" /* Get */ }); } /** * Returns the list of Safes where the module address provided is enabled. * * @param moduleAddress - The Safe module address * @returns The list of Safe addresses where the module provided is enabled * @throws "Invalid module address" * @throws "Module address checksum not valid" */ async getSafesByModule(moduleAddress) { if (moduleAddress === "") { throw new Error("Invalid module address"); } const { address } = this.#getEip3770Address(moduleAddress); return this.#api({ url: `${this.#txServiceBaseUrl}/v1/modules/${address}/safes/`, method: "get" /* Get */ }); } /** * Returns all the information of a Safe transaction. * * @param safeTxHash - Hash of the Safe transaction * @returns The information of a Safe transaction * @throws "Invalid safeTxHash" * @throws "Not found." */ async getTransaction(safeTxHash) { if (safeTxHash === "") { throw new Error("Invalid safeTxHash"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v2/multisig-transactions/${safeTxHash}/`, method: "get" /* Get */ }); } /** * Returns the list of confirmations for a given a Safe transaction. * * @param safeTxHash - The hash of the Safe transaction * @returns The list of confirmations * @throws "Invalid safeTxHash" */ async getTransactionConfirmations(safeTxHash) { if (safeTxHash === "") { throw new Error("Invalid safeTxHash"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/multisig-transactions/${safeTxHash}/confirmations/`, method: "get" /* Get */ }); } /** * Adds a confirmation for a Safe transaction. * * @param safeTxHash - Hash of the Safe transaction that will be confirmed * @param signature - Signature of the transaction * @returns * @throws "Invalid safeTxHash" * @throws "Invalid signature" * @throws "Malformed data" * @throws "Error processing data" */ async confirmTransaction(safeTxHash, signature) { if (safeTxHash === "") { throw new Error("Invalid safeTxHash"); } if (signature === "") { throw new Error("Invalid signature"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/multisig-transactions/${safeTxHash}/confirmations/`, method: "post" /* Post */, body: { signature } }); } /** * Returns the information and configuration of the provided Safe address. * * @param safeAddress - The Safe address * @returns The information and configuration of the provided Safe address * @throws "Invalid Safe address" * @throws "Checksum address validation failed" */ async getSafeInfo(safeAddress) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safes/${address}/`, method: "get" /* Get */ }).then((response) => { if (!response?.singleton) { const { masterCopy, ...rest } = response; return { ...rest, singleton: masterCopy }; } return response; }); } /** * Returns the creation information of a Safe. * * @param safeAddress - The Safe address * @returns The creation information of a Safe * @throws "Invalid Safe address" * @throws "Safe creation not found" * @throws "Checksum address validation failed" * @throws "Problem connecting to Ethereum network" */ async getSafeCreationInfo(safeAddress) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safes/${address}/creation/`, method: "get" /* Get */ }).then((response) => { if (!response?.singleton) { const { masterCopy, ...rest } = response; return { ...rest, singleton: masterCopy }; } return response; }); } /** * Estimates the safeTxGas for a given Safe multi-signature transaction. * * @param safeAddress - The Safe address * @param safeTransaction - The Safe transaction to estimate * @returns The safeTxGas for the given Safe transaction * @throws "Invalid Safe address" * @throws "Data not valid" * @throws "Safe not found" * @throws "Tx not valid" */ async estimateSafeTransaction(safeAddress, safeTransaction) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safes/${address}/multisig-transactions/estimations/`, method: "post" /* Post */, body: safeTransaction }); } /** * Creates a new multi-signature transaction with its confirmations and stores it in the Safe Transaction Service. * * @param proposeTransactionConfig - The configuration of the proposed transaction * @returns The hash of the Safe transaction proposed * @throws "Invalid Safe address" * @throws "Invalid safeTxHash" * @throws "Invalid data" * @throws "Invalid ethereum address/User is not an owner/Invalid signature/Nonce already executed/Sender is not an owner" */ async proposeTransaction({ safeAddress, safeTransactionData, safeTxHash, senderAddress, senderSignature, origin }) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address: safe } = this.#getEip3770Address(safeAddress); const { address: sender } = this.#getEip3770Address(senderAddress); if (safeTxHash === "") { throw new Error("Invalid safeTxHash"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v2/safes/${safe}/multisig-transactions/`, method: "post" /* Post */, body: { ...safeTransactionData, contractTransactionHash: safeTxHash, sender, signature: senderSignature, origin } }); } /** * Returns the history of incoming transactions of a Safe account. * * @param safeAddress - The Safe address * @param options - Optional parameters to filter or modify the response * @returns The history of incoming transactions * @throws "Invalid Safe address" * @throws "Checksum address validation failed" */ async getIncomingTransactions(safeAddress, options) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); const url = new URL(`${this.#txServiceBaseUrl}/v1/safes/${address}/incoming-transfers/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Returns the history of module transactions of a Safe account. * * @param safeAddress - The Safe address * @param options - Optional parameters to filter or modify the response * @returns The history of module transactions * @throws "Invalid Safe address" * @throws "Invalid data" * @throws "Invalid ethereum address" */ async getModuleTransactions(safeAddress, options) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); const url = new URL(`${this.#txServiceBaseUrl}/v1/safes/${address}/module-transactions/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Returns the history of multi-signature transactions of a Safe account. * * @param safeAddress - The Safe address * @param options - Optional parameters to filter or modify the response * @returns The history of multi-signature transactions * @throws "Invalid Safe address" * @throws "Checksum address validation failed" */ async getMultisigTransactions(safeAddress, options) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); const url = new URL(`${this.#txServiceBaseUrl}/v2/safes/${address}/multisig-transactions/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Returns the list of multi-signature transactions that are waiting for the confirmation of the Safe owners. * * @param safeAddress - The Safe address * @param {PendingTransactionsOptions} options The options to filter the list of transactions * @returns The list of transactions waiting for the confirmation of the Safe owners * @throws "Invalid Safe address" * @throws "Invalid data" * @throws "Invalid ethereum address" */ async getPendingTransactions(safeAddress, options = {}) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { currentNonce, hasConfirmations, ordering, limit, offset } = options; const { address } = this.#getEip3770Address(safeAddress); const nonce = currentNonce ? currentNonce : (await this.getSafeInfo(address)).nonce; const url = new URL( `${this.#txServiceBaseUrl}/v2/safes/${address}/multisig-transactions/?executed=false&nonce__gte=${nonce}` ); if (hasConfirmations) { url.searchParams.set("has_confirmations", hasConfirmations.toString()); } if (ordering) { url.searchParams.set("ordering", ordering); } if (limit != null) { url.searchParams.set("limit", limit.toString()); } if (offset != null) { url.searchParams.set("offset", offset.toString()); } return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Returns a list of transactions for a Safe. The list has different structures depending on the transaction type * * @param safeAddress - The Safe address * @param options - Optional parameters to filter or modify the response * @returns The list of transactions waiting for the confirmation of the Safe owners * @throws "Invalid Safe address" * @throws "Checksum address validation failed" * @throws "Ordering field is not valid" */ async getAllTransactions(safeAddress, options) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); const url = new URL(`${this.#txServiceBaseUrl}/v2/safes/${address}/all-transactions/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Returns the right nonce to propose a new transaction after the last pending transaction. * * @param safeAddress - The Safe address * @returns The right nonce to propose a new transaction after the last pending transaction * @throws "Invalid Safe address" * @throws "Invalid data" * @throws "Invalid ethereum address" */ async getNextNonce(safeAddress) { if (safeAddress === "") { throw new Error("Invalid Safe address"); } const { address } = this.#getEip3770Address(safeAddress); const pendingTransactions = await this.getPendingTransactions(address); if (pendingTransactions.results.length > 0) { const maxNonce = pendingTransactions.results.reduce((acc, tx) => { const curr = BigInt(tx.nonce); return curr > acc ? curr : acc; }, 0n); return (maxNonce + 1n).toString(); } const safeInfo = await this.getSafeInfo(address); return safeInfo.nonce; } /** * Returns the list of all the ERC20 tokens handled by the Safe. * * @param options - Optional parameters to filter or modify the response * @returns The list of all the ERC20 tokens */ async getTokenList(options) { const url = new URL(`${this.#txServiceBaseUrl}/v1/tokens/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Returns the information of a given ERC20 token. * * @param tokenAddress - The token address * @returns The information of the given ERC20 token * @throws "Invalid token address" * @throws "Checksum address validation failed" */ async getToken(tokenAddress) { if (tokenAddress === "") { throw new Error("Invalid token address"); } const { address } = this.#getEip3770Address(tokenAddress); return this.#api({ url: `${this.#txServiceBaseUrl}/v1/tokens/${address}/`, method: "get" /* Get */ }); } /** * Get the SafeOperations that were sent from a particular address. * @param safeAddress - The Safe address * @param options - Optional parameters to filter or modify the response * @throws "Safe address must not be empty" * @throws "Invalid Ethereum address {safeAddress}" * @returns The SafeOperations sent from the given Safe's address */ async getSafeOperationsByAddress(safeAddress, options) { if (!safeAddress) { throw new Error("Safe address must not be empty"); } const { address } = this.#getEip3770Address(safeAddress); const url = new URL(`${this.#txServiceBaseUrl}/v1/safes/${address}/safe-operations/`); this.#addUrlQueryParams(url, options); return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Get the SafeOperations that are pending to send to the bundler * @param safeAddress - The Safe address * @param options - Optional parameters to filter or modify the response * @throws "Safe address must not be empty" * @throws "Invalid Ethereum address {safeAddress}" * @returns The pending SafeOperations */ async getPendingSafeOperations(safeAddress, options) { return this.getSafeOperationsByAddress(safeAddress, { ...options, executed: false }); } /** * Get a SafeOperation by its hash. * @param safeOperationHash The SafeOperation hash * @throws "SafeOperation hash must not be empty" * @throws "Not found." * @returns The SafeOperation */ async getSafeOperation(safeOperationHash) { if (!safeOperationHash) { throw new Error("SafeOperation hash must not be empty"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safe-operations/${safeOperationHash}/`, method: "get" /* Get */ }); } /** * Create a new 4337 SafeOperation for a Safe. * @param addSafeOperationProps - The configuration of the SafeOperation * @throws "Safe address must not be empty" * @throws "Invalid Safe address {safeAddress}" * @throws "Module address must not be empty" * @throws "Invalid module address {moduleAddress}" * @throws "Signature must not be empty" */ async addSafeOperation(safeOperation) { let safeAddress, moduleAddress; let addSafeOperationProps; if (isSafeOperation(safeOperation)) { addSafeOperationProps = await getAddSafeOperationProps(safeOperation); } else { addSafeOperationProps = safeOperation; } const { entryPoint, moduleAddress: moduleAddressProp, options, safeAddress: safeAddressProp, userOperation } = addSafeOperationProps; if (!safeAddressProp) { throw new Error("Safe address must not be empty"); } try { safeAddress = this.#getEip3770Address(safeAddressProp).address; } catch (err) { throw new Error(`Invalid Safe address ${safeAddressProp}`); } if (!moduleAddressProp) { throw new Error("Module address must not be empty"); } try { moduleAddress = this.#getEip3770Address(moduleAddressProp).address; } catch (err) { throw new Error(`Invalid module address ${moduleAddressProp}`); } if (isEmptyData(userOperation.signature)) { throw new Error("Signature must not be empty"); } const getISOString = (date) => !date ? null : new Date(date * 1e3).toISOString(); const userOperationV06 = userOperation; return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/safe-operations/`, method: "post" /* Post */, body: { initCode: isEmptyData(userOperationV06.initCode) ? null : userOperationV06.initCode, nonce: userOperation.nonce, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit.toString(), verificationGasLimit: userOperation.verificationGasLimit.toString(), preVerificationGas: userOperation.preVerificationGas.toString(), maxFeePerGas: userOperation.maxFeePerGas.toString(), maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas.toString(), paymasterAndData: isEmptyData(userOperationV06.paymasterAndData) ? null : userOperationV06.paymasterAndData, entryPoint, validAfter: getISOString(options?.validAfter), validUntil: getISOString(options?.validUntil), signature: userOperation.signature, moduleAddress } }); } /** * Returns the list of confirmations for a given a SafeOperation. * * @param safeOperationHash - The hash of the SafeOperation to get confirmations for * @param getSafeOperationConfirmationsOptions - Additional options for fetching the list of confirmations * @returns The list of confirmations * @throws "Invalid SafeOperation hash" * @throws "Invalid data" */ async getSafeOperationConfirmations(safeOperationHash, { limit, offset } = {}) { if (!safeOperationHash) { throw new Error("Invalid SafeOperation hash"); } const url = new URL( `${this.#txServiceBaseUrl}/v1/safe-operations/${safeOperationHash}/confirmations/` ); if (limit != null) { url.searchParams.set("limit", limit.toString()); } if (offset != null) { url.searchParams.set("offset", offset.toString()); } return this.#api({ url: url.toString(), method: "get" /* Get */ }); } /** * Adds a confirmation for a SafeOperation. * * @param safeOperationHash The SafeOperation hash * @param signature - Signature of the SafeOperation * @returns * @throws "Invalid SafeOperation hash" * @throws "Invalid signature" * @throws "Malformed data" * @throws "Error processing data" */ async confirmSafeOperation(safeOperationHash, signature) { if (!safeOperationHash) { throw new Error("Invalid SafeOperation hash"); } if (!signature) { throw new Error("Invalid signature"); } return this.#api({ url: `${this.#txServiceBaseUrl}/v1/safe-operations/${safeOperationHash}/confirmations/`, method: "post" /* Post */, body: { signature } }); } }; var SafeApiKit_default = SafeApiKit; // src/index.ts var index_default = SafeApiKit_default; export { index_default as default };