goosefx-ssl-sdk
Version:
SDK for interacting with GooseFX Single-sided Liquidity pools
290 lines • 14.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Swap = exports.wasmData = void 0;
const anchor_1 = require("@project-serum/anchor");
const layouts_1 = require("../layouts");
const spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
const buffer_1 = require("buffer");
const constants_1 = require("../constants");
const SwapIDL = __importStar(require("../idl/gfx_ssl_idl.json"));
const utils_1 = require("./utils");
const gfx_ssl_wasm_data_1 = __importDefault(require("../wasm/gfx_ssl_wasm_data"));
const gfx_ssl_wasm_1 = __importStar(require("../wasm/gfx_ssl_wasm")), wasm = gfx_ssl_wasm_1;
const spl_token_2 = require("@solana/spl-token");
const ssl_1 = require("./ssl");
const client_1 = require("@pythnetwork/client");
var gfx_ssl_wasm_data_2 = require("../wasm/gfx_ssl_wasm_data");
Object.defineProperty(exports, "wasmData", { enumerable: true, get: function () { return __importDefault(gfx_ssl_wasm_data_2).default; } });
let wasmInited = false;
class Swap {
constructor(connection, controller = constants_1.ADDRESSES["MAINNET"].GFX_CONTROLLER, programId = constants_1.ADDRESSES["MAINNET"].SSL_PROGRAM_ID) {
this.connection = connection;
this.controller = controller;
this.programId = programId;
this.getPairAddress = (tokenA, tokenB) => {
const addresses = [tokenA.toBuffer(), tokenB.toBuffer()].sort(buffer_1.Buffer.compare);
const pairArr = web3_js_1.PublicKey.findProgramAddressSync([
buffer_1.Buffer.from("GFX-SSL-Pair", "utf-8"),
this.controller.toBuffer(),
addresses[0],
addresses[1],
], this.programId);
return pairArr[0];
};
this.createAssociatedTokenAccountIx = (mint, associatedAccount, owner) => (0, spl_token_1.createAssociatedTokenAccountInstruction)(owner, associatedAccount, owner, mint);
this.getQuoter = async (tokenIn, tokenOut) => {
let wasm = await this.getWasm();
return new Quoter(this.connection, this.programId, this.controller, tokenIn, tokenOut, wasm);
};
this.getQuote = async (tokenIn, tokenOut, inTokenAmount, silent = true) => {
const quoter = await this.getQuoter(tokenIn, tokenOut);
await quoter.prepare();
return quoter.quote(inTokenAmount, silent);
};
this.getMinimumQuote = async (tokenA, tokenB, inTokenAmount, slippage) => {
const result = await this.getQuote(tokenA, tokenB, inTokenAmount);
const minAmountOut = (result.out * (10000n - BigInt(slippage * 10000))) / 10000n;
return minAmountOut;
};
this.createSwapIx = async (tokenA, tokenB, inTokenAmount, minOut, wallet, referrerTokenAccount) => {
let ixs = [];
const addedComputeBudgetIX = web3_js_1.ComputeBudgetProgram.requestUnits({
units: 1000000,
additionalFee: 0,
});
ixs.push(addedComputeBudgetIX);
const program = new anchor_1.Program(SwapIDL, this.programId.toBase58(), new anchor_1.Provider(this.connection, wallet, {
commitment: "processed",
}));
const inst = program.instruction;
const pair = this.getPairAddress(tokenA, tokenB);
const [inTokenAtaUser, outTokenAtaUser] = [
(0, utils_1.findAssociatedTokenAddress)(wallet, tokenA),
(0, utils_1.findAssociatedTokenAddress)(wallet, tokenB),
];
const sslIn = ssl_1.SSL.findAddress(this.controller, tokenA, this.programId);
const sslOut = ssl_1.SSL.findAddress(this.controller, tokenB, this.programId);
if (!(await this.connection.getAccountInfo(outTokenAtaUser))) {
ixs.push(this.createAssociatedTokenAccountIx(tokenB, outTokenAtaUser, wallet));
}
const pairData = await this.connection.getAccountInfo(pair);
if (!pairData || !pairData.data)
throw new Error("Token Pair do not exist yet.");
const tokenAccountB = (0, utils_1.findAssociatedTokenAddress)(wallet, tokenB);
if (tokenB.toBase58() !== spl_token_1.NATIVE_MINT.toBase58() &&
!(await this.connection.getParsedAccountInfo(tokenAccountB)).value) {
ixs.push(this.createAssociatedTokenAccountIx(tokenB, tokenAccountB, wallet));
}
const decoded = layouts_1.PAIR_LAYOUT.decode(pairData.data);
const { oracles, nOracle, feeCollector } = decoded;
const n = Number(nOracle.toString());
const remainingAccounts = [];
if (referrerTokenAccount !== undefined) {
remainingAccounts.push({ isSigner: false, isWritable: true, pubkey: referrerTokenAccount });
}
for (const oracle of oracles.slice(0, n)) {
for (const elem of oracle.elements.slice(0, Number(oracle.n))) {
remainingAccounts.push({
isSigner: false,
isWritable: false,
pubkey: elem.address,
});
}
}
const accounts = {
controller: this.controller,
pair,
sslIn: sslIn,
sslOut: sslOut,
liabilityVaultIn: (0, utils_1.findAssociatedTokenAddress)(sslIn, tokenA),
liabilityVaultOut: (0, utils_1.findAssociatedTokenAddress)(sslOut, tokenB),
swappedLiabilityVaultIn: (0, utils_1.findAssociatedTokenAddress)(sslIn, tokenB),
swappedLiabilityVaultOut: (0, utils_1.findAssociatedTokenAddress)(sslOut, tokenA),
userInAta: inTokenAtaUser,
userOutAta: outTokenAtaUser,
feeCollectorAta: (0, utils_1.findAssociatedTokenAddress)(feeCollector, tokenA),
userWallet: wallet,
feeCollector: feeCollector,
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
};
ixs.push(await inst.swap(new anchor_1.BN(inTokenAmount.toString()), new anchor_1.BN(minOut.toString()), { accounts, remainingAccounts }));
return ixs;
};
}
async getWasm() {
if (!wasmInited) {
await (0, gfx_ssl_wasm_1.default)(buffer_1.Buffer.from(gfx_ssl_wasm_data_1.default, "base64"));
wasmInited = true;
}
return wasm;
}
}
exports.Swap = Swap;
class Quoter {
constructor(connection, programId, controller, tokenIn, tokenOut, wasm) {
this.connection = connection;
this.programId = programId;
this.controller = controller;
this.tokenIn = tokenIn;
this.tokenOut = tokenOut;
this.wasm = wasm;
this.prepared = undefined;
this.getPairAddress = (tokenA, tokenB) => {
const addresses = [tokenA.toBuffer(), tokenB.toBuffer()].sort(buffer_1.Buffer.compare);
const pairArr = web3_js_1.PublicKey.findProgramAddressSync([
buffer_1.Buffer.from("GFX-SSL-Pair", "utf-8"),
this.controller.toBuffer(),
addresses[0],
addresses[1],
], this.programId);
return pairArr[0];
};
}
async prepare() {
const pair = this.getPairAddress(this.tokenIn, this.tokenOut);
const pairData = await this.connection.getAccountInfo(pair);
if (!pairData)
throw "Cannot get Pair";
const sslIn = ssl_1.SSL.findAddress(this.controller, this.tokenIn, this.programId);
const sslInData = await this.connection.getAccountInfo(sslIn);
if (!sslInData)
throw "Cannot get SSL for tokenIn";
const sslOut = ssl_1.SSL.findAddress(this.controller, this.tokenOut, this.programId);
const sslOutData = await this.connection.getAccountInfo(sslOut);
if (!sslOutData)
throw "Cannot get SSL for tokenOut";
const liabilityVaultIn = await (0, spl_token_2.getAccount)(this.connection, (0, utils_1.findAssociatedTokenAddress)(sslIn, this.tokenIn));
const swappedLiabilityVaultIn = await (0, spl_token_2.getAccount)(this.connection, (0, utils_1.findAssociatedTokenAddress)(sslIn, this.tokenOut));
const liabilityVaultOut = await (0, spl_token_2.getAccount)(this.connection, (0, utils_1.findAssociatedTokenAddress)(sslOut, this.tokenOut));
const swappedLiabilityVaultOut = await (0, spl_token_2.getAccount)(this.connection, (0, utils_1.findAssociatedTokenAddress)(sslOut, this.tokenIn));
const OracleRegistry = wasm.OracleRegistry;
const decoded = layouts_1.PAIR_LAYOUT.decode(pairData.data);
const { maxDelay, oracles, nOracle } = decoded;
const n = Number(nOracle.toString());
let publishedSlots = [];
let pythStatus = [];
let pythConfs = [];
const registry = new OracleRegistry();
for (const oracle of oracles.slice(0, n)) {
const n = Number(oracle.n);
for (const elem of oracle.elements.slice(0, n)) {
const acctInfo = await this.connection.getAccountInfo(elem.address);
if (acctInfo?.data) {
registry.add_oracle(elem.address.toBuffer(), acctInfo.data);
let priceData = (0, client_1.parsePriceData)(acctInfo.data);
publishedSlots.push(priceData.aggregate.publishSlot);
pythStatus.push(priceData.aggregate.status);
let price = priceData.aggregate.price / Math.pow(10, -priceData.exponent);
let std = priceData.aggregate.confidence / Math.pow(10, -priceData.exponent);
pythConfs.push(price / std);
}
}
}
this.prepared = {
pairData: pairData.data,
sslInData: sslInData.data,
sslOutData: sslOutData.data,
liabilityIn: liabilityVaultIn.amount,
swappedLiabilityIn: swappedLiabilityVaultIn.amount,
liabilityOut: liabilityVaultOut.amount,
swappedLiabilityOut: swappedLiabilityVaultOut.amount,
registry: registry,
suspended: (new ssl_1.SSL(sslInData)).isSuspended() || (new ssl_1.SSL(sslOutData)).isSuspended(),
publishedSlots: publishedSlots.map((val) => BigInt(val)),
pythStatus,
pythConfs,
confidence: decoded.confidence,
maxDelay: maxDelay
};
}
isSuspended(currentSlot) {
if (this.prepared === undefined)
throw "Run prepare first";
let suspended = this.prepared.suspended;
if (currentSlot !== undefined) {
for (const pubSlot of this.prepared.publishedSlots) {
suspended || (suspended = pubSlot + this.prepared.maxDelay <= currentSlot);
}
}
for (const pythStatus of this.prepared.pythStatus) {
suspended || (suspended = pythStatus !== client_1.PriceStatus.Trading);
}
for (const conf of this.prepared.pythConfs) {
suspended || (suspended = this.prepared.confidence > conf);
}
return suspended;
}
quote(inTokenAmount, silent = true, niter = 64) {
const swapWASM = wasm.swap;
if (inTokenAmount === 0n)
return {
amountIn: 0n,
fee: 0n,
amountOut: 0n,
impact: 0,
swapPrice: 0,
instantPrice: 0,
oraclePrice: 0,
};
if (this.prepared === undefined)
throw "Run prepare first";
const prepared = this.prepared;
let out;
try {
out = swapWASM(prepared.sslInData.slice(), prepared.sslOutData.slice(), prepared.pairData.slice(), prepared.liabilityIn, prepared.liabilityOut, prepared.swappedLiabilityIn, prepared.swappedLiabilityOut, prepared.registry, inTokenAmount, niter);
}
catch (e) {
if (silent) {
return {
amountIn: inTokenAmount,
fee: 0n,
amountOut: 0n,
impact: 1,
swapPrice: 0,
instantPrice: 0,
oraclePrice: 0,
};
}
else {
throw e;
}
}
return {
amountIn: out.amount_in,
fee: out.fee_paid,
amountOut: out.amount_out,
impact: out.price_impact,
swapPrice: out.swap_price,
instantPrice: out.insta_price,
oraclePrice: out.oracle_price,
};
}
}
//# sourceMappingURL=swap.js.map