UNPKG

@frakters/nft-lending-v2

Version:

Client library for interacting with nft lenging solana program

452 lines (451 loc) 24.4 kB
"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;