test-raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
330 lines (294 loc) • 10.9 kB
text/typescript
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);
}
}
}
}