Skip to content
Open
8 changes: 4 additions & 4 deletions src/ao/sharedLogic/globalPositionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ export interface GlobalPosition {
};
}

type RedstonePrices = Record<string, { t: number; a: string; v: number }>;

// Shared utility for calculating global positions
interface CalculateGlobalPositionParams {
positions: Record<string, Record<string, TokenPosition>>; // walletAddress -> token -> position
prices: RedstonePrices;
prices: {
[ticker: string]: number;
};
}

interface CalculateGlobalPositionResult {
Expand Down Expand Up @@ -83,7 +83,7 @@ export function calculateGlobalPositions({
globalPosition.tokenPositions[token] = tokenPosition;

// Get token price and denomination for USD conversion
const tokenPrice = prices[convertTicker(token)].v;
const tokenPrice = prices[convertTicker(token)];
const tokenDenomination =
tokenData[token as SupportedTokensTickers].denomination;

Expand Down
4 changes: 4 additions & 0 deletions src/ao/utils/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type Services = {
GRAPHQL_RETRY_BACKOFF?: number;
MU_URL?: string;
CU_URL?: string;
HB_NODE_URL?: string;
};

export interface AoUtils {
Expand All @@ -30,6 +31,7 @@ const DEFAULT_SERVICES: Services = {
MU_URL: "https://mu.ao-testnet.xyz",
CU_URL: "https://cu.ao-testnet.xyz",
GATEWAY_URL: "https://arweave.net",
HB_NODE_URL: "https://state-2.forward.computer",
};

export function connectToAO(services?: Partial<Services>) {
Expand All @@ -46,6 +48,7 @@ export function connectToAO(services?: Partial<Services>) {
GATEWAY_URL = DEFAULT_SERVICES.GATEWAY_URL,
MU_URL = DEFAULT_SERVICES.MU_URL,
CU_URL = DEFAULT_SERVICES.CU_URL,
HB_NODE_URL = DEFAULT_SERVICES.HB_NODE_URL,
} = services || {};

const configs = {
Expand All @@ -55,6 +58,7 @@ export function connectToAO(services?: Partial<Services>) {
GRAPHQL_RETRY_BACKOFF,
MU_URL,
CU_URL,
HB_NODE_URL,
};

const { spawn, message, result, dryrun } = connect({
Expand Down
4 changes: 2 additions & 2 deletions src/functions/liquidations/getDiscountedQuantity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function getTokenValue(
) {
// token datas
const fromData = {
price: prices[convertTicker(from.token)].v,
price: prices[convertTicker(from.token)],
scale:
BigInt(10) **
tokenData[from.token as SupportedTokensTickers].denomination,
Expand All @@ -24,7 +24,7 @@ function getTokenValue(
Math.round(fromData.price * Number(fromData.scale)),
);
const toData = {
price: prices[convertTicker(to)].v,
price: prices[convertTicker(to)],
scale: BigInt(10) ** tokenData[to as SupportedTokensTickers].denomination,
};
const toScaledPrice = BigInt(Math.round(toData.price * Number(toData.scale)));
Expand Down
70 changes: 26 additions & 44 deletions src/functions/liquidations/getLiquidations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { getData } from "../../ao/messaging/getData";
import {
collateralEnabledTickers,
tokens,
controllerAddress,
} from "../../ao/utils/tokenAddressData";
Expand All @@ -9,13 +7,16 @@ import {
getAllPositions,
GetAllPositionsRes,
} from "../protocolData/getAllPositions";
import { dryRunAwait } from "../../ao/utils/dryRunAwait";
import { convertTicker } from "../../ao/utils/tokenAddressData";
import {
calculateGlobalPositions,
TokenPosition,
} from "../../ao/sharedLogic/globalPositionUtils";
import { Services } from "../../ao/utils/connect";
import Patching from "../utils/patching";

export interface RedstonePrices {
[ticker: string]: number;
};

export interface GetLiquidationsRes {
liquidations: Map<string, QualifyingPosition>;
Expand All @@ -38,11 +39,6 @@ export interface QualifyingPosition {
discount: BigInt;
}

export type RedstonePrices = Record<
string,
{ t: number; a: string; v: number }
>;

interface Tag {
name: string;
value: string;
Expand All @@ -60,20 +56,16 @@ export async function getLiquidations(
// Get list of tokens to process
const tokensList = Object.keys(tokens);

// patching config
const patching = new Patching(config?.HB_NODE_URL);

// Make a request to RedStone oracle process for prices (same used onchain)
const redstonePriceFeedRes = await getData(
{
Owner: controllerAddress,
Target: redstoneOracleAddress,
Action: "v2.Request-Latest-Data",
Tickers: JSON.stringify(collateralEnabledTickers.map(convertTicker)),
},
config,
const prices = await patching.now(
redstoneOracleAddress,
"/price",
{ json: true }
);

// add dry run await to not get rate limited
await dryRunAwait(1);

// Get positions for each token
const positionsList = [];
for (const token of tokensList) {
Expand All @@ -84,8 +76,6 @@ export async function getLiquidations(
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
positions = await getAllPositions({ token }, config);
// add dry run await to not get rate limited
await dryRunAwait(1);
break; // Success, exit retry loop
} catch (error) {
console.log(
Expand Down Expand Up @@ -117,30 +107,22 @@ export async function getLiquidations(
}

// get discovered liquidations
const auctionsRes = await getData(
{
Target: controllerAddress,
Action: "Get-Auctions",
},
config,
);
// add dry run await to not get rate limited
await dryRunAwait(1);

// parse prices and auctions
const prices: RedstonePrices = JSON.parse(
redstonePriceFeedRes.Messages[0].Data,
);
const auctions: Record<string, number> = JSON.parse(
auctionsRes.Messages[0].Data,
);
const [auctions, discountConfig] = await Promise.all([
patching.now(
controllerAddress,
"/auctions",
{ json: true }
),
patching.compute(
controllerAddress,
"/discount-config",
{ json: true }
)
]);

// maximum discount percentage and discount period
const auctionTags = Object.fromEntries(
auctionsRes.Messages[0].Tags.map((tag: Tag) => [tag.name, tag.value]),
);
const maxDiscount = parseFloat(auctionTags["Initial-Discount"]);
const discountInterval = parseInt(auctionTags["Discount-Interval"]);
const maxDiscount = discountConfig.max;
const discountInterval = discountConfig.interval;

// Convert positions to the format expected by calculateGlobalPositions
const allPositions: Record<string, Record<string, TokenPosition>> = {};
Expand Down
25 changes: 8 additions & 17 deletions src/functions/liquidations/getLiquidationsMap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getData } from "../../ao/messaging/getData";
import {
collateralEnabledTickers,
controllerAddress,
Expand All @@ -17,6 +16,7 @@ import {
} from "../../ao/sharedLogic/globalPositionUtils";
import { RedstonePrices } from "./getLiquidations";
import { Services } from "../../ao/utils/connect";
import Patching from "../utils/patching";

export interface GetLiquidationsMapRes {
/** The wallet/user address that owns this position */
Expand All @@ -42,20 +42,16 @@ export async function getLiquidationsMap(
// Get list of tokens to process
const tokensList = Object.keys(tokens);

// setup patching
const patching = new Patching(config?.HB_NODE_URL);

// Make a request to RedStone oracle process for prices (same used onchain)
const redstonePriceFeedRes = await getData(
{
Owner: controllerAddress,
Target: redstoneOracleAddress,
Action: "v2.Request-Latest-Data",
Tickers: JSON.stringify(collateralEnabledTickers.map(convertTicker)),
},
config,
const prices = await patching.now(
redstoneOracleAddress,
"/price",
{ json: true }
);

// add dry run await to not get rate limited
await dryRunAwait(1);

// Get positions for each token
const positionsList = [];
for (const token of tokensList) {
Expand Down Expand Up @@ -98,11 +94,6 @@ export async function getLiquidationsMap(
});
}

// parse prices
const prices: RedstonePrices = JSON.parse(
redstonePriceFeedRes.Messages[0].Data,
);

// Convert positions to the format expected by calculateGlobalPositions
const allPositions: Record<string, Record<string, TokenPosition>> = {};

Expand Down
19 changes: 6 additions & 13 deletions src/functions/oTokenData/getBalances.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getData } from "../../ao/messaging/getData";
import { Services } from "../../ao/utils/connect";
import { TokenInput, tokenInput } from "../../ao/utils/tokenInput";
import Patching from "../utils/patching";

export interface GetBalances {
token: TokenInput;
Expand All @@ -19,20 +19,13 @@ export async function getBalances(

const { oTokenAddress } = tokenInput(token);

const res = await getData(
{
Target: oTokenAddress,
Action: "Balances",
},
config,
const patching = new Patching(config?.HB_NODE_URL);
const balances = await patching.compute(
oTokenAddress,
"/balances",
{ json: true }
);

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: GetBalancesRes = {};

for (const key in balances) {
Expand Down
37 changes: 7 additions & 30 deletions src/functions/oTokenData/getBorrowAPR.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getData } from "../../ao/messaging/getData";
import { Services } from "../../ao/utils/connect";
import { TokenInput, tokenInput } from "../../ao/utils/tokenInput";
import Patching from "../utils/patching";

export interface GetBorrowAPR {
token: TokenInput;
Expand All @@ -17,38 +17,15 @@ export async function getBorrowAPR(
throw new Error("Please specify a token.");
}

const patching = new Patching(config?.HB_NODE_URL);
const { oTokenAddress } = tokenInput(token);

const checkDataRes = await getData(
{
Target: oTokenAddress,
Action: "Get-APR",
},
config,
const res = await patching.now(
oTokenAddress,
"/pool-state/rates",
{ json: true }
);

const tags = checkDataRes.Messages[0].Tags;
const aprResponse: {
"Annual-Percentage-Rate": string;
"Rate-Multiplier": string;
} = {
"Annual-Percentage-Rate": "",
"Rate-Multiplier": "",
};

tags.forEach((tag: { name: string; value: string }) => {
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;
return parseFloat(res.borrow);
} catch (error) {
throw new Error("Error in getBorrowAPR function: " + error);
}
Expand Down
35 changes: 14 additions & 21 deletions src/functions/oTokenData/getCooldown.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getData } from "../../ao/messaging/getData";
import { Services } from "../../ao/utils/connect";
import { tokenInput } from "../../ao/utils/tokenInput";
import Patching from "../utils/patching";

export interface GetCooldown {
recipient: string;
Expand All @@ -23,37 +23,30 @@ export async function getCooldown(
if (!token) throw new Error("Please specify a token address");

const { oTokenAddress } = tokenInput(token);
const patching = new Patching(config?.HB_NODE_URL);

const cooldownRes = await getData(
{
Target: oTokenAddress,
Owner: recipient,
Action: "Is-Cooldown",
},
config,
const cooldownRes = await patching.now(
oTokenAddress,
`/cooldowns/${recipient}`
);

if (!cooldownRes?.Messages?.[0]?.Tags) {
if (!cooldownRes || cooldownRes == "") {
return { onCooldown: false };
}

const cooldownResTags = Object.fromEntries(
cooldownRes.Messages[0].Tags.map((tag: { name: string; value: string }) => [
tag.name,
tag.value,
]),
);
const networkInfo = await (
await fetch(`${config?.GATEWAY_URL || "https://arweave.net"}/info`)
).json();
const currentBlock = networkInfo?.height || 0;
const expiryBlock = parseInt(cooldownRes);

if (!cooldownResTags["Is-Cooldown"]) {
if (expiryBlock <= currentBlock) {
return { onCooldown: false };
}

const expiresOn = parseInt(cooldownResTags["Cooldown-Expires"]);

return {
onCooldown: true,
expiryBlock: expiresOn,
remainingBlocks:
expiresOn - parseInt(cooldownResTags["Request-Block-Height"]),
expiryBlock,
remainingBlocks: expiryBlock - currentBlock,
};
}
Loading
Loading