UNPKG

test-raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

330 lines (294 loc) 10.9 kB
import { Connection, PublicKey } from "@solana/web3.js"; import { seq, struct, u64 } from "@/marshmallow"; export const MODEL_DATA_PUBKEY = new PublicKey("CDSr3ssLcRB6XYPJwAfFt18MZvEZp4LjHcvzBVZ45duo"); const ELEMENT_SIZE = 50000; export const DataElement = struct([u64("x"), u64("y"), u64("price")]); export const modelDataInfoLayout = struct([ u64("accountType"), u64("status"), u64("multiplier"), u64("validDataCount"), seq(DataElement, ELEMENT_SIZE, "DataElement"), ]); export interface StableModelLayout { accountType: number; status: number; multiplier: number; validDataCount: number; DataElement: { x: number; y: number; price: number }[]; } function estimateRangeByXyReal(_xReal: number, _yReal: number): number[] { return [0, ELEMENT_SIZE - 2]; } function estimateRangeByX(_x: number): number[] { return [0, ELEMENT_SIZE - 2]; } function estimateRangeByY(_y: number): number[] { return [0, ELEMENT_SIZE - 2]; } function getMininumRangeByXyReal( layoutData: StableModelLayout, xReal: number, yReal: number, ): [number, number, boolean] { const [min, max] = estimateRangeByXyReal(xReal, yReal); let minRangeIdx = min; let maxRangeIdx = max; let mid = 0; const target = (xReal * layoutData.multiplier) / yReal; while (minRangeIdx <= maxRangeIdx) { mid = Math.floor((maxRangeIdx + minRangeIdx) / 2); if (mid === 0 || mid >= ELEMENT_SIZE - 2) { return [mid, mid, false]; } const cur = (layoutData.DataElement[mid].x * layoutData.multiplier) / layoutData.DataElement[mid].y; const left = (layoutData.DataElement[mid - 1].x * layoutData.multiplier) / layoutData.DataElement[mid - 1].y; const right = (layoutData.DataElement[mid + 1].x * layoutData.multiplier) / layoutData.DataElement[mid + 1].y; if (target === cur) { return [mid, mid, true]; } else if (target === left) { return [mid - 1, mid - 1, true]; } else if (target === right) { return [mid + 1, mid + 1, true]; } else if (target < left) { maxRangeIdx = mid - 1; } else if (target > left && target < cur) { return [mid - 1, mid, true]; } else if (target > cur && target < right) { return [mid, mid + 1, true]; } else { minRangeIdx = mid + 1; } } return [mid, mid, false]; } function getRatio(layoutData: StableModelLayout, xReal: number, yReal: number): number { const [minRangeIdx, maxRangeIdx, find] = getMininumRangeByXyReal(layoutData, xReal, yReal); if (!find) { return 0; } if (minRangeIdx === maxRangeIdx) { const x = layoutData.DataElement[minRangeIdx].x; const ratio = (xReal * layoutData.multiplier) / x; return ratio; } else { const x1 = layoutData.DataElement[minRangeIdx].x; const y1 = layoutData.DataElement[minRangeIdx].y; const x2 = layoutData.DataElement[maxRangeIdx].x; const y2 = layoutData.DataElement[maxRangeIdx].y; const xDenominator = yReal * (x2 * y1 - x1 * y2); const xNumerator1 = x1 * xDenominator; const xNumerator2 = (x2 - x1) * (xReal * y1 - x1 * yReal) * y2; const xNumerator = xNumerator1 + xNumerator2; const ratio = (xReal * layoutData.multiplier * xDenominator) / xNumerator; return ratio; } } function realToTable(layoutData: StableModelLayout, realValue: number, ratio: number): number { return (realValue * layoutData.multiplier) / ratio; } function tableToReal(layoutData: StableModelLayout, tableValue: number, ratio: number): number { return (tableValue * ratio) / layoutData.multiplier; } function getMinimumRangeByX(layoutData: StableModelLayout, x: number): [number, number, boolean] { const [min, max] = estimateRangeByX(x); let minRangeIdx = min; let maxRangeIdx = max; let mid = 0; const target = x; while (minRangeIdx < maxRangeIdx) { mid = Math.floor((maxRangeIdx + minRangeIdx) / 2); if (mid <= 0 || mid > ELEMENT_SIZE - 2) { return [mid, mid, false]; } const cur = layoutData.DataElement[mid].x; const left = layoutData.DataElement[mid - 1].x; const right = layoutData.DataElement[mid + 1].x; if (target === cur) return [mid, mid, true]; else if (target === left) return [mid - 1, mid - 1, true]; else if (target === right) return [mid + 1, mid + 1, true]; else if (target < left) maxRangeIdx = mid - 1; else if (target > left && target < cur) return [mid - 1, mid, true]; else if (target > cur && target < right) return [mid, mid + 1, true]; else minRangeIdx = mid + 1; } return [mid, mid, false]; } function getMinimumRangeByY(layoutData: StableModelLayout, y: number): [number, number, boolean] { const [min, max] = estimateRangeByY(y); let minRangeIdx = min; let maxRangeIdx = max; let mid = 0; const target = y; while (minRangeIdx <= maxRangeIdx) { mid = Math.floor((maxRangeIdx + minRangeIdx) / 2); if (mid <= 0 || mid >= ELEMENT_SIZE - 2) { return [mid, mid, false]; } const cur = layoutData.DataElement[mid].y; const left = layoutData.DataElement[mid - 1].y; const right = layoutData.DataElement[mid + 1].y; if (target === cur) return [mid, mid, true]; else if (target === left) return [mid - 1, mid - 1, true]; else if (target === right) return [mid + 1, mid + 1, true]; else if (target < right) { minRangeIdx = mid + 1; } else if (target < left && target > cur) return [mid - 1, mid, true]; else if (target < cur && target > right) return [mid, mid + 1, true]; else maxRangeIdx = mid - 1; } return [mid, mid, false]; } function getDataByX( layoutData: StableModelLayout, x: number, dx: number, priceUp: boolean, ): [number, number, boolean, boolean] { const xWithDx = priceUp ? x + dx : x - dx; const [minIdx, maxIdx, find] = getMinimumRangeByX(layoutData, xWithDx); if (!find) return [0, 0, false, find]; if (minIdx === maxIdx) return [layoutData.DataElement[maxIdx].price, layoutData.DataElement[maxIdx].y, false, find]; else { const x1 = layoutData.DataElement[minIdx].x; const x2 = layoutData.DataElement[maxIdx].x; const p1 = layoutData.DataElement[minIdx].price; const p2 = layoutData.DataElement[maxIdx].price; const y1 = layoutData.DataElement[minIdx].y; const y2 = layoutData.DataElement[maxIdx].y; if (x >= x1 && x <= x2) { if (priceUp) return [p2, y2, true, find]; else return [p1, y1, true, find]; } else { let p, y; if (priceUp) { p = p1 + ((p2 - p1) * (x - x1)) / (x2 - x1); y = y1 - ((xWithDx - x1) * layoutData.multiplier) / p2; } else { p = p1 + ((p2 - p1) * (x - x1)) / (x2 - x1); y = y2 + ((x2 - xWithDx) * layoutData.multiplier) / p1; } return [p, y, false, find]; } } } function getDataByY( layoutData: StableModelLayout, y: number, dy: number, priceUp: boolean, ): [number, number, boolean, boolean] { const yWithDy = priceUp ? y - dy : y + dy; const [minIdx, maxIdx, find] = getMinimumRangeByY(layoutData, yWithDy); if (!find) return [0, 0, false, find]; if (minIdx === maxIdx) return [layoutData.DataElement[maxIdx].price, layoutData.DataElement[maxIdx].x, false, find]; else { const x1 = layoutData.DataElement[minIdx].x; const x2 = layoutData.DataElement[maxIdx].x; const p1 = layoutData.DataElement[minIdx].price; const p2 = layoutData.DataElement[maxIdx].price; const y1 = layoutData.DataElement[minIdx].y; const y2 = layoutData.DataElement[maxIdx].y; if (y >= y2 && y <= y1) { return priceUp ? [p2, x2, true, find] : [p1, x1, true, find]; } else { let p, x; if (priceUp) { p = p1 + ((p2 - p1) * (y1 - y)) / (y1 - y2); x = x1 + (p2 * (y1 - yWithDy)) / layoutData.multiplier; } else { p = p1 + ((p2 - p1) * (y1 - y)) / (y1 - y2); x = x2 - (p1 * (yWithDy - y2)) / layoutData.multiplier; } return [p, x, false, find]; } } } function getMidPrice(layoutData: StableModelLayout, x: number): number { const ret = getDataByX(layoutData, x, 0, false); if (ret[3]) return ret[0]; else return 0; } export function getDyByDxBaseIn(layoutData: StableModelLayout, xReal: number, yReal: number, dxReal: number): number { const ratio = getRatio(layoutData, xReal, yReal); const x = realToTable(layoutData, xReal, ratio); const y = realToTable(layoutData, yReal, ratio); const dx = realToTable(layoutData, dxReal, ratio); const priceUp = true; const [p, y2, lessTrade, find] = getDataByX(layoutData, x, dx, priceUp); if (!find) return 0; if (lessTrade) { const dyReal = (dxReal * layoutData.multiplier) / p; return dyReal; } else { const dy = y - y2; const dyReal = tableToReal(layoutData, dy, ratio); return dyReal; } } export function getDxByDyBaseIn(layoutData: StableModelLayout, xReal: number, yReal: number, dyReal: number): number { const ratio = getRatio(layoutData, xReal, yReal); const x = realToTable(layoutData, xReal, ratio); const y = realToTable(layoutData, yReal, ratio); const dy = realToTable(layoutData, dyReal, ratio); const priceUp = false; const [p, x2, lessTrade, find] = getDataByY(layoutData, y, dy, priceUp); if (!find) return 0; if (lessTrade) { const dxReal = (dyReal * p) / layoutData.multiplier; return dxReal; } else { const dx = x - x2; const dxReal = tableToReal(layoutData, dx, ratio); return dxReal; } } export function formatLayout(buffer: Buffer): StableModelLayout { const layoutInfo = modelDataInfoLayout.decode(buffer); return { accountType: layoutInfo.accountType.toNumber(), status: layoutInfo.status.toNumber(), multiplier: layoutInfo.multiplier.toNumber(), validDataCount: layoutInfo.validDataCount.toNumber(), DataElement: layoutInfo.DataElement.map((item: any) => ({ x: item.x.toNumber(), y: item.y.toNumber(), price: item.price.toNumber(), })), }; } export function getStablePrice( layoutData: StableModelLayout, coinReal: number, pcReal: number, baseCoin: boolean, ): number { const price = getMidPrice(layoutData, realToTable(layoutData, coinReal, getRatio(layoutData, coinReal, pcReal))) / layoutData.multiplier; return baseCoin ? price : 1 / price; } export class StableLayout { private readonly connection: Connection; private _layoutData: StableModelLayout = { accountType: 0, status: 0, multiplier: 0, validDataCount: 0, DataElement: [], }; constructor({ connection }: { connection: Connection }) { this.connection = connection; } get stableModelData(): StableModelLayout { return this._layoutData; } public async initStableModelLayout(): Promise<void> { if (this._layoutData.validDataCount === 0) { if (this.connection) { const acc = await this.connection.getAccountInfo(MODEL_DATA_PUBKEY); if (acc) this._layoutData = formatLayout(acc?.data); } } } }