@frakters/nft-lending-v2
Version:
Client library for interacting with nft lenging solana program
452 lines (451 loc) • 24.4 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.clearLocal = exports.Market = exports.preInitialize = exports.initialize = exports.createAmm = exports.getMarket = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
// @ts-ignore
const buffer_layout_1 = require("buffer-layout");
// import { AMM_INFO_LAYOUT_V4 } from './liquidity'
const serum_1 = require("@project-serum/serum");
const market_js_1 = require("@project-serum/serum/lib/market.js");
const token_instructions_1 = require("@project-serum/serum/lib/token-instructions");
const spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
const web3_1 = require("./web3");
const tokens_1 = require("./tokens");
const ids_1 = require("./ids");
const errors_1 = require("./errors");
const layouts_1 = require("./layouts");
const pools_1 = require("./pools");
const swap_1 = require("./swap");
function getMarket(conn, marketAddress) {
return __awaiter(this, void 0, void 0, function* () {
try {
const expectAmmId = (yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), new web3_js_1.PublicKey(marketAddress), ids_1.AMM_ASSOCIATED_SEED)).toString();
if (pools_1.LIQUIDITY_POOLS.find((item) => item.ammId === expectAmmId)) {
throw new Error('There is already a pool for this Serum Market');
}
const marketAddressPubKey = new web3_js_1.PublicKey(marketAddress);
const market = yield Market.load(conn, marketAddressPubKey, undefined, new web3_js_1.PublicKey(ids_1.SERUM_PROGRAM_ID_V3));
const { asksAddress, bidsAddress, quoteMint, } = market;
let coinOrPcInTokenFlag = false;
for (const item of [tokens_1.TOKENS.USDT, tokens_1.TOKENS.USDC, tokens_1.TOKENS.RAY, tokens_1.TOKENS.WSOL, tokens_1.TOKENS.SRM, tokens_1.TOKENS.PAI, tokens_1.TOKENS.mSOL]) {
if ((quoteMint === null || quoteMint === void 0 ? void 0 : quoteMint.toBase58()) === item.mintAddress) {
coinOrPcInTokenFlag = true;
break;
}
}
if (!coinOrPcInTokenFlag) {
throw new Error('Only markets that contain USDC, USDT, SOL, RAY, SRM, PAI or mSOL as the Quote Token are currently supported.');
}
const asks = [];
const bids = [];
const orderBookMsg = yield web3_1.getMultipleAccounts(conn, [bidsAddress, asksAddress], web3_1.commitment);
orderBookMsg.forEach((info) => {
// @ts-ignore
const data = info.account.data;
// @ts-ignore
const orderbook = market_js_1.Orderbook.decode(market, data);
const { isBids, slab } = orderbook;
if (isBids) {
for (const item of slab.items(true)) {
bids.push((market === null || market === void 0 ? void 0 : market.priceLotsToNumber(item.key.ushrn(64))) || 0);
}
}
else {
for (const item of slab.items(false)) {
asks.push((market === null || market === void 0 ? void 0 : market.priceLotsToNumber(item.key.ushrn(64))) || 0);
}
}
});
const price = asks.length > 0 && bids.length > 0 ? (asks[0] + bids[0]) / 2 : NaN;
const baseMintDecimals = new bignumber_js_1.default(yield web3_1.getMintDecimals(conn, market.baseMintAddress));
const quoteMintDecimals = new bignumber_js_1.default(yield web3_1.getMintDecimals(conn, market.quoteMintAddress));
return { market, price, msg: '', baseMintDecimals, quoteMintDecimals };
}
catch (error) {
if (error.message === 'Non-base58 character') {
return { market: null, price: null, msg: 'market input error', baseMintDecimals: 0, quoteMintDecimals: 0 };
}
else {
return { market: null, price: null, msg: error.message, baseMintDecimals: 0, quoteMintDecimals: 0 };
}
}
});
}
exports.getMarket = getMarket;
function createAmm(conn, wallet, market, userInputBaseValue, userInputQuoteValue) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const transaction = new web3_js_1.Transaction();
const signers = [];
const owner = wallet.publicKey;
const { publicKey, nonce } = yield web3_1.createAmmAuthority(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4));
const ammAuthority = publicKey;
const ammId = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.AMM_ASSOCIATED_SEED);
const poolCoinTokenAccount = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.COIN_VAULT_ASSOCIATED_SEED);
const poolPcTokenAccount = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.PC_VAULT_ASSOCIATED_SEED);
const lpMintAddress = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.LP_MINT_ASSOCIATED_SEED);
const poolTempLpTokenAccount = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.TEMP_LP_TOKEN_ASSOCIATED_SEED);
const ammTargetOrders = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.TARGET_ASSOCIATED_SEED);
const poolWithdrawQueue = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.WITHDRAW_ASSOCIATED_SEED);
const ammOpenOrders = yield web3_1.createAssociatedId(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), market.address, ids_1.OPEN_ORDER_ASSOCIATED_SEED);
let accountSuccessFlag = false;
let accountAllSuccessFlag = false;
const multipleInfo = yield web3_1.getMultipleAccounts(conn, [lpMintAddress], web3_1.commitment);
if (multipleInfo.length > 0 && multipleInfo[0] !== null) {
const tempLpMint = layouts_1.MINT_LAYOUT.decode((_a = multipleInfo[0]) === null || _a === void 0 ? void 0 : _a.account.data);
if (layouts_1.getBigNumber(tempLpMint.supply) === 0) {
accountSuccessFlag = true;
}
else {
accountAllSuccessFlag = true;
}
}
else {
accountSuccessFlag = false;
}
console.log('init flag: ', accountSuccessFlag, accountAllSuccessFlag);
transaction.add(preInitialize(new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), ammTargetOrders, poolWithdrawQueue, ammAuthority, lpMintAddress, market.baseMintAddress, market.quoteMintAddress, poolCoinTokenAccount, poolPcTokenAccount, poolTempLpTokenAccount, market.address, owner, nonce));
const destLpToken = yield web3_1.findAssociatedTokenAddress(owner, lpMintAddress);
const destLpTokenInfo = yield conn.getAccountInfo(destLpToken);
if (!destLpTokenInfo) {
transaction.add(spl_token_1.Token.createAssociatedTokenAccountInstruction(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, ids_1.TOKEN_PROGRAM_ID, lpMintAddress, destLpToken, owner, owner));
}
if (!accountSuccessFlag) {
const txid = yield web3_1.sendTransaction(conn, wallet, transaction, signers);
console.log('txid', txid);
let txidSuccessFlag = 0;
yield conn.onSignature(txid, function (_signatureResult, _context) {
if (_signatureResult.err) {
txidSuccessFlag = -1;
}
else {
txidSuccessFlag = 1;
}
});
const timeAwait = new Date().getTime();
let outOfWhile = false;
while (!outOfWhile) {
console.log('txid', outOfWhile, txidSuccessFlag, (new Date().getTime() - timeAwait) / 1000);
if (txidSuccessFlag !== 0) {
outOfWhile = true;
}
yield new Promise((resolve) => setTimeout(resolve, 1000));
}
if (txidSuccessFlag !== 1) {
throw new Error('create tx1 error');
}
}
const ammKeys = {
ammId,
ammAuthority,
poolCoinTokenAccount,
poolPcTokenAccount,
lpMintAddress,
ammOpenOrders,
ammTargetOrders,
poolWithdrawQueue,
poolTempLpTokenAccount,
destLpToken,
nonce,
};
if (!accountAllSuccessFlag) {
yield initAmm(conn, wallet, market, new web3_js_1.PublicKey(ids_1.LIQUIDITY_POOL_PROGRAM_ID_V4), new web3_js_1.PublicKey(ids_1.SERUM_PROGRAM_ID_V3),
// ammId,
ammKeys, userInputBaseValue, userInputQuoteValue, poolCoinTokenAccount, poolPcTokenAccount);
}
return ammId.toBase58();
});
}
exports.createAmm = createAmm;
function initAmm(conn, wallet, market, ammProgramId, dexProgramId,
// ammKeypair: PublicKey,
ammKeys, userInputBaseValue, userInputQuoteValue, poolCoinTokenAccount, poolPcTokenAccount) {
return __awaiter(this, void 0, void 0, function* () {
const baseMintDecimals = new bignumber_js_1.default(yield web3_1.getMintDecimals(conn, market.baseMintAddress));
const quoteMintDecimals = new bignumber_js_1.default(yield web3_1.getMintDecimals(conn, market.quoteMintAddress));
const coinVol = new bignumber_js_1.default(10).exponentiatedBy(baseMintDecimals).multipliedBy(userInputBaseValue);
const pcVol = new bignumber_js_1.default(10).exponentiatedBy(quoteMintDecimals).multipliedBy(userInputQuoteValue);
const transaction = new web3_js_1.Transaction();
const signers = [];
const owner = wallet.publicKey;
const baseTokenAccount = yield web3_1.getFilteredTokenAccountsByOwner(conn, owner, market.baseMintAddress);
const quoteTokenAccount = yield web3_1.getFilteredTokenAccountsByOwner(conn, owner, market.quoteMintAddress);
const baseTokenList = baseTokenAccount.value.map((item) => {
if (item.account.data.parsed.info.tokenAmount.amount >= layouts_1.getBigNumber(coinVol)) {
return item.pubkey;
}
return null;
});
const quoteTokenList = quoteTokenAccount.value.map((item) => {
if (item.account.data.parsed.info.tokenAmount.amount >= layouts_1.getBigNumber(pcVol)) {
return item.pubkey;
}
return null;
});
let baseToken = null;
for (const item of baseTokenList) {
if (item !== null) {
baseToken = item;
}
}
let quoteToken = null;
for (const item of quoteTokenList) {
if (item !== null) {
quoteToken = item;
}
}
if ((baseToken === null && market.baseMintAddress.toString() !== tokens_1.TOKENS.WSOL.mintAddress) ||
(quoteToken === null && market.quoteMintAddress.toString() !== tokens_1.TOKENS.WSOL.mintAddress)) {
throw new Error('no money');
}
if (market.baseMintAddress.toString() === tokens_1.TOKENS.WSOL.mintAddress) {
const newAccount = new web3_js_1.Account();
transaction.add(web3_js_1.SystemProgram.createAccount({
fromPubkey: owner,
newAccountPubkey: newAccount.publicKey,
lamports: parseInt(coinVol.toFixed()) + 1e7,
space: layouts_1.ACCOUNT_LAYOUT.span,
programId: ids_1.TOKEN_PROGRAM_ID,
}));
transaction.add(token_instructions_1.initializeAccount({
account: newAccount.publicKey,
mint: new web3_js_1.PublicKey(tokens_1.TOKENS.WSOL.mintAddress),
owner,
}));
transaction.add(swap_1.transfer(newAccount.publicKey, poolCoinTokenAccount, owner, parseInt(coinVol.toFixed())));
transaction.add(token_instructions_1.closeAccount({
source: newAccount.publicKey,
destination: owner,
owner,
}));
signers.push(newAccount);
}
else {
transaction.add(
// @ts-ignore
swap_1.transfer(new web3_js_1.PublicKey(baseToken), poolCoinTokenAccount, owner, parseInt(coinVol.toFixed())));
}
if (market.quoteMintAddress.toString() === tokens_1.TOKENS.WSOL.mintAddress) {
const newAccount = new web3_js_1.Account();
transaction.add(web3_js_1.SystemProgram.createAccount({
fromPubkey: owner,
newAccountPubkey: newAccount.publicKey,
lamports: parseInt(pcVol.toFixed()) + 1e7,
space: layouts_1.ACCOUNT_LAYOUT.span,
programId: ids_1.TOKEN_PROGRAM_ID,
}));
transaction.add(token_instructions_1.initializeAccount({
account: newAccount.publicKey,
mint: new web3_js_1.PublicKey(tokens_1.TOKENS.WSOL.mintAddress),
owner,
}));
transaction.add(swap_1.transfer(newAccount.publicKey, poolPcTokenAccount, owner, parseInt(pcVol.toFixed())));
transaction.add(token_instructions_1.closeAccount({
source: newAccount.publicKey,
destination: owner,
owner,
}));
signers.push(newAccount);
}
else {
// @ts-ignore
transaction.add(swap_1.transfer(new web3_js_1.PublicKey(quoteToken), poolPcTokenAccount, owner, parseInt(pcVol.toFixed())));
}
transaction.add(initialize(ammProgramId, ammKeys.ammId, ammKeys.ammAuthority, ammKeys.ammOpenOrders, ammKeys.lpMintAddress, market.baseMintAddress, market.quoteMintAddress, ammKeys.poolCoinTokenAccount, ammKeys.poolPcTokenAccount, ammKeys.poolWithdrawQueue, ammKeys.ammTargetOrders, ammKeys.destLpToken, ammKeys.poolTempLpTokenAccount, dexProgramId, market.address, owner, ammKeys.nonce));
const txid = yield web3_1.sendTransaction(conn, wallet, transaction, signers);
console.log('txid3', txid);
let txidSuccessFlag = 0;
yield conn.onSignature(txid, function (_signatureResult, _context) {
if (_signatureResult.err) {
txidSuccessFlag = -1;
}
else {
txidSuccessFlag = 1;
}
});
const timeAwait = new Date().getTime();
let outOfWhile = false;
while (!outOfWhile) {
console.log('txid3', outOfWhile, txidSuccessFlag, (new Date().getTime() - timeAwait) / 1000);
if (txidSuccessFlag !== 0) {
outOfWhile = true;
}
yield new Promise((resolve) => setTimeout(resolve, 1000));
}
if (txidSuccessFlag !== 1) {
throw new Error('Transaction failed');
}
clearLocal();
});
}
function initialize(ammProgramId, ammId, ammAuthority, ammOpenOrders, lpMintAddress, coinMint, pcMint, poolCoinTokenAccount, poolPcTokenAccount, poolWithdrawQueue, ammTargetOrders, poolLpTokenAccount, poolTempLpTokenAccount, serumProgramId, serumMarket, owner, nonce) {
const dataLayout = buffer_layout_1.struct([buffer_layout_1.u8('instruction'), buffer_layout_1.u8('nonce')]);
const keys = [
{ pubkey: ids_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: ids_1.SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: web3_js_1.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: ammId, isSigner: false, isWritable: true },
{ pubkey: ammAuthority, isSigner: false, isWritable: false },
{ pubkey: ammOpenOrders, isSigner: false, isWritable: true },
{ pubkey: lpMintAddress, isSigner: false, isWritable: true },
{ pubkey: coinMint, isSigner: false, isWritable: true },
{ pubkey: pcMint, isSigner: false, isWritable: true },
{ pubkey: poolCoinTokenAccount, isSigner: false, isWritable: true },
{ pubkey: poolPcTokenAccount, isSigner: false, isWritable: true },
{ pubkey: poolWithdrawQueue, isSigner: false, isWritable: true },
{ pubkey: ammTargetOrders, isSigner: false, isWritable: true },
{ pubkey: poolLpTokenAccount, isSigner: false, isWritable: true },
{ pubkey: poolTempLpTokenAccount, isSigner: false, isWritable: true },
{ pubkey: serumProgramId, isSigner: false, isWritable: false },
{ pubkey: serumMarket, isSigner: false, isWritable: true },
{ pubkey: owner, isSigner: true, isWritable: false },
];
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode({
instruction: 0,
nonce,
}, data);
return new web3_js_1.TransactionInstruction({
keys,
programId: ammProgramId,
data,
});
}
exports.initialize = initialize;
function preInitialize(programId, ammTargetOrders, poolWithdrawQueue, ammAuthority, lpMintAddress, coinMintAddress, pcMintAddress, poolCoinTokenAccount, poolPcTokenAccount, poolTempLpTokenAccount, market, owner, nonce) {
const dataLayout = buffer_layout_1.struct([buffer_layout_1.u8('instruction'), buffer_layout_1.u8('nonce')]);
const keys = [
{ pubkey: ids_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: ids_1.SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: web3_js_1.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: ammTargetOrders, isSigner: false, isWritable: true },
{ pubkey: poolWithdrawQueue, isSigner: false, isWritable: true },
{ pubkey: ammAuthority, isSigner: false, isWritable: false },
{ pubkey: lpMintAddress, isSigner: false, isWritable: true },
{ pubkey: coinMintAddress, isSigner: false, isWritable: false },
{ pubkey: pcMintAddress, isSigner: false, isWritable: false },
{ pubkey: poolCoinTokenAccount, isSigner: false, isWritable: true },
{ pubkey: poolPcTokenAccount, isSigner: false, isWritable: true },
{ pubkey: poolTempLpTokenAccount, isSigner: false, isWritable: true },
{ pubkey: market, isSigner: false, isWritable: false },
{ pubkey: owner, isSigner: true, isWritable: false },
];
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode({
instruction: 10,
nonce,
}, data);
return new web3_js_1.TransactionInstruction({
keys,
programId,
data,
});
}
exports.preInitialize = preInitialize;
// @ts-ignore
class Market extends serum_1.Market {
constructor() {
super(...arguments);
this.baseVault = null;
this.quoteVault = null;
this.requestQueue = null;
this.eventQueue = null;
this.bids = null;
this.asks = null;
this.baseLotSize = 0;
this.quoteLotSize = 0;
this.quoteMint = null;
this.baseMint = null;
}
static load(connection, address, options = {}, programId) {
return __awaiter(this, void 0, void 0, function* () {
const { owner, data } = errors_1.throwIfNull(yield connection.getAccountInfo(address), 'Market not found');
if (!owner.equals(programId)) {
throw new Error('Address not owned by program: ' + owner.toBase58());
}
const decoded = this.getLayout(programId).decode(data);
if (!decoded.accountFlags.initialized || !decoded.accountFlags.market || !decoded.ownAddress.equals(address)) {
throw new Error('Invalid market');
}
const [baseMintDecimals, quoteMintDecimals] = yield Promise.all([
web3_1.getMintDecimals(connection, decoded.baseMint),
web3_1.getMintDecimals(connection, decoded.quoteMint),
]);
const market = new Market(decoded, baseMintDecimals, quoteMintDecimals, options, programId);
market._decoded = decoded;
market.baseLotSize = decoded.baseLotSize;
market.quoteLotSize = decoded.quoteLotSize;
market.baseVault = decoded.baseVault;
market.quoteVault = decoded.quoteVault;
market.requestQueue = decoded.requestQueue;
market.eventQueue = decoded.eventQueue;
market.bids = decoded.bids;
market.asks = decoded.asks;
market.quoteMint = decoded.quoteMint;
market.baseMint = decoded.baseMint;
return market;
});
}
static loadAll(connection, options = {}, programId) {
return __awaiter(this, void 0, void 0, function* () {
// const { owner, data } = throwIfNull(await connection.getAccountInfo(address), 'Market not found')
// if (!owner.equals(programId)) {
// throw new Error('Address not owned by program: ' + owner.toBase58())
// }
const markets = [];
const allAccounts = yield connection.getProgramAccounts(programId, 'confirmed');
for (let account of allAccounts) {
const data = account.account.data;
const decoded = this.getLayout(programId).decode(data);
// if (!decoded.accountFlags.initialized || !decoded.accountFlags.market || !decoded.ownAddress.equals(address)) {
// throw new Error('Invalid market')
// }
const [baseMintDecimals, quoteMintDecimals] = yield Promise.all([
web3_1.getMintDecimals(connection, decoded.baseMint),
web3_1.getMintDecimals(connection, decoded.quoteMint),
]);
const market = new Market(decoded, baseMintDecimals, quoteMintDecimals, options, programId);
market._decoded = decoded;
market.baseLotSize = decoded.baseLotSize;
market.quoteLotSize = decoded.quoteLotSize;
market.baseVault = decoded.baseVault;
market.quoteVault = decoded.quoteVault;
market.requestQueue = decoded.requestQueue;
market.eventQueue = decoded.eventQueue;
market.bids = decoded.bids;
market.asks = decoded.asks;
market.quoteMint = decoded.quoteMint;
market.baseMint = decoded.baseMint;
markets.push(market);
}
return markets;
});
}
}
exports.Market = Market;
function clearLocal() {
localStorage.removeItem('poolCoinTokenAccount');
localStorage.removeItem('poolPcTokenAccount');
localStorage.removeItem('lpMintAddress');
localStorage.removeItem('poolTempLpTokenAccount');
localStorage.removeItem('ammId');
localStorage.removeItem('ammOpenOrders');
localStorage.removeItem('ammTargetOrders');
localStorage.removeItem('poolWithdrawQueue');
localStorage.removeItem('destLpToken');
localStorage.removeItem('createMarket');
}
exports.clearLocal = clearLocal;