UNPKG

liquidops

Version:

LiquidOps is an over-collateralised lending and borrowing protocol built on Arweave's L2 AO.

1,585 lines (1,553 loc) 60 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { controllerAddress: () => controllerAddress, default: () => index_default, oTokens: () => oTokens, tokenData: () => tokenData, tokenInput: () => tokenInput, tokens: () => tokens }); module.exports = __toCommonJS(index_exports); // src/ao/utils/connect.ts var aoConnect = __toESM(require("@permaweb/aoconnect"), 1); var DEFAULT_SERVICES = { MODE: "legacy", MU_URL: "https://mu.ao-testnet.xyz", CU_URL: "https://cu.ao-testnet.xyz", GATEWAY_URL: "https://arweave.net" }; async function connectToAO(services, maxRetries = 3, initialDelay = 1e3) { let lastError = null; let delay = initialDelay; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`AO connection attempt ${attempt}/${maxRetries}`); const { MODE = DEFAULT_SERVICES.MODE, GRAPHQL_URL, GRAPHQL_MAX_RETRIES, GRAPHQL_RETRY_BACKOFF, GATEWAY_URL = DEFAULT_SERVICES.GATEWAY_URL, MU_URL = DEFAULT_SERVICES.MU_URL, CU_URL = DEFAULT_SERVICES.CU_URL } = services || {}; const { spawn, message, result } = aoConnect.connect({ // @ts-ignore, MODE is needed here but is not in the aoconnect type yet MODE, GATEWAY_URL, GRAPHQL_URL, GRAPHQL_MAX_RETRIES, GRAPHQL_RETRY_BACKOFF, MU_URL, CU_URL }); if (attempt !== 1) { console.log(`\u2705 AO connected successfully on attempt ${attempt}`); } return { spawn, message, result }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); const errorMessage = error instanceof Error ? error.message : String(error); console.warn(`\u274C Connection attempt ${attempt} failed:`, errorMessage); if (attempt < maxRetries) { console.log(`\u23F3 Waiting ${delay}ms before retry...`); await new Promise((resolve) => setTimeout(resolve, delay)); delay *= 1.5; } } } throw new Error( `Failed to connect to AO after ${maxRetries} attempts. Last error: ${lastError == null ? void 0 : lastError.message}` ); } // src/ao/utils/tokenAddressData.ts var controllerAddress = "SmmMv0rJwfIDVM3RvY2-P729JFYwhdGSeGo2deynbfY"; var redstoneOracleAddress = "R5rRjBFS90qIGaohtzd1IoyPwZD0qJZ25QXkP7_p5a0"; var APRAgentAddress = "D3AlSUAtbWKcozsrvckRuCY6TVkAY1rWtLYGoGf6KIA"; var tokenData = { QAR: { name: "Quantum Arweave", icon: "8VLMb0c9NATl4iczfwpMDe1Eh8kFWIUpSlIkcGfDFzM", ticker: "QAR", address: "NG-0lVX882MG5nhARrSzyprEK6ejonHpdUmaaMPsHE8", oTicker: "oQAR", oAddress: "fODpFVOb5weX9Yc-26AA82m2MhmT7N9L0TkynOsruK0", controllerAddress, cleanTicker: "qAR", denomination: BigInt(12), collateralEnabled: true, baseDenomination: BigInt(12), deprecated: true, oIcon: "i_U-jhdMMaib2hK51qPrKXbLo6cx2Nt58_gNz5FA4sw" }, WAR: { name: "Wrapped Arweave", icon: "ICMLzIKdVMedibwgOy014I4yan_F8h2ZhORhRG5dgzs", ticker: "WAR", address: "xU9zFkq3X2ZQ6olwNVvr1vUWIjc3kXTWr7xKQD6dh10", oTicker: "oWAR", oAddress: "rAc0aP0g9NXYUXAbvlLjPH_XxyQy6eYmwSuIcf6ukuw", controllerAddress, cleanTicker: "wAR", denomination: BigInt(12), collateralEnabled: true, baseDenomination: BigInt(12), deprecated: false, oIcon: "lTWBOBtEZ2JvTAHfvoPq5aXRWTVouv7jZ-6B9HTwosU" }, WUSDC: { name: "Wrapped USD Circle", icon: "iNYk0bDqUiH0eLT2rbYjYAI5i126R4ye8iAZb55IaIM", ticker: "WUSDC", address: "7zH9dlMNoxprab9loshv3Y7WG45DOny_Vrq9KrXObdQ", oTicker: "oWUSDC", oAddress: "4MW7uLFtttSLWM-yWEqV9TGD6fSIDrqa4lbTgYL2qHg", controllerAddress, cleanTicker: "wUSDC", denomination: BigInt(6), collateralEnabled: true, baseDenomination: BigInt(6), deprecated: false, oIcon: "7EEISJIzxC-3RPhgvRc-lAZnP7st1b79_ER4Sc5P_MU" }, WUSDT: { name: "Wrapped USD Tether", icon: "JaxupVYerLRZWLd32llz_3CG8sCQaNhn2hAWm51U_7s", ticker: "WUSDT", address: "7j3jUyFpTuepg_uu_sJnwLE6KiTVuA9cLrkfOp2MFlo", oTicker: "oWUSDT", oAddress: "9B9J1O5FDoMsFZGJUSOa6TwivsH7LYIfiaizPn7fUHs", controllerAddress, cleanTicker: "wUSDT", denomination: BigInt(18), collateralEnabled: true, baseDenomination: BigInt(18), deprecated: false, oIcon: "bkAnKOF4NhqPHnccDhPyOzBws42zNE-u9WtxCPdaABU" }, WETH: { name: "Wrapped Ethereum", icon: "Bi7iqzLQXN-wVD3nM8TYGfTI9g7HgGtiD0XuruoQTJk", ticker: "WETH", address: "cBgS-V_yGhOe9P1wCIuNSgDA_JS8l4sE5iFcPTr0TD0", oTicker: "oWETH", oAddress: "rNa0hdxEZjz_TAUoI85OcPRul_BzoS6Py_3vamJKpr4", controllerAddress, cleanTicker: "wETH", denomination: BigInt(18), collateralEnabled: true, baseDenomination: BigInt(18), deprecated: false, oIcon: "z1nnBgzGpt-eXHrjD5A9KrQX6dK8E1ONDuBIqB94VTA" } }; function convertTicker(ticker) { if (ticker === "QAR") return "AR"; if (ticker === "WUSDC") return "USDC"; if (ticker === "WAR") return "AR"; if (ticker === "WUSDT") return "USDT"; if (ticker === "WETH") return "ETH"; return ticker; } var tokens = Object.fromEntries( Object.entries(tokenData).map(([ticker, data]) => [ticker, data.address]) ); var oTokens = Object.fromEntries( Object.entries(tokenData).map(([_, data]) => [data.oTicker, data.oAddress]) ); var collateralEnabledTickers = Object.keys(tokenData).filter( (ticker) => tokenData[ticker].collateralEnabled ); var collateralEnabledOTickers = collateralEnabledTickers.map( (ticker) => tokenData[ticker].oTicker ); // src/ao/utils/tokenInput.ts function tokenInput(token) { const tokenEntry = Object.entries(tokens).find( ([ticker, address]) => ticker === token || address === token ); if (tokenEntry) { const [ticker, tokenAddress] = tokenEntry; return { tokenAddress, oTokenAddress: oTokens[`o${ticker}`], controllerAddress: tokenData[ticker].controllerAddress, ticker }; } if (Object.values(oTokens).some((address) => address === token) || Object.keys(oTokens).includes(token)) { throw new Error("Token input cannot be an oToken ticker or address."); } throw new Error("Token input is not supported."); } // src/arweave/getTags.ts var import_ar_gql = require("ar-gql"); async function getTags({ aoUtils, tags, owner, cursor }) { var _a, _b; try { const gqlEndpoint = aoUtils.configs.GRAPHQL_URL || "https://arweave-search.goldsky.com/graphql"; const gql = (0, import_ar_gql.arGql)({ endpointUrl: gqlEndpoint }); const recipientTag = tags.find((tag) => tag.name === "recipient"); const recipientValue = recipientTag ? Array.isArray(recipientTag.values) ? recipientTag.values[0] : recipientTag.values : void 0; const filteredTags = tags.filter((tag) => tag.name !== "recipient"); const formattedTags = filteredTags.map((tag) => ({ name: tag.name, values: Array.isArray(tag.values) ? tag.values : [tag.values] })); const query = ` query GetTransactions( $tags: [TagFilter!], $cursor: String ${owner ? ", $owner: String!" : ""} ${recipientValue ? ", $recipients: [String!]" : ""} ) { transactions( tags: $tags ${owner ? "owners: [$owner]" : ""} ${recipientValue ? "recipients: $recipients" : ""} first: 100 after: $cursor sort: HEIGHT_DESC ) { edges { cursor node { id tags { name value } block { timestamp } } } pageInfo { hasNextPage } } } `; const variables = { tags: formattedTags, cursor: cursor || "", ...owner && { owner }, ...recipientValue && { recipients: [recipientValue] } }; const response = await gql.run(query, variables); if (((_b = (_a = response.errors) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) === "internal server error") { throw new Error("GraphQL endpoint internal server error."); } return response.data.transactions; } catch (error) { throw new Error(`Failed to retrieve Arweave GraphQL data: ${error}`); } } // src/ao/messaging/validationUtils.ts async function retryOperation(operation, maxRetries = 10, retryInterval = 600) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { if (attempt === maxRetries) return "pending"; await new Promise((resolve) => setTimeout(resolve, retryInterval)); } } return "pending"; } async function validateTransaction(aoUtils, transferID, targetProcessID, config, maxRetries = 10, retryInterval = 600) { const result = await retryOperation( async () => { const { Messages } = await aoUtils.result({ message: transferID, process: targetProcessID }); if (!Array.isArray(Messages) || Messages.length !== config.expectedTxCount) { throw new Error("Invalid transaction response"); } const hasRequiredActions = config.requiredNotices.every( (notice) => Messages.some( (msg) => { var _a; return (_a = msg.Tags) == null ? void 0 : _a.some( (tag) => tag.name === "Action" && tag.value === notice ); } ) ); if (hasRequiredActions) return true; return false; }, maxRetries, retryInterval ); return result === "pending" ? "pending" : !!result; } async function findTransactionIds(aoUtils, transferID, processId, maxRetries = 10, retryInterval = 6e3) { const result = await retryOperation( async () => { const transactionsFound = await getTags({ aoUtils, tags: [ { name: "Pushed-For", values: transferID }, { name: "From-Process", values: processId } ], cursor: "" }); if (!(transactionsFound == null ? void 0 : transactionsFound.edges) || transactionsFound.edges.length !== 2) { throw new Error("No transactions found or invalid count"); } const debitTx = transactionsFound.edges.find( (edge) => edge.node.tags.some( (tag) => tag.name === "Action" && tag.value === "Debit-Notice" ) ); const creditTx = transactionsFound.edges.find( (edge) => edge.node.tags.some( (tag) => tag.name === "Action" && tag.value === "Credit-Notice" ) ); if (!debitTx || !creditTx) { throw new Error( "Missing required Debit-Notice or Credit-Notice transactions" ); } return { debitID: debitTx.node.id, creditID: creditTx.node.id }; }, maxRetries, retryInterval ); if (result === "pending") { throw new Error("Operation timed out"); } return result; } // src/functions/borrow/borrow.ts var BORROW_CONFIG = { action: "Borrow", expectedTxCount: 2, confirmationTag: "Borrow-Confirmation", requiredNotices: ["Transfer", "Borrow-Confirmation"], requiresCreditDebit: true }; async function borrow(aoUtilsInput, { token, quantity, noResult = false }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const { oTokenAddress, tokenAddress } = tokenInput(token); const transferID = await aoUtils.message({ process: oTokenAddress, tags: [ { name: "Action", value: "Borrow" }, { name: "Quantity", value: quantity.toString() }, { name: "Protocol-Name", value: "LiquidOps" }, { name: "Analytics-Tag", value: "Borrow" }, { name: "timestamp", value: JSON.stringify(Date.now()) }, { name: "token", value: tokenAddress } ], signer: aoUtils.signer }); if (noResult) { return transferID; } const transferResult = await validateTransaction( aoUtils, transferID, oTokenAddress, BORROW_CONFIG ); if (transferResult === "pending") { return { status: "pending", transferID, response: "Transaction pending." }; } if (!transferResult) { throw new Error("Transaction validation failed"); } const transferTxnId = await findExtraBorrowTransfer( aoUtils, transferID, oTokenAddress ); if (transferTxnId === "pending") { return { status: "pending", transferID, response: "Transfer transaction pending." }; } const transactionIds = await findTransactionIds( aoUtils, transferID, tokenAddress ); return { status: true, ...transactionIds, transferID }; } catch (error) { throw new Error("Error in borrow function: " + error); } } async function findExtraBorrowTransfer(aoUtils, transferID, oTokenAddress, maxRetries = 10, retryInterval = 6e3) { var _a, _b, _c; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const transferTxns = await getTags({ aoUtils, tags: [ { name: "Pushed-For", values: transferID }, { name: "Action", values: "Transfer" }, { name: "From-Process", values: oTokenAddress } ], cursor: "" }); if (!((_c = (_b = (_a = transferTxns == null ? void 0 : transferTxns.edges) == null ? void 0 : _a[0]) == null ? void 0 : _b.node) == null ? void 0 : _c.id)) { throw new Error("Could not find transfer transaction"); } return transferTxns.edges[0].node.id; } catch (error) { if (attempt === maxRetries) return "pending"; await new Promise((resolve) => setTimeout(resolve, retryInterval)); } } return "pending"; } // src/functions/borrow/repay.ts var REPAY_CONFIG = { action: "Repay", expectedTxCount: 2, confirmationTag: "Repay-Confirmation", requiredNotices: ["Debit-Notice", "Credit-Notice"], requiresCreditDebit: true }; async function repay(aoUtilsInput, { token, quantity, onBehalfOf, noResult = false }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const { tokenAddress, oTokenAddress } = tokenInput(token); const transferID = await aoUtils.message({ process: tokenAddress, tags: [ { name: "Action", value: "Transfer" }, { name: "Quantity", value: quantity.toString() }, { name: "Recipient", value: oTokenAddress }, { name: "X-Action", value: "Repay" }, { name: "Protocol-Name", value: "LiquidOps" }, ...onBehalfOf ? [{ name: "X-On-Behalf", value: onBehalfOf }] : [], { name: "Analytics-Tag", value: "Repay" }, { name: "timestamp", value: JSON.stringify(Date.now()) }, { name: "token", value: tokenAddress } ], signer: aoUtils.signer }); if (noResult) { return transferID; } const transferResult = await validateTransaction( aoUtils, transferID, tokenAddress, REPAY_CONFIG ); if (transferResult === "pending") { return { status: "pending", transferID, response: "Transaction pending." }; } if (!transferResult) { throw new Error("Transaction validation failed"); } const transactionIds = await findTransactionIds( aoUtils, transferID, tokenAddress ); return { status: true, ...transactionIds, transferID }; } catch (error) { throw new Error("Error in repay function: " + error); } } // src/functions/getTransactions/getTransactions.ts async function getTransactions(aoUtilsInput, { token, action, walletAddress, cursor = "" }) { var _a; try { if (!token || !action || !walletAddress) { throw new Error("Please specify a token, action and walletAddress."); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const tags = [{ name: "Protocol-Name", values: ["LiquidOps"] }]; const { oTokenAddress, tokenAddress } = tokenInput(token); if (action === "borrow") { tags.push({ name: "recipient", values: [oTokenAddress] }); tags.push({ name: "Action", values: ["Borrow"] }); tags.push({ name: "Analytics-Tag", values: ["Borrow"] }); } else if (action === "repay") { tags.push({ name: "recipient", values: [tokenAddress] }); tags.push({ name: "Action", values: ["Transfer"] }); tags.push({ name: "Recipient", values: [oTokenAddress] }); tags.push({ name: "X-Action", values: ["Repay"] }); tags.push({ name: "Analytics-Tag", values: ["Repay"] }); } else if (action === "lend") { tags.push({ name: "recipient", values: [tokenAddress] }); tags.push({ name: "Action", values: ["Transfer"] }); tags.push({ name: "Recipient", values: [oTokenAddress] }); tags.push({ name: "X-Action", values: ["Mint"] }); tags.push({ name: "Analytics-Tag", values: ["Lend"] }); } else if (action === "unLend") { tags.push({ name: "recipient", values: [oTokenAddress] }); tags.push({ name: "Action", values: ["Redeem"] }); tags.push({ name: "Analytics-Tag", values: ["UnLend"] }); } else { throw new Error("Please specify an action."); } const queryArweave = await getTags({ aoUtils, tags, owner: walletAddress, cursor }); return { transactions: processTransactions(queryArweave.edges), pageInfo: { hasNextPage: queryArweave.pageInfo.hasNextPage, cursor: (_a = queryArweave.edges[queryArweave.edges.length - 1]) == null ? void 0 : _a.cursor } }; } catch (error) { throw new Error("Error in getTransactions function:" + error); } } function processTransactions(transactions) { return transactions.map(({ node }) => { var _a, _b; const processedTransaction = { id: node.id, tags: {}, block: { timestamp: (_b = (_a = node == null ? void 0 : node.block) == null ? void 0 : _a.timestamp) != null ? _b : 0 } }; node.tags.forEach((tag) => { processedTransaction.tags[tag.name] = tag.value; }); return processedTransaction; }); } // src/functions/lend/lend.ts var LEND_CONFIG = { action: "Mint", expectedTxCount: 2, confirmationTag: "Mint-Confirmation", requiredNotices: ["Debit-Notice", "Credit-Notice"], requiresCreditDebit: true }; async function lend(aoUtilsInput, { token, quantity, noResult = false }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const { tokenAddress, oTokenAddress } = tokenInput(token); const transferID = await aoUtils.message({ process: tokenAddress, tags: [ { name: "Action", value: "Transfer" }, { name: "Quantity", value: quantity.toString() }, { name: "Recipient", value: oTokenAddress }, { name: "X-Action", value: "Mint" }, { name: "Protocol-Name", value: "LiquidOps" }, { name: "Analytics-Tag", value: "Lend" }, { name: "timestamp", value: JSON.stringify(Date.now()) }, { name: "token", value: tokenAddress } ], signer: aoUtils.signer }); if (noResult) { return transferID; } const transferResult = await validateTransaction( aoUtils, transferID, tokenAddress, LEND_CONFIG ); if (transferResult === "pending") { return { status: "pending", transferID, response: "Transaction pending." }; } if (!transferResult) { throw new Error("Transaction validation failed"); } const transactionIds = await findTransactionIds( aoUtils, transferID, tokenAddress ); return { status: true, ...transactionIds, transferID }; } catch (error) { throw new Error("Error in lend function: " + error); } } // src/functions/lend/unLend.ts var UNLEND_CONFIG = { action: "Redeem", expectedTxCount: 1, confirmationTag: "Redeem-Confirmation", requiredNotices: ["Redeem-Confirmation"], requiresCreditDebit: false }; async function unLend(aoUtilsInput, { token, quantity, noResult = false }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const { oTokenAddress, tokenAddress } = tokenInput(token); const transferID = await aoUtils.message({ process: oTokenAddress, tags: [ { name: "Action", value: "Redeem" }, { name: "Quantity", value: quantity.toString() }, { name: "Protocol-Name", value: "LiquidOps" }, { name: "Analytics-Tag", value: "UnLend" }, { name: "Analytics-Tag", value: "UnLend" }, { name: "timestamp", value: JSON.stringify(Date.now()) }, { name: "token", value: tokenAddress } ], signer: aoUtils.signer }); if (noResult) { return transferID; } const transferResult = await validateTransaction( aoUtils, transferID, oTokenAddress, UNLEND_CONFIG ); if (transferResult === "pending") { return { status: "pending", transferID, response: "Transaction pending." }; } return { status: true, transferID }; } catch (error) { throw new Error("Error in unLend function: " + error); } } // src/functions/liquidations/getDiscountedQuantity.ts function getTokenValue(from, to, prices) { const fromData = { price: prices[convertTicker(from.token)].v, scale: BigInt(10) ** tokenData[from.token].denomination }; const fromScaledPrice = BigInt( Math.round(fromData.price * Number(fromData.scale)) ); const toData = { price: prices[convertTicker(to)].v, scale: BigInt(10) ** tokenData[to].denomination }; const toScaledPrice = BigInt(Math.round(toData.price * Number(toData.scale))); const fromValUSD = from.quantity * fromScaledPrice / fromData.scale; return fromValUSD * toData.scale / toScaledPrice; } function getDiscountedQuantity({ liquidated, rewardToken, qualifyingPosition, priceData, validateMax = false }, precisionFactor) { if (!Number.isInteger(precisionFactor)) { throw new Error("The precision factor has to be an integer"); } const dept = qualifyingPosition.debts.find( (d) => d.ticker === liquidated.token ); const reward = qualifyingPosition.collaterals.find( (c) => c.ticker === rewardToken ); if (!dept || !reward) { throw new Error( "Liquidated token or reward token is not in the user's position" ); } if (validateMax && dept.quantity < liquidated.quantity) { throw new Error("Not enough tokens to liquidate"); } let marketValue = getTokenValue(liquidated, rewardToken, priceData); if (qualifyingPosition.discount > BigInt(0)) { const precise100 = BigInt(100 * precisionFactor); marketValue = marketValue * (precise100 + qualifyingPosition.discount) / precise100; } if (validateMax && reward.quantity < marketValue) { throw new Error("Not enough tokens to receive as reward"); } return marketValue; } // src/ao/messaging/getData.ts var import_aoconnect = require("@permaweb/aoconnect"); async function getData(messageTags) { const convertedMessageTags = Object.entries(messageTags).map(([name, value]) => ({ name, value })).filter((t) => t.name !== "Owner"); convertedMessageTags.push({ name: "Protocol-Name", value: "LiquidOps" }); const targetProcessID = messageTags["Target"]; try { const { Messages, Spawns, Output, Error: Error2 } = await (0, import_aoconnect.dryrun)({ process: targetProcessID, data: "", tags: convertedMessageTags, Owner: messageTags.Owner || "1234" }); return { Messages, Spawns, Output, Error: Error2 }; } catch (error) { throw new Error(`Error sending ao dryrun: ${error}`); } } // src/functions/protocolData/getAllPositions.ts async function getAllPositions({ token }) { try { if (!token) { throw new Error("Please specify a token."); } const { oTokenAddress } = tokenInput(token); const res = await getData({ Target: oTokenAddress, Action: "Positions" }); const allPositions = JSON.parse(res.Messages[0].Data); const transformedPositions = {}; for (const walletAddress in allPositions) { const originalPosition = allPositions[walletAddress]; transformedPositions[walletAddress] = { borrowBalance: BigInt(originalPosition["Borrow-Balance"]), capacity: BigInt(originalPosition.Capacity), collateralization: BigInt(originalPosition["Collateralization"]), liquidationLimit: BigInt(originalPosition["Liquidation-Limit"]) }; } return transformedPositions; } catch (error) { throw new Error(`Error in getAllPositions function: ${error}`); } } // src/ao/utils/dryRunAwait.ts async function dryRunAwait(seconds) { const miliseconds = seconds * 1e3; await new Promise((resolve) => setTimeout(resolve, miliseconds)); } // src/ao/sharedLogic/globalPositionUtils.ts function calculateGlobalPositions({ positions, prices }) { const globalPositions = /* @__PURE__ */ new Map(); let highestDenomination = BigInt(0); for (const [walletAddress, walletPositions] of Object.entries(positions)) { for (const [token, position] of Object.entries(walletPositions)) { if (!position) continue; const tokenDenomination = tokenData[token].denomination; if (highestDenomination < tokenDenomination) { highestDenomination = tokenDenomination; } } } for (const [walletAddress, walletPositions] of Object.entries(positions)) { const globalPosition = { borrowBalanceUSD: BigInt(0), capacityUSD: BigInt(0), collateralizationUSD: BigInt(0), liquidationLimitUSD: BigInt(0), usdDenomination: highestDenomination, tokenPositions: {} }; for (const [token, position] of Object.entries(walletPositions)) { if (!position) continue; const tokenPosition = { borrowBalance: BigInt(position.borrowBalance), capacity: BigInt(position.capacity), collateralization: BigInt(position.collateralization), liquidationLimit: BigInt(position.liquidationLimit), ticker: token }; globalPosition.tokenPositions[token] = tokenPosition; const tokenPrice = prices[convertTicker(token)].v; const tokenDenomination = tokenData[token].denomination; const scale = BigInt(10) ** highestDenomination; const priceScaled = BigInt(Math.round(tokenPrice * Number(scale))); const scaleDifference = BigInt(10) ** (highestDenomination - tokenDenomination); const borrowBalanceUSD = tokenPosition.borrowBalance * scaleDifference * priceScaled / scale; const capacityUSD = tokenPosition.capacity * scaleDifference * priceScaled / scale; const collateralizationUSD = tokenPosition.collateralization * scaleDifference * priceScaled / scale; const liquidationLimitUSD = tokenPosition.liquidationLimit * scaleDifference * priceScaled / scale; globalPosition.borrowBalanceUSD += borrowBalanceUSD; globalPosition.capacityUSD += capacityUSD; globalPosition.collateralizationUSD += collateralizationUSD; globalPosition.liquidationLimitUSD += liquidationLimitUSD; } globalPositions.set(walletAddress, globalPosition); } return { globalPositions, highestDenomination }; } // src/functions/liquidations/getLiquidations.ts async function getLiquidations(precisionFactor) { try { if (!Number.isInteger(precisionFactor)) { throw new Error("The precision factor has to be an integer"); } const tokensList = Object.keys(tokens); const redstonePriceFeedRes = await getData({ Target: redstoneOracleAddress, Action: "v2.Request-Latest-Data", Tickers: JSON.stringify(collateralEnabledTickers.map(convertTicker)) }); await dryRunAwait(1); const positionsList = []; for (const token of tokensList) { const maxRetries = 3; const retryDelay = 3e3; let positions = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { positions = await getAllPositions({ token }); await dryRunAwait(1); break; } catch (error) { console.log( `Attempt ${attempt} failed for getAllPositions with token ${token}:`, error ); if (attempt === maxRetries) { throw new Error( `Failed to get all positions for token ${token} after ${maxRetries} attempts: ${error}` ); } await new Promise((resolve) => setTimeout(resolve, retryDelay)); } } if (!positions) { throw new Error(`Unexpected: positions is null for token ${token}`); } positionsList.push({ token, positions }); } const auctionsRes = await getData({ Target: controllerAddress, Action: "Get-Auctions" }); await dryRunAwait(1); const prices = JSON.parse( redstonePriceFeedRes.Messages[0].Data ); const auctions = JSON.parse( auctionsRes.Messages[0].Data ); const auctionTags = Object.fromEntries( auctionsRes.Messages[0].Tags.map((tag) => [tag.name, tag.value]) ); const maxDiscount = parseFloat(auctionTags["Initial-Discount"]); const discountInterval = parseInt(auctionTags["Discount-Interval"]); const allPositions = {}; for (const { token, positions: localPositions } of positionsList) { for (const [walletAddress, position] of Object.entries(localPositions)) { if (!allPositions[walletAddress]) { allPositions[walletAddress] = {}; } allPositions[walletAddress][token] = { borrowBalance: position.borrowBalance, capacity: position.capacity, collateralization: position.collateralization, liquidationLimit: position.liquidationLimit, ticker: token }; } } const { globalPositions, highestDenomination } = calculateGlobalPositions({ positions: allPositions, prices }); const res = /* @__PURE__ */ new Map(); for (const [walletAddress, position] of globalPositions) { if (position.borrowBalanceUSD <= position.liquidationLimitUSD) continue; const currentTime = Date.now(); let timeSinceDiscovery = currentTime - (auctions[walletAddress] || currentTime); if (timeSinceDiscovery > discountInterval) { timeSinceDiscovery = discountInterval; } const discount = BigInt( Math.max( Math.floor( (discountInterval - timeSinceDiscovery) * maxDiscount * precisionFactor / discountInterval ), 0 ) ); const qualifyingPos = { debts: [], collaterals: [], discount }; for (const [token, localPosition] of Object.entries( position.tokenPositions )) { if (localPosition.borrowBalance > BigInt(0)) { qualifyingPos.debts.push({ ticker: token, quantity: localPosition.borrowBalance }); } if (localPosition.collateralization > BigInt(0)) { qualifyingPos.collaterals.push({ ticker: token, quantity: localPosition.collateralization }); } } res.set(walletAddress, qualifyingPos); } return { liquidations: res, usdDenomination: highestDenomination, prices }; } catch (error) { throw new Error(`Error in getLiquidations function: ${error}`); } } // src/functions/liquidations/liquidate.ts var LIQUIDATE_CONFIG = { action: "Liquidate", expectedTxCount: 2, confirmationTag: "Liquidate-Confirmation", requiredNotices: ["Debit-Notice", "Credit-Notice"], requiresCreditDebit: true }; async function liquidate(aoUtilsInput, { token, rewardToken, targetUserAddress, quantity, minExpectedQuantity, noResult = false }) { try { if (!token || !rewardToken || !targetUserAddress || !quantity || !minExpectedQuantity) { throw new Error( "Please specify token, reward token, target address, quantity and minExpectedQuantity." ); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const { tokenAddress: repayTokenAddress, controllerAddress: controllerAddress2 } = tokenInput(token); const { tokenAddress: rewardTokenAddress } = tokenInput(rewardToken); const transferID = await aoUtils.message({ process: repayTokenAddress, tags: [ { name: "Action", value: "Transfer" }, { name: "Quantity", value: quantity.toString() }, { name: "Recipient", value: controllerAddress2 }, { name: "X-Target", value: targetUserAddress }, { name: "X-Reward-Token", value: rewardTokenAddress }, { name: "X-Action", value: "Liquidate" }, ...minExpectedQuantity && [ { name: "X-Min-Expected-Quantity", value: minExpectedQuantity.toString() } ] || [], { name: "Protocol-Name", value: "LiquidOps" } ], signer: aoUtils.signer }); if (noResult) { return transferID; } const transferResult = await validateTransaction( aoUtils, transferID, repayTokenAddress, LIQUIDATE_CONFIG ); if (transferResult === "pending") { return { status: "pending", transferID, response: "Transaction pending." }; } if (!transferResult) { throw new Error("Transaction validation failed"); } const transactionIds = await findTransactionIds( aoUtils, transferID, repayTokenAddress ); return { status: true, ...transactionIds, transferID }; } catch (error) { throw new Error("Error in liquidate function: " + error); } } // src/functions/oTokenData/getBalances.ts async function getBalances({ token }) { try { if (!token) { throw new Error("Please specify a token."); } const { oTokenAddress } = tokenInput(token); const res = await getData({ Target: oTokenAddress, Action: "Balances" }); if (!res.Messages || !res.Messages[0] || !res.Messages[0].Data) { throw new Error("Invalid response format from getData"); } const balances = JSON.parse(res.Messages[0].Data); const result = {}; for (const key in balances) { if (Object.prototype.hasOwnProperty.call(balances, key)) { result[key] = BigInt(balances[key]); } } return result; } catch (error) { throw new Error(`Error in getBalances function: ${error}`); } } // src/functions/oTokenData/getBorrowAPR.ts async function getBorrowAPR({ token }) { try { if (!token) { throw new Error("Please specify a token."); } const { oTokenAddress } = tokenInput(token); const checkDataRes = await getData({ Target: oTokenAddress, Action: "Get-APR" }); const tags = checkDataRes.Messages[0].Tags; const aprResponse = { "Annual-Percentage-Rate": "", "Rate-Multiplier": "" }; tags.forEach((tag) => { if (tag.name === "Annual-Percentage-Rate" || tag.name === "Rate-Multiplier") { aprResponse[tag.name] = tag.value; } }); const apr = parseFloat(aprResponse["Annual-Percentage-Rate"]); const rateMultiplier = parseFloat(aprResponse["Rate-Multiplier"]); return apr / rateMultiplier; } catch (error) { throw new Error("Error in getBorrowAPR function: " + error); } } // src/functions/oTokenData/getExchangeRate.ts async function getExchangeRate({ token, quantity }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } const { oTokenAddress } = tokenInput(token); const message = await getData({ Target: oTokenAddress, Action: "Exchange-Rate-Current", ...quantity && { Quantity: quantity.toString() } }); const valueTag = message.Messages[0].Tags.find( (tag) => tag.name === "Value" ); return BigInt(valueTag.value); } catch (error) { throw new Error("Error in getExchangeRate function: " + error); } } // src/functions/oTokenData/getPosition.ts async function getPosition({ token, recipient }) { try { if (!token || !recipient) { throw new Error("Please specify a token and recipient."); } const { oTokenAddress } = tokenInput(token); const res = await getData({ Target: oTokenAddress, Action: "Position", ...recipient && { Recipient: recipient } }); const tagsObject = Object.fromEntries( res.Messages[0].Tags.map((tag) => [tag.name, tag.value]) ); return { capacity: tagsObject["Capacity"], borrowBalance: tagsObject["Borrow-Balance"], collateralTicker: convertTicker(tagsObject["Collateral-Ticker"]), collateralDenomination: tagsObject["Collateral-Denomination"], collateralization: tagsObject["Collateralization"], liquidationLimit: tagsObject["Liquidation-Limit"] }; } catch (error) { throw new Error("Error in getPosition function: " + error); } } // src/functions/oTokenData/getGlobalPosition.ts async function getGlobalPosition({ walletAddress }) { try { if (!walletAddress) { throw new Error("Please specify a wallet address."); } const tokensList = Object.keys(tokens); const redstonePriceFeedRes = await getData({ Target: redstoneOracleAddress, Action: "v2.Request-Latest-Data", Tickers: JSON.stringify(collateralEnabledTickers.map(convertTicker)) }); await dryRunAwait(1); const prices = JSON.parse( redstonePriceFeedRes.Messages[0].Data ); const positionsPromises = tokensList.map(async (token) => { const maxRetries = 3; const retryDelay = 5e3; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const position = await getPosition({ token, recipient: walletAddress }); await dryRunAwait(1); return { token, position }; } catch (error) { console.log(`Attempt ${attempt} failed for token ${token}:`, error); if (attempt === maxRetries) { throw new Error( `Failed to get position for token ${token} after ${maxRetries} attempts: ${error}` ); } await new Promise((resolve) => setTimeout(resolve, retryDelay)); } } return { token, position: null }; }); const positionsResults = await Promise.all(positionsPromises); const positions = { [walletAddress]: {} }; for (const { token, position } of positionsResults) { if (position) { const tokenPosition = { borrowBalance: BigInt(position.borrowBalance), capacity: BigInt(position.capacity), collateralization: BigInt(position.collateralization), liquidationLimit: BigInt(position.liquidationLimit), ticker: token }; positions[walletAddress][token] = tokenPosition; } } const { globalPositions } = calculateGlobalPositions({ positions, prices }); const globalPosition = globalPositions.get(walletAddress); return { globalPosition, prices }; } catch (error) { throw new Error(`Error in getGlobalPosition function: ${error}`); } } // src/functions/oTokenData/getInfo.ts async function getInfo({ token }) { try { if (!token) { throw new Error("Please specify a token."); } const { oTokenAddress } = tokenInput(token); const res = await getData({ Target: oTokenAddress, Action: "Info" }); const tagsObject = Object.fromEntries( res.Messages[0].Tags.map((tag) => [ (tag.name[0].toLowerCase() + tag.name.slice(1)).replace(/-/g, ""), tag.value ]) ); return tagsObject; } catch (error) { throw new Error("Error in getInfo function: " + error); } } // src/functions/oTokenData/getSupplyAPR.ts var import_ao_tokens = require("ao-tokens"); async function getSupplyAPR({ token, getInfoRes, getBorrowAPRRes }) { try { if (!token) { throw new Error("Please specify a token."); } if (!getBorrowAPRRes) { getBorrowAPRRes = await getBorrowAPR({ token }); await dryRunAwait(1); } const borrowAPY = getBorrowAPRRes; const { tokenAddress } = tokenInput(token); if (getInfoRes && getInfoRes.collateralId !== tokenAddress) { throw new Error("getInfoRes supplied does not match token supplied."); } if (!getInfoRes) { getInfoRes = await getInfo({ token }); } const { totalBorrows, collateralDenomination, reserveFactor, totalSupply } = getInfoRes; const scaledCollateralDenomination = BigInt(collateralDenomination); const scaledTotalBorrows = new import_ao_tokens.Quantity( totalBorrows, scaledCollateralDenomination ); const scaledTotalSupply = new import_ao_tokens.Quantity( totalSupply, scaledCollateralDenomination ); const utilizationRate = import_ao_tokens.Quantity.__div( scaledTotalBorrows, scaledTotalSupply ).toNumber(); const reserveFactorFract = Number(reserveFactor) / 100; return borrowAPY * utilizationRate * (1 - reserveFactorFract); } catch (error) { throw new Error("Error in getSupplyAPR function: " + error); } } // src/functions/oTokenData/getCooldown.ts async function getCooldown({ recipient, token }) { var _a, _b; if (!recipient) throw new Error("Please specify a recipient"); if (!token) throw new Error("Please specify a token address"); const { oTokenAddress } = tokenInput(token); const cooldownRes = await getData({ Target: oTokenAddress, Owner: recipient, Action: "Is-Cooldown" }); if (!((_b = (_a = cooldownRes == null ? void 0 : cooldownRes.Messages) == null ? void 0 : _a[0]) == null ? void 0 : _b.Tags)) { return { onCooldown: false }; } const cooldownResTags = Object.fromEntries( cooldownRes.Messages[0].Tags.map((tag) => [ tag.name, tag.value ]) ); if (!cooldownResTags["Is-Cooldown"]) { return { onCooldown: false }; } const expiresOn = parseInt(cooldownResTags["Cooldown-Expires"]); return { onCooldown: true, expiryBlock: expiresOn, remainingBlocks: expiresOn - parseInt(cooldownResTags["Request-Block-Height"]) }; } // src/functions/protocolData/getHistoricalAPR.ts async function getHistoricalAPR({ token, fillGaps = true }) { var _a, _b; try { if (!token) { throw new Error("Please specify a token."); } const { oTokenAddress } = tokenInput(token); const response = await getData({ Target: APRAgentAddress, Action: "Get-Data", Token: oTokenAddress, "Fill-Gaps": fillGaps.toString() }); if (!((_b = (_a = response.Messages) == null ? void 0 : _a[0]) == null ? void 0 : _b.Data)) { const errorTag = response.Messages[0].Tags.find( (tag) => tag.name === "Error" ); if (errorTag.value === "No data about this market") { return [ { apr: 0, timestamp: Date.now() } ]; } else { throw new Error("No historical APR data received"); } } return JSON.parse(response.Messages[0].Data); } catch (error) { throw new Error("Error in getHistoricalAPR function: " + error); } } // src/functions/utils/getBalance.ts var import_ao_tokens2 = require("ao-tokens"); async function getBalance({ tokenAddress, walletAddress }) { if (!tokenAddress || !walletAddress) { throw new Error("Please specify a tokenAddress and walletAddress."); } try { const tokenInstance = await (0, import_ao_tokens2.Token)(tokenAddress); const balance = await tokenInstance.getBalance(walletAddress); return new import_ao_tokens2.Quantity(balance.raw, tokenInstance.info.Denomination); } catch (error) { throw new Error("Error getting balance: " + error); } } // src/functions/utils/getResult.ts async function getResult(aoUtilsInput, { transferID, tokenAddress, action }) { try { const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; let CONFIG; if (action === "lend") { CONFIG = LEND_CONFIG; } else if (action === "unLend") { CONFIG = UNLEND_CONFIG; } else if (action === "borrow") { CONFIG = BORROW_CONFIG; } else { CONFIG = REPAY_CONFIG; } const txResult = validateTransaction( aoUtils, transferID, tokenAddress, CONFIG ); return txResult; } catch (error) { throw new Error("Error in getResult function: " + error); } } // src/ao/messaging/sendData.ts async function sendData(aoUtils, messageTags) { const convertedMessageTags = Object.entries(messageTags).map( ([name, value]) => ({ name, value }) ); convertedMessageTags.push({ name: "Protocol-Name", value: "LiquidOps" }); const targetProcessID = messageTags["Target"]; let messageID; try { messageID = await aoUtils.message({ process: targetProcessID, tags: convertedMessageTags, signer: aoUtils.signer }); } catch (error) { throw new Error(`Error sending ao message: ${error}`); } try { const { Messages, Spawns, Output, Error: Error2 } = await aoUtils.result({ message: messageID, process: targetProcessID }); return { Messages, Spawns, Output, Error: Error2, initialMessageID: messageID }; } catch (error) { throw new Error(`Error reading ao message result: ${error}`); } } // src/functions/utils/transfer.ts async function transfer(aoUtilsInput, { token, recipient, quantity }) { var _a; try { if (!token || !recipient || !quantity) { throw new Error("Please specify a token, recipient and quantity."); } const { spawn, message, result } = await connectToAO(aoUtilsInput.configs); const aoUtils = { spawn, message, result, signer: aoUtilsInput.signer, configs: aoUtilsInput.configs }; const { tokenAddress } = tokenInput(token); const res = await sendData(aoUtils, { Target: tokenAddress, Action: "Transfer", Recipient: recipient, Quantity: quantity.toString() }); const hasDebitNotice = (_a = res.Messages[0]) == null ? void 0 : _a.Tags.some( (tag) => tag.name === "Action" && tag.value === "Debit-Notice" ); return { id: res.initialMessageID, status: hasDebitNotice }; } catch (error) { throw new Error("Error in transfer function: " + error); } } // src/functions/utils/trackResult.ts var SU_ROUTER = "https://su-router.ao-testnet.xyz"; async funct