UNPKG

liquidops-test-liquidations

Version:

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

1,397 lines (1,368 loc) 42.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { controllerAddress: () => controllerAddress, default: () => src_default, oTokens: () => oTokens, tokenData: () => tokenData, tokenInput: () => tokenInput, tokens: () => tokens }); module.exports = __toCommonJS(src_exports); // src/ao/utils/tokenAddressData.ts var controllerAddress = "4IDY80UNvSLDp9wNlVHHNw8CMuPnA1UJfX1-kR5B4yU"; var redstoneOracleAddress = "dCPZ4dEitEXQB2p8wS1V2VIkQ9BQFKEgMm4El-3NGhw"; var APRAgentAddress = "D3AlSUAtbWKcozsrvckRuCY6TVkAY1rWtLYGoGf6KIA"; var tokenData = { QAR: { name: "Quantum Arweave", icon: "8VLMb0c9NATl4iczfwpMDe1Eh8kFWIUpSlIkcGfDFzM", ticker: "QAR", address: "rjYl6i4cDpE4c-OIJ7srTrcNulrf8Xw4Y8pDZDBAOUs", oTicker: "oQAR", oAddress: "oUYf4mzhcRLbuWmLRbdgXYgxyZhZeCt789Fct3t1EwY", controllerAddress, cleanTicker: "qAR", denomination: BigInt(12), collateralEnabled: true }, USDC: { name: "USD Circle", icon: "iNYk0bDqUiH0eLT2rbYjYAI5i126R4ye8iAZb55IaIM", ticker: "USDC", address: "zFEDdM1uAW1n3dwgzLUTO0GGFbCMdEXfDQjNc3Gbong", oTicker: "oUSDC", oAddress: "wBmXOpYi_Dj7XTtrkuR_fX_tm4FGBOUpHuj48Sbp_jY", controllerAddress, cleanTicker: "USDC", denomination: BigInt(12), collateralEnabled: true } }; 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(aoUtils, { token, quantity }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } 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 }); 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(aoUtils, { token, quantity, onBehalfOf }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } 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 }); 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(aoUtils, { token, action, walletAddress, cursor = "" }) { var _a; try { if (!token || !action || !walletAddress) { throw new Error("Please specify a token, action and walletAddress."); } 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(aoUtils, { token, quantity }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } 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 }); 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(aoUtils, { token, quantity }) { try { if (!token || !quantity) { throw new Error("Please specify a token and quantity."); } 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 }); 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[from.token === "QAR" ? "AR" : from.token].v, scale: BigInt(10) ** tokenData[from.token].denomination }; const fromScaledPrice = BigInt( Math.round(fromData.price * Number(fromData.scale)) ); const toData = { price: prices[to === "QAR" ? "AR" : 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/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, positionsList, auctionsRes] = [ { Messages: [{ Data: JSON.stringify({ "AR": { "a": "0x0000000000000000000000000000000000000000", "v": 2.1, "t": 1742900720937 }, "USDC": { "a": "0x0000000000000000000000000000000000000000", "v": 1, "t": 1742900720937 } }) }] }, [ { token: "QAR", positions: { "-CZI5_c-SA_0gnwyXxE9zJyBvEvy5gxEoTwtxLID9UE": { "collateralization": BigInt("20000007272774"), "capacity": BigInt("10000003636387"), "borrowBalance": BigInt("0"), "liquidationLimit": BigInt("10200003709114") }, "ljvCPN31XCLPkBo9FUeB7vAK0VC6-eY52-CS-6Iho8U": { "collateralization": BigInt("0"), "capacity": BigInt("0"), "borrowBalance": BigInt("2500007272774"), "liquidationLimit": BigInt("0") } } }, { token: "USDC", positions: { "ljvCPN31XCLPkBo9FUeB7vAK0VC6-eY52-CS-6Iho8U": { "borrowBalance": BigInt("0"), "collateralization": BigInt("10000000000000"), "liquidationLimit": BigInt("5100000000000"), "capacity": BigInt("5000000000000") } } } ], { Messages: [{ Tags: [ { name: "Initial-Discount", value: "5" }, { name: "Discount-Interval", value: (1e3 * 60 * 60).toString() } ], Data: JSON.stringify({ "ljvCPN31XCLPkBo9FUeB7vAK0VC6-eY52-CS-6Iho8U": Date.now() - 1e3 * 60 * 0 }) }] } ]; 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"] || "0"); const discountInterval = parseInt(auctionTags["Discount-Interval"] || "0"); const globalPositions = /* @__PURE__ */ new Map(); for (const { token, positions: localPositions } of positionsList) { const tokenPrice = prices[token === "QAR" ? "AR" : token].v; const tokenDenomination = tokenData[token].denomination; const scale = BigInt(10) ** tokenDenomination; const priceScaled = BigInt(Math.round(tokenPrice * Number(scale))); for (const [walletAddress, position] of Object.entries( localPositions )) { const posValueUSD = { borrowBalanceUSD: position.borrowBalance * priceScaled / scale, capacityUSD: position.capacity * priceScaled / scale, collateralizationUSD: position.collateralization * priceScaled / scale, liquidationLimitUSD: position.liquidationLimit * priceScaled / scale }; if (!globalPositions.has(walletAddress)) { globalPositions.set(walletAddress, { ...posValueUSD, tokenPositions: { [token]: position } }); } else { const globalPos = globalPositions.get(walletAddress); globalPos.borrowBalanceUSD += posValueUSD.borrowBalanceUSD; globalPos.capacityUSD += posValueUSD.capacityUSD; globalPos.collateralizationUSD += posValueUSD.collateralizationUSD; globalPos.liquidationLimitUSD += posValueUSD.liquidationLimitUSD; globalPos.tokenPositions[token] = position; } } } 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, 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(aoUtils, { token, rewardToken, targetUserAddress, quantity, minExpectedQuantity }) { try { if (!token || !rewardToken || !targetUserAddress || !quantity) { throw new Error( "Please specify token, reward token, target address, and quantity." ); } const { tokenAddress: repayTokenAddress, controllerAddress: controllerAddress3 } = 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: controllerAddress3 }, { name: "X-Target", value: targetUserAddress }, { name: "X-Reward-Token", value: rewardTokenAddress }, ...minExpectedQuantity && [ { name: "X-Min-Expected-Quantity", value: minExpectedQuantity.toString() } ] || [], { name: "Protocol-Name", value: "LiquidOps" } ], signer: aoUtils.signer }); 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/ao/messaging/getData.ts var import_aoconnect = require("@permaweb/aoconnect"); async function getData(messageTags) { const convertedMessageTags = Object.entries(messageTags).map( ([name, value]) => ({ name, value }) ); 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 }); return { Messages, Spawns, Output, Error: Error2 }; } catch (error) { throw new Error(`Error sending ao dryrun: ${error}`); } } // src/functions/oTokenData/getAPR.ts async function getAPR({ 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 getAPR function: " + error); } } // src/functions/utils/getBalance.ts var import_ao_tokens = 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_tokens.Token)(tokenAddress); const balance = await tokenInstance.getBalance(walletAddress); return new import_ao_tokens.Quantity(balance.raw, tokenInstance.info.Denomination); } catch (error) { throw new Error("Error getting balance: " + 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: tagsObject["Collateral-Ticker"] === "AR" ? "qAR" : 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( (ticker) => ticker === "QAR" ? "AR" : ticker ) ) }); const prices = JSON.parse( redstonePriceFeedRes.Messages[0].Data ); const positionsPromises = tokensList.map(async (token) => { try { const position = await getPosition({ token, recipient: walletAddress }); return { token, position }; } catch (error) { return { token, position: null }; } }); const positionsResults = await Promise.all(positionsPromises); const globalPosition = { borrowBalanceUSD: BigInt(0), capacityUSD: BigInt(0), collateralizationUSD: BigInt(0), liquidationLimitUSD: BigInt(0), tokenPositions: {} }; for (const { token, position } of positionsResults) { if (!position) continue; const tokenPosition = { borrowBalance: BigInt(position.borrowBalance || 0), capacity: BigInt(position.capacity || 0), collateralization: BigInt(position.collateralization || 0), liquidationLimit: BigInt(position.liquidationLimit || 0), ticker: token }; globalPosition.tokenPositions[token] = tokenPosition; const tokenPrice = prices[token === "QAR" ? "AR" : token].v; const tokenDenomination = tokenData[token].denomination; const scale = BigInt(10) ** BigInt(tokenDenomination); const priceScaled = BigInt(Math.round(tokenPrice * Number(scale))); const borrowBalanceUSD = tokenPosition.borrowBalance * priceScaled / scale; const capacityUSD = tokenPosition.capacity * priceScaled / scale; const collateralizationUSD = tokenPosition.collateralization * priceScaled / scale; const liquidationLimitUSD = tokenPosition.liquidationLimit * priceScaled / scale; globalPosition.borrowBalanceUSD += borrowBalanceUSD; globalPosition.capacityUSD += capacityUSD; globalPosition.collateralizationUSD += collateralizationUSD; globalPosition.liquidationLimitUSD += liquidationLimitUSD; } 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, tag.value]) ); return { name: tagsObject["Name"], ticker: tagsObject["Ticker"] === "AR" ? "qAR" : tagsObject["Ticker"], logo: tagsObject["Logo"], denomination: tagsObject["Denomination"], cash: tagsObject["Cash"], totalBorrows: tagsObject["Total-Borrows"] }; } catch (error) { throw new Error("Error in getInfo function: " + 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/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/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 balance = JSON.parse(res.Messages[0].Data); const key = Object.keys(balance)[0]; const value = Object.values(balance)[0]; if (!key || !value) { throw new Error("Invalid balance data format"); } return { [key]: BigInt(value) }; } catch (error) { throw new Error(`Error in getBalances function: ${error}`); } } // src/functions/utils/getResult.ts async function getResult(aoUtils, { transferID, tokenAddress, action }) { try { 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 result = validateTransaction( aoUtils, transferID, tokenAddress, CONFIG ); return result; } 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(aoUtils, { token, recipient, quantity }) { var _a; try { if (!token || !recipient || !quantity) { throw new Error("Please specify a token, recipient and quantity."); } 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/ao/utils/connect.ts var import_aoconnect2 = require("@permaweb/aoconnect"); var DEFAULT_SERVICES = { MODE: "legacy", MU_URL: "https://mu.ao-testnet.xyz", CU_URL: "https://cu.ao-testnet.xyz", GATEWAY_URL: "https://arweave.net" }; function connectToAO(services) { 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 } = (0, import_aoconnect2.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 }); return { spawn, message, result }; } // src/index.ts var _LiquidOps = class _LiquidOps { constructor(signer, configs = {}) { if (!signer) { throw new Error("Please specify a ao createDataItemSigner signer"); } const { spawn, message, result } = connectToAO(configs); this.aoUtils = { spawn, message, result, signer, configs }; } // borrow async borrow(params) { return borrow(this.aoUtils, params); } async repay(params) { return repay(this.aoUtils, params); } // getTransactions async getTransactions(params) { return getTransactions(this.aoUtils, params); } // lend async lend(params) { return lend(this.aoUtils, params); } async unLend(params) { return unLend(this.aoUtils, params); } // liquidations getDiscountedQuantity(params) { return getDiscountedQuantity(params, _LiquidOps.liquidationPrecisionFactor); } async getLiquidations() { return getLiquidations(_LiquidOps.liquidationPrecisionFactor); } async liquidate(params) { return liquidate(this.aoUtils, params); } // oTokenData async getAPR(params) { return getAPR(params); } async getBalances(params) { return getBalances(params); } async getExchangeRate(params) { return getExchangeRate(params); } async getGlobalPosition(params) { return getGlobalPosition(params); } async getInfo(params) { return getInfo(params); } async getPosition(params) { return getPosition(params); } // protocol data async getAllPositions(params) { return getAllPositions(params); } async getHistoricalAPR(params) { return getHistoricalAPR(params); } // utils async getBalance(params) { return getBalance(params); } async getResult(params) { return getResult(this.aoUtils, params); } async transfer(params) { return transfer(this.aoUtils, params); } }; _LiquidOps.liquidationPrecisionFactor = 1e6; // process data _LiquidOps.oTokens = oTokens; _LiquidOps.tokens = tokens; var LiquidOps = _LiquidOps; var src_default = LiquidOps; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { controllerAddress, oTokens, tokenData, tokenInput, tokens });