UNPKG

@mean-dao/ddca

Version:

Typescript library to interact with the Decentralized DCA program

790 lines (789 loc) 57.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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 __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.DdcaClient = void 0; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const anchor = __importStar(require("@project-serum/anchor")); const anchor_1 = require("@project-serum/anchor"); const _1 = require("."); const anchor_2 = require("@project-serum/anchor"); const bytes_1 = require("@project-serum/anchor/dist/cjs/utils/bytes"); const idl_json_1 = __importDefault(require("./idl.json")); // CONSTANTS const SYSTEM_PROGRAM_ID = anchor.web3.SystemProgram.programId; const DDCA_OPERATING_ACCOUNT_ADDRESS = new web3_js_1.PublicKey("6u1Hc9AqC6AvpYDQcFjhMVqAwcQ83Kn5TVm6oWMjDDf1"); const HLA_PROGRAM_ADDRESS = new web3_js_1.PublicKey("B6gLd2uyVQLZMdC1s9C4WR7ZP9fMhJNh7WZYcsibuzN3"); const HLA_OPERATING_ACCOUNT_ADDRESS = new web3_js_1.PublicKey("FZMd4pn9FsvMC55D4XQfaexJvKBtQpVuqMk5zuonLRDX"); const DDCA_SWAP_PERCENT_SLIPPAGE = 1.0; // 1 % /** * Anchor based client for the DDCA program */ class DdcaClient { /** * Create a DDCA client */ constructor(rpcUrl, anchorWallet, // commitment: Commitment | string = 'confirmed' as Commitment confirmOptions, verbose = false, tempoApiUrl = "https://tempo-api.meanops.com") { this.rpcVersion = null; if (!rpcUrl) throw new Error("wallet cannot be null or undefined"); if (!anchorWallet || !anchorWallet.publicKey) throw new Error("wallet's public key cannot be null or undefined"); // const confirmationOptions = { // preflightCommitment: commitment, // commitment: commitment // } as anchor.web3.ConfirmOptions; // const provider = this.getAnchorProvider(rpcUrl, anchorWallet, confirmationOptions as anchor.web3.Connection); this.ownerAccountAddress = anchorWallet.publicKey; this.rpcUrl = rpcUrl; const provider = this.getAnchorProvider(rpcUrl, anchorWallet, confirmOptions); this.provider = provider; this.connection = provider.connection; anchor.setProvider(provider); const programId = new anchor.web3.PublicKey(idl_json_1.default.metadata.address); this.program = new anchor.Program(idl_json_1.default, programId, provider); this.verbose = verbose; this.tempoApiUrl = tempoApiUrl; } getAnchorProvider(rpcUrl, // commitment: Commitment | string = 'confirmed', anchorWallet, opts) { opts = opts !== null && opts !== void 0 ? opts : anchor.Provider.defaultOptions(); const connection = new web3_js_1.Connection(rpcUrl, opts.preflightCommitment); const provider = new anchor.Provider(connection, anchorWallet, opts); return provider; } createWrapSolInstructions(amountOfToWrapInLamports, ownerWrapTokenAccountAddress) { return __awaiter(this, void 0, void 0, function* () { // Allocate memory for the account const minimumAccountBalance = yield spl_token_1.Token.getMinBalanceRentForExemptAccount(this.connection); const newWrapAccount = web3_js_1.Keypair.generate(); let wrapIxs = [ web3_js_1.SystemProgram.createAccount({ fromPubkey: this.ownerAccountAddress, newAccountPubkey: newWrapAccount.publicKey, lamports: minimumAccountBalance + amountOfToWrapInLamports, space: spl_token_1.AccountLayout.span, programId: spl_token_1.TOKEN_PROGRAM_ID, }), spl_token_1.Token.createInitAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.NATIVE_MINT, newWrapAccount.publicKey, this.ownerAccountAddress), spl_token_1.Token.createTransferInstruction(spl_token_1.TOKEN_PROGRAM_ID, newWrapAccount.publicKey, ownerWrapTokenAccountAddress, this.ownerAccountAddress, [], amountOfToWrapInLamports // BN to number ), spl_token_1.Token.createCloseAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, newWrapAccount.publicKey, this.ownerAccountAddress, this.ownerAccountAddress, []) ]; return [wrapIxs, newWrapAccount]; }); } createDdcaTx(fromMint, toMint, amountPerSwap, swapsCount, intervalInSeconds, wrapSolIfNeeded = false) { return __awaiter(this, void 0, void 0, function* () { const swapsCountBn = new anchor_1.BN(swapsCount); if (swapsCountBn.lte(new anchor_1.BN(1))) throw new Error("Invalid param 'swapsCount'. Needs to be greater than 1."); let changedFromMintTowSol = false; if (fromMint.equals(_1.SOL_MINT)) { fromMint = spl_token_1.NATIVE_MINT; changedFromMintTowSol = true; } else if (toMint.equals(_1.SOL_MINT)) { toMint = spl_token_1.NATIVE_MINT; } if (fromMint.equals(toMint)) throw Error("Cannot create DDCA with same 'from' and 'to' mints"); const fromMintDecimals = fromMint.equals(spl_token_1.NATIVE_MINT) ? _1.SOL_MINT_DECIMALS : (yield this.connection.getTokenSupply(fromMint)).value.decimals; const amountPerSwapBn = new anchor_1.BN(amountPerSwap * Math.pow(10, fromMintDecimals)); const depositAmountBn = amountPerSwapBn.mul(swapsCountBn); const blockHeight = yield this.connection.getSlot('confirmed'); // const blockHeight = (await this.connection.getEpochInfo()).blockHeight; const blockHeightBn = new anchor_1.BN(blockHeight); // const blockHeightBytes = blockHeightBn.toBuffer('be', 8); const blockHeightBytes = blockHeightBn.toArrayLike(Buffer, 'be', 8); //ddca account pda and bump const [ddcaAccountPda, ddcaAccountPdaBump] = yield anchor.web3.PublicKey.findProgramAddress([ this.ownerAccountAddress.toBuffer(), blockHeightBytes, Buffer.from(anchor.utils.bytes.utf8.encode("ddca-seed")), ], this.program.programId); //owner token account (from) const ownerFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, this.ownerAccountAddress); //ddca associated token account (from) const ddcaFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, ddcaAccountPda, true); //ddca associated token account (to) const ddcaToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, ddcaAccountPda, true); //hla operating token account (from) const hlaOperatingFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, HLA_OPERATING_ACCOUNT_ADDRESS); // Instructions let ixs = new Array(); let ownerFromAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ownerFromTokenAccountAddress, fromMint, this.ownerAccountAddress, this.ownerAccountAddress, this.connection); if (ownerFromAtaCreateInstruction !== null) ixs.push(ownerFromAtaCreateInstruction); let hlaOperatingFromAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(hlaOperatingFromTokenAccountAddress, fromMint, HLA_OPERATING_ACCOUNT_ADDRESS, this.ownerAccountAddress, this.connection); if (hlaOperatingFromAtaCreateInstruction !== null) ixs.push(hlaOperatingFromAtaCreateInstruction); let signers = new Array(); if (changedFromMintTowSol) { const [wrapIxs, newWrapAccount] = yield this.createWrapSolInstructions(depositAmountBn.toNumber(), ownerFromTokenAccountAddress); ixs.push(...wrapIxs); signers.push(newWrapAccount); } else if (fromMint.equals(spl_token_1.NATIVE_MINT) && wrapSolIfNeeded) { let ownerWSolAtaBalanceBn = new anchor_1.BN(0); if (!ownerFromAtaCreateInstruction) { // owner wSOL ATA account does not exist so balance is zero const ownerWSolAtaTokenAmount = (yield this.connection.getTokenAccountBalance(ownerFromTokenAccountAddress)).value; ownerWSolAtaBalanceBn = new anchor_1.BN(ownerWSolAtaTokenAmount.amount); } if (depositAmountBn.gt(ownerWSolAtaBalanceBn)) { const amountToWrapBn = depositAmountBn.sub(ownerWSolAtaBalanceBn); const [wrapIxs, newWrapAccount] = yield this.createWrapSolInstructions(amountToWrapBn.toNumber(), ownerFromTokenAccountAddress); ixs.push(...wrapIxs); signers.push(newWrapAccount); } } if (ixs.length === 0) ixs = undefined; let wakeAccountAddress; try { wakeAccountAddress = yield this.CreateCrankAddress(this.ownerAccountAddress, this.rpcUrl, blockHeight); } catch (error) { throw new Error(`Unable to create wake account (${error})`); } if (this.verbose) { console.log("TEST PARAMETERS:"); console.log(" Program ID: " + this.program.programId); console.log(" payer.address: " + this.ownerAccountAddress); console.log(" fromMint: " + fromMint); console.log(" toMint: " + toMint); console.log(" depositAmount: " + depositAmountBn.toNumber()); console.log(" amountPerSwap: " + amountPerSwapBn.toNumber()); console.log(" intervalInSeconds: " + intervalInSeconds); console.log(" blockHeight: " + blockHeight); console.log(); console.log(" ownerAccountAddress: " + this.ownerAccountAddress); console.log(" ownerFromTokenAccountAddress: " + ownerFromTokenAccountAddress); console.log(); console.log(" ddcaAccountPda: " + ddcaAccountPda); console.log(" ddcaAccountPdaBump: " + ddcaAccountPdaBump); console.log(" ddcaFromTokenAccountAddress: " + ddcaFromTokenAccountAddress); console.log(" ddcaToTokenAccountAddress: " + ddcaToTokenAccountAddress); console.log(" wakeAccountAddress: " + wakeAccountAddress); console.log(); console.log(" SYSTEM_PROGRAM_ID: " + SYSTEM_PROGRAM_ID); console.log(" TOKEN_PROGRAM_ID: " + spl_token_1.TOKEN_PROGRAM_ID); console.log(" ASSOCIATED_TOKEN_PROGRAM_ID: " + spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); console.log(); } const createTx = yield this.program.transaction.create(new anchor_1.BN(blockHeight), ddcaAccountPdaBump, depositAmountBn, amountPerSwapBn, new anchor_1.BN(intervalInSeconds), { accounts: { // owner ownerAccount: this.ownerAccountAddress, ownerFromTokenAccount: ownerFromTokenAccountAddress, // ddca ddcaAccount: ddcaAccountPda, fromMint: fromMint, fromTokenAccount: ddcaFromTokenAccountAddress, toMint: toMint, toTokenAccount: ddcaToTokenAccountAddress, wakeAccount: wakeAccountAddress, // system accounts rent: anchor.web3.SYSVAR_RENT_PUBKEY, clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, systemProgram: SYSTEM_PROGRAM_ID, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID }, instructions: ixs, }); createTx.feePayer = this.ownerAccountAddress; let hash = yield this.connection.getRecentBlockhash(this.connection.commitment); createTx.recentBlockhash = hash.blockhash; if (signers.length > 0) createTx.partialSign(...signers); return [ddcaAccountPda, createTx]; }); } createWakeAndSwapTx(ddcaAccountAddress, hlaInfo) { return __awaiter(this, void 0, void 0, function* () { if (!ddcaAccountAddress) throw new Error("Invalid param 'ddcaAccountAddress'"); if (!hlaInfo) throw new Error("Invalid param 'hlaInfo'"); const ddcaAccount = yield this.program.account.ddcaAccount.fetch(ddcaAccountAddress); if (!ddcaAccount) { throw new Error(`No DDCA account was found for address: ${ddcaAccountAddress}`); } const fromMint = ddcaAccount.fromMint; const toMint = ddcaAccount.toMint; //ddca associated token account (from) const ddcaFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, ddcaAccountAddress, true); //ddca associated token account (to) const ddcaToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, ddcaAccountAddress, true); //ddca operating token account (from) const ddcaOperatingFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, DDCA_OPERATING_ACCOUNT_ADDRESS); //hla operating token account (from) const hlaOperatingFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, HLA_OPERATING_ACCOUNT_ADDRESS); const hlaSwapPctFee = hlaInfo.aggregatorPercentFees; const exchangeRate = hlaInfo.exchangeRate; // out price const inAmount = ddcaAccount.amountPerSwap / (Math.pow(10, ddcaAccount.fromMintDecimals)); // in amount const inAmountWithFee = inAmount * (1 - hlaSwapPctFee / 100); // in amount with fee deducted const outAmount = inAmountWithFee * exchangeRate; const minOutAmount = outAmount * (1 - DDCA_SWAP_PERCENT_SLIPPAGE / 100); const toMintDecimals = (yield this.connection.getTokenSupply(toMint)).value.decimals; const swapMinimumOutAmountBn = new anchor_1.BN(minOutAmount * Math.pow(10, toMintDecimals)); const swapSlippageBn = new anchor_1.BN(DDCA_SWAP_PERCENT_SLIPPAGE * 100); if (this.verbose) { console.log("TEST PARAMETERS:"); console.log(" Program ID: " + this.program.programId); console.log(" ddcaAccountPda: " + ddcaAccountAddress); console.log(" fromMint: " + fromMint); console.log(" toMint: " + toMint); console.log(); console.log(" ddcaFromTokenAccountAddress: " + ddcaFromTokenAccountAddress); console.log(" ddcaToTokenAccountAddress: " + ddcaToTokenAccountAddress); console.log(); console.log(" DDCA_OPERATING_ACCOUNT_ADDRESS: " + DDCA_OPERATING_ACCOUNT_ADDRESS); console.log(" ddcaOperatingFromTokenAccountAddress: " + ddcaOperatingFromTokenAccountAddress); console.log(); console.log(" HLA_PROGRAM_ADDRESS: " + HLA_PROGRAM_ADDRESS); console.log(" HLA_OPERATING_ACCOUNT_ADDRESS: " + HLA_OPERATING_ACCOUNT_ADDRESS); console.log(" hlaOperatingFromTokenAccountAddress: " + hlaOperatingFromTokenAccountAddress); console.log(" exchangeRate: " + exchangeRate); console.log(" inAmount: " + inAmount); console.log(" inAmountWithFee: " + inAmountWithFee); console.log(" outAmount: " + outAmount); console.log(" minOutAmount: " + minOutAmount); console.log(" DDCA_SWAP_PERCENT_SLIPPAGE: " + DDCA_SWAP_PERCENT_SLIPPAGE); console.log(); console.log(" SYSTEM_PROGRAM_ID: " + SYSTEM_PROGRAM_ID); console.log(" TOKEN_PROGRAM_ID: " + spl_token_1.TOKEN_PROGRAM_ID); console.log(" ASSOCIATED_TOKEN_PROGRAM_ID: " + spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID); console.log(); } const wakeAndSwapTx = yield this.program.transaction.wakeAndSwap(swapMinimumOutAmountBn, swapSlippageBn, { accounts: { // wake account wakeAccount: this.ownerAccountAddress, // ddca ddcaAccount: ddcaAccountAddress, fromMint: fromMint, fromTokenAccount: ddcaFromTokenAccountAddress, toMint: toMint, toTokenAccount: ddcaToTokenAccountAddress, // hybrid liquidity aggregator accounts hlaProgram: HLA_PROGRAM_ADDRESS, hlaOperatingAccount: HLA_OPERATING_ACCOUNT_ADDRESS, hlaOperatingFromTokenAccount: hlaOperatingFromTokenAccountAddress, // system accounts rent: anchor.web3.SYSVAR_RENT_PUBKEY, clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, systemProgram: SYSTEM_PROGRAM_ID, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID }, remainingAccounts: hlaInfo.remainingAccounts, // hla specific amm pool accounts }); wakeAndSwapTx.feePayer = this.ownerAccountAddress; let hash = yield this.connection.getRecentBlockhash(this.connection.commitment); wakeAndSwapTx.recentBlockhash = hash.blockhash; return wakeAndSwapTx; }); } createAddFundsTx(ddcaAccountAddress, swapsCount, wrapSolIfNeeded = false) { return __awaiter(this, void 0, void 0, function* () { if (!ddcaAccountAddress) throw new Error("Invalid param 'ddcaAccountAddress'"); const swapsCountBn = new anchor_1.BN(swapsCount); if (swapsCountBn.lte(new anchor_1.BN(1))) throw new Error("Invalid param 'swapsCount'. Needs to be greater than 1."); const ownerAccountAddress = this.ownerAccountAddress; const ddcaAccount = yield this.program.account.ddcaAccount.fetch(ddcaAccountAddress); if (!ddcaAccount) { throw new Error(`No DDCA account was found for address: ${ddcaAccountAddress}`); } if (ddcaAccount.ownerAccAddr.toBase58() !== ownerAccountAddress.toBase58()) { throw new Error(`DDCA account: ${ddcaAccountAddress} ins not owned by this owner`); } const depositAmountBn = (new anchor_1.BN(ddcaAccount.amountPerSwap)).mul(swapsCountBn); //owner token account (from) const ownerFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, ddcaAccount.fromMint, ownerAccountAddress); // Instructions let ixs = new Array(); let ownerFromAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ownerFromTokenAccountAddress, ddcaAccount.fromMint, ownerAccountAddress, ownerAccountAddress, this.connection); if (ownerFromAtaCreateInstruction !== null) ixs.push(ownerFromAtaCreateInstruction); let signers = new Array(); if (ddcaAccount.fromMint.equals(spl_token_1.NATIVE_MINT) && wrapSolIfNeeded) { let ownerWSolAtaBalanceBn = new anchor_1.BN(0); if (!ownerFromAtaCreateInstruction) { // owner wSOL ATA account does not exist so balance is zero const ownerWSolAtaTokenAmount = (yield this.connection.getTokenAccountBalance(ownerFromTokenAccountAddress)).value; ownerWSolAtaBalanceBn = new anchor_1.BN(ownerWSolAtaTokenAmount.amount); } if (depositAmountBn.gt(ownerWSolAtaBalanceBn)) { const amountToWrapBn = depositAmountBn.sub(ownerWSolAtaBalanceBn); const [wrapIxs, newWrapAccount] = yield this.createWrapSolInstructions(amountToWrapBn.toNumber(), ownerFromTokenAccountAddress); ixs.push(...wrapIxs); signers.push(newWrapAccount); } } if (ixs.length === 0) ixs = undefined; if (this.verbose) { console.log("TEST PARAMETERS:"); console.log(" Program ID: " + this.program.programId); console.log(" payer.address: " + ownerAccountAddress); console.log(); console.log(" ownerAccountAddress: " + this.ownerAccountAddress); console.log(" ownerFromTokenAccountAddress: " + ownerFromTokenAccountAddress); console.log(" ddcaAccountPda: " + ddcaAccountAddress); console.log(" fromMint: " + ddcaAccount.fromMint); console.log(" fromMintDecimals: " + ddcaAccount.fromMintDecimals); console.log(" wakeAccountAddress: " + ddcaAccount.wakeAccAddr); console.log(); } const addFundsTx = yield this.program.transaction.addFunds(depositAmountBn, { accounts: { // owner ownerAccount: ownerAccountAddress, ownerFromTokenAccount: ownerFromTokenAccountAddress, // ddca ddcaAccount: ddcaAccountAddress, fromTokenAccount: ddcaAccount.fromTaccAddr, wakeAccount: ddcaAccount.wakeAccAddr, // system accounts rent: anchor.web3.SYSVAR_RENT_PUBKEY, clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, systemProgram: SYSTEM_PROGRAM_ID, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID }, instructions: ixs, // seeds: [] }); addFundsTx.feePayer = ownerAccountAddress; let hash = yield this.connection.getRecentBlockhash(this.connection.commitment); addFundsTx.recentBlockhash = hash.blockhash; if (signers.length > 0) addFundsTx.partialSign(...signers); return addFundsTx; }); } createWithdrawTx(ddcaAccountAddress, withdrawAmount) { return __awaiter(this, void 0, void 0, function* () { if (!ddcaAccountAddress) throw new Error("Invalid param 'ddcaAccountAddress'"); if (!withdrawAmount || withdrawAmount <= 0) throw new Error("Invalid param 'withdrawAmount'"); const ddcaAccount = yield this.program.account.ddcaAccount.fetch(ddcaAccountAddress); if (!ddcaAccount) { throw new Error(`No DDCA account was found for address: ${ddcaAccountAddress}`); } if (ddcaAccount.ownerAccAddr.toBase58() !== this.ownerAccountAddress.toBase58()) { throw new Error(`DDCA account: ${ddcaAccountAddress} ins not owned by this owner`); } const toMint = ddcaAccount.toMint; //owner token account (to) const ownerToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, this.ownerAccountAddress); //ddca associated token account (to) const ddcaToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, ddcaAccountAddress, true); //ddca operating token account (to) const ddcaOperatingToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, DDCA_OPERATING_ACCOUNT_ADDRESS); // Instructions let ixs = new Array(); let ownerToAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ownerToTokenAccountAddress, toMint, this.ownerAccountAddress, this.ownerAccountAddress, this.connection); if (ownerToAtaCreateInstruction !== null) ixs.push(ownerToAtaCreateInstruction); let ddcaOperatingToAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ddcaOperatingToTokenAccountAddress, toMint, DDCA_OPERATING_ACCOUNT_ADDRESS, this.ownerAccountAddress, this.connection); if (ddcaOperatingToAtaCreateInstruction !== null) ixs.push(ddcaOperatingToAtaCreateInstruction); if (ixs.length === 0) ixs = undefined; if (this.verbose) { console.log("TEST PARAMETERS:"); console.log(" Program ID: " + this.program.programId); console.log(" payer.address: " + this.ownerAccountAddress); console.log(" toMint: " + toMint); console.log(); console.log(" ownerAccountAddress: " + this.ownerAccountAddress); console.log(" ownerToTokenAccountAddress: " + ownerToTokenAccountAddress); console.log(); console.log(" ddcaAccountPda: " + ddcaAccountAddress); console.log(" ddcaToTokenAccountAddress: " + ddcaToTokenAccountAddress); console.log(); console.log(" DDCA_OPERATING_ACCOUNT_ADDRESS: " + DDCA_OPERATING_ACCOUNT_ADDRESS); console.log(" ddcaOperatingFromTokenAccountAddress: " + ddcaOperatingToTokenAccountAddress); console.log(); console.log(" TOKEN_PROGRAM_ID: " + spl_token_1.TOKEN_PROGRAM_ID); console.log(); } const withdrawAmountBn = new anchor_1.BN(withdrawAmount * Math.pow(10, ddcaAccount.toMintDecimals)); const closeTx = yield this.program.transaction.withdraw(withdrawAmountBn, { accounts: { // owner ownerAccount: this.ownerAccountAddress, ownerToTokenAccount: ownerToTokenAccountAddress, // ddca ddcaAccount: ddcaAccountAddress, ddcaToTokenAccount: ddcaToTokenAccountAddress, // operating operatingAccount: DDCA_OPERATING_ACCOUNT_ADDRESS, operatingToTokenAccount: ddcaOperatingToTokenAccountAddress, toMint: toMint, // system accounts rent: anchor.web3.SYSVAR_RENT_PUBKEY, systemProgram: SYSTEM_PROGRAM_ID, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID }, instructions: ixs, }); closeTx.feePayer = this.ownerAccountAddress; let hash = yield this.connection.getRecentBlockhash(this.connection.commitment); closeTx.recentBlockhash = hash.blockhash; return closeTx; }); } createCloseTx(ddcaAccountAddress) { return __awaiter(this, void 0, void 0, function* () { const ddcaAccountToClose = yield this.program.account.ddcaAccount.fetch(ddcaAccountAddress); if (!ddcaAccountToClose) { throw new Error(`No DDCA account was found for address: ${ddcaAccountAddress}`); } const fromMint = ddcaAccountToClose.fromMint; const toMint = ddcaAccountToClose.toMint; //owner token account (from) const ownerFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, this.ownerAccountAddress); //owner token account (to) const ownerToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, this.ownerAccountAddress); //ddca associated token account (from) const ddcaFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, ddcaAccountAddress, true); //ddca associated token account (to) const ddcaToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, ddcaAccountAddress, true); //ddca operating token account (from) const ddcaOperatingFromTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, fromMint, DDCA_OPERATING_ACCOUNT_ADDRESS); //ddca operating token account (to) const ddcaOperatingToTokenAccountAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, toMint, DDCA_OPERATING_ACCOUNT_ADDRESS); // Instructions let ixs = new Array(); let ownerFromAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ownerFromTokenAccountAddress, fromMint, this.ownerAccountAddress, this.ownerAccountAddress, this.connection); if (ownerFromAtaCreateInstruction !== null) ixs.push(ownerFromAtaCreateInstruction); let ownerToAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ownerToTokenAccountAddress, toMint, this.ownerAccountAddress, this.ownerAccountAddress, this.connection); if (ownerToAtaCreateInstruction !== null) ixs.push(ownerToAtaCreateInstruction); let ddcaOperatingFromAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ddcaOperatingFromTokenAccountAddress, fromMint, DDCA_OPERATING_ACCOUNT_ADDRESS, this.ownerAccountAddress, this.connection); if (ddcaOperatingFromAtaCreateInstruction !== null) ixs.push(ddcaOperatingFromAtaCreateInstruction); let ddcaOperatingToAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ddcaOperatingToTokenAccountAddress, toMint, DDCA_OPERATING_ACCOUNT_ADDRESS, this.ownerAccountAddress, this.connection); if (ddcaOperatingToAtaCreateInstruction !== null) ixs.push(ddcaOperatingToAtaCreateInstruction); if (ixs.length === 0) ixs = undefined; if (this.verbose) { console.log("TEST PARAMETERS:"); console.log(" Program ID: " + this.program.programId); console.log(" payer.address: " + this.ownerAccountAddress); console.log(); console.log(" ownerAccountAddress: " + this.ownerAccountAddress); console.log(" ownerFromTokenAccountAddress: " + ownerFromTokenAccountAddress); console.log(" ownerToTokenAccountAddress: " + ownerToTokenAccountAddress); console.log(); console.log(" ddcaAccountAddress: " + ddcaAccountAddress); console.log(" ddcaFromTokenAccountAddress: " + ddcaFromTokenAccountAddress); console.log(" ddcaToTokenAccountAddress: " + ddcaToTokenAccountAddress); console.log(); console.log(" DDCA_OPERATING_ACCOUNT_ADDRESS: " + DDCA_OPERATING_ACCOUNT_ADDRESS); console.log(" ddcaOperatingFromTokenAccountAddress: " + ddcaOperatingFromTokenAccountAddress); console.log(" ddcaOperatingToTokenAccountAddress: " + ddcaOperatingToTokenAccountAddress); console.log(); console.log(" TOKEN_PROGRAM_ID: " + spl_token_1.TOKEN_PROGRAM_ID); console.log(); } const closeTx = yield this.program.transaction.close({ accounts: { // owner ownerAccount: this.ownerAccountAddress, wakeAccount: ddcaAccountToClose.wakeAccAddr, ownerFromTokenAccount: ownerFromTokenAccountAddress, ownerToTokenAccount: ownerToTokenAccountAddress, // ddca fromMint: fromMint, toMint: toMint, ddcaAccount: ddcaAccountAddress, ddcaFromTokenAccount: ddcaFromTokenAccountAddress, ddcaToTokenAccount: ddcaToTokenAccountAddress, // operating operatingAccount: DDCA_OPERATING_ACCOUNT_ADDRESS, operatingFromTokenAccount: ddcaOperatingFromTokenAccountAddress, operatingToTokenAccount: ddcaOperatingToTokenAccountAddress, // system accounts rent: anchor.web3.SYSVAR_RENT_PUBKEY, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID, systemProgram: SYSTEM_PROGRAM_ID, associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, }, instructions: ixs, }); closeTx.feePayer = this.ownerAccountAddress; let hash = yield this.connection.getRecentBlockhash(this.connection.commitment); closeTx.recentBlockhash = hash.blockhash; return closeTx; }); } updateCloseTx(ddcaAccountAddress, closeTxSignedByOwner) { return __awaiter(this, void 0, void 0, function* () { if (!closeTxSignedByOwner) { throw Error("Invalid close transaction"); } const closeTxSerialized = closeTxSignedByOwner.serialize({ requireAllSignatures: false }); const b64CloseTx = bytes_1.base64.encode(closeTxSerialized); const b64CloseTxUpdated = yield this.sendCloseTxUpdateRequest(ddcaAccountAddress.toBase58(), b64CloseTx); const closeTxUpdatedBytes = bytes_1.base64.decode(b64CloseTxUpdated); const closeTxUpdated = web3_js_1.Transaction.from(closeTxUpdatedBytes); return closeTxUpdated; }); } listDdcas(stortByStartTs = true, desc = true) { return __awaiter(this, void 0, void 0, function* () { const walletFilter = [ { dataSize: 500 }, { memcmp: { offset: 8, bytes: this.ownerAccountAddress.toBase58() } } ]; const ddcaAccounts = yield this.program.account.ddcaAccount.all(walletFilter); const results = ddcaAccounts.map(x => { var _a; const values = { ddcaAccountAddress: x.publicKey.toBase58(), fromMint: x.account.fromMint.toBase58(), toMint: x.account.toMint.toBase58(), amountPerSwap: x.account.amountPerSwap.toNumber() / (Math.pow(10, x.account.fromMintDecimals)), totalDepositsAmount: x.account.totalDepositsAmount.toNumber() / (Math.pow(10, x.account.fromMintDecimals)), createdSlot: (_a = x.account.createdSlot) === null || _a === void 0 ? void 0 : _a.toNumber(), startTs: x.account.startTs.toNumber(), startUtc: tsToUTCString(x.account.startTs.toNumber()), intervalInSeconds: x.account.intervalInSeconds.toNumber(), wakeAccountAddress: x.account.wakeAccAddr.toBase58(), lastCompletedSwapTs: x.account.lastCompletedSwapTs.toNumber(), lastCompletedSwapUtc: tsToUTCString(x.account.lastCompletedSwapTs.toNumber()), isPaused: x.account.isPaused, }; return values; }); if (stortByStartTs) { if (desc) { results.sort((a, b) => b.startTs - a.startTs); } else { results.sort((a, b) => a.startTs - b.startTs); } } return results; }); } getDdca(ddcaAccountAddress) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { const ddcaAccount = yield this.program.account.ddcaAccount.fetch(ddcaAccountAddress); if (ddcaAccount === null) return null; const fromTokenResponse = yield this.connection.getTokenAccountBalance(ddcaAccount.fromTaccAddr); const fromTokenBalance = (_a = fromTokenResponse.value.uiAmount) !== null && _a !== void 0 ? _a : 0; const toTokenResponse = yield this.connection.getTokenAccountBalance(ddcaAccount.toTaccAddr); const toTokenBalance = (_b = toTokenResponse.value.uiAmount) !== null && _b !== void 0 ? _b : 0; let startTs = ddcaAccount.startTs.toNumber(); let interval = ddcaAccount.intervalInSeconds.toNumber(); let lastCompletedSwapTs = ddcaAccount.lastCompletedSwapTs.toNumber(); let nowTs = Math.floor(Date.now() / 1000); let maxDiffInSecs = Math.min(Math.floor(interval / 100), 3600); // +/-1% up to 3600 sec (ok for min interval = 5 min) let prevCheckpoint = Math.floor((nowTs - startTs) / interval); let prevTs = startTs + prevCheckpoint * interval; let nextCheckpoint = prevCheckpoint + 1; let nextTs = startTs + nextCheckpoint * interval; let nextScheduledTs; // console.log("DDCA schedule: { start_ts: %s, interval:%s, last_completed_ts: %s, now_ts: %s, max_diff_in_secs: %s, low: %s, high: %s, low_ts: %s, high_ts: %s }", // startTs, interval, lastCompletedSwapTs, nowTs, maxDiffInSecs, prevCheckpoint, nextCheckpoint, prevTs, nextTs); if (nowTs <= prevTs + maxDiffInSecs) { // we are in the prevTs swap window if (lastCompletedSwapTs != prevTs) // we are in the prevTs swap window and we haven't consumed the swap yet nextScheduledTs = prevTs; else // we are in the prevTs swap window but we already consumed the swap nextScheduledTs = nextTs; } else if (nowTs >= nextTs - maxDiffInSecs) { // we are in the nextTs swap window if (lastCompletedSwapTs != nextTs) // we are in the nextTs swap window and we haven't consumed the swap yet nextScheduledTs = nextTs; else // we are in the nextTs swap window but we already consumed the swap, so next in schedule will be nextCheckpoint + 1 nextScheduledTs = startTs + (nextCheckpoint + 1) * interval; } else { // we are in between prevTs and nextTs but no close ennough to any nextScheduledTs = nextTs; } const amountPerSwap = ddcaAccount.amountPerSwap.toNumber() / (Math.pow(10, ddcaAccount.fromMintDecimals)); const remainingSwapsCount = Math.floor(fromTokenBalance / amountPerSwap); let fromBalanceWillRunOutByUtc = ''; if (remainingSwapsCount > 0) { fromBalanceWillRunOutByUtc = tsToUTCString(nextScheduledTs + (remainingSwapsCount - 1) * interval); } const ddca = { ddcaAccountAddress: ddcaAccountAddress.toBase58(), fromMint: ddcaAccount.fromMint.toBase58(), toMint: ddcaAccount.toMint.toBase58(), amountPerSwap: amountPerSwap, totalDepositsAmount: ddcaAccount.totalDepositsAmount.toNumber() / (Math.pow(10, ddcaAccount.fromMintDecimals)), createdSlot: (_c = ddcaAccount.createdSlot) === null || _c === void 0 ? void 0 : _c.toNumber(), startTs: startTs, startUtc: tsToUTCString(startTs), intervalInSeconds: interval, wakeAccountAddress: ddcaAccount.wakeAccAddr.toBase58(), lastCompletedSwapTs: lastCompletedSwapTs, lastCompletedSwapUtc: tsToUTCString(lastCompletedSwapTs), isPaused: ddcaAccount.isPaused, fromBalance: fromTokenBalance, toBalance: toTokenBalance, fromBalanceWillRunOutByUtc: fromBalanceWillRunOutByUtc, nextScheduledSwapUtc: tsToUTCString(nextScheduledTs), swapCount: ddcaAccount.swapCount.toNumber(), swapAvgRate: ddcaAccount.swapAvgRate.toNumber() / (Math.pow(10, ddcaAccount.toMintDecimals)), lastDepositTs: ddcaAccount.lastDepositTs.toNumber(), lastDepositSlot: ddcaAccount.lastDepositSlot.toNumber(), lastDepositedtUtc: tsToUTCString(ddcaAccount.lastDepositTs), }; return ddca; }); } /** * ToString */ toString() { var _a, _b, _c, _d, _e, _f, _g; return `{ rpcUrl: ${this.rpcUrl}, ownerAccountAddress: ${(_a = this.ownerAccountAddress) === null || _a === void 0 ? void 0 : _a.toBase58()}, commitment: ${(_c = (_b = this.provider) === null || _b === void 0 ? void 0 : _b.opts) === null || _c === void 0 ? void 0 : _c.commitment}, preflightCommitment: ${(_e = (_d = this.provider) === null || _d === void 0 ? void 0 : _d.opts) === null || _e === void 0 ? void 0 : _e.preflightCommitment}, skipPreflight: ${(_g = (_f = this.provider) === null || _f === void 0 ? void 0 : _f.opts) === null || _g === void 0 ? void 0 : _g.skipPreflight} }`; } /** * Attempts to parse an rpc error. Experimental */ tryParseRpcError(rawError) { const errorLogs = rawError === null || rawError === void 0 ? void 0 : rawError.logs; if (errorLogs) { for (let i = 0; i < errorLogs.length; i++) { const logEntry = errorLogs[i]; if (logEntry.startsWith(`Program ${HLA_PROGRAM_ADDRESS} failed:`)) return null; } } const idlErrors = (0, anchor_2.parseIdlErrors)(this.program.idl); const parsedError = anchor_2.ProgramError.parse(rawError, idlErrors); return parsedError; } getRpcVersion() { return __awaiter(this, void 0, void 0, function* () { if (!this.rpcVersion) this.rpcVersion = yield this.connection.getVersion(); return this.rpcVersion; }); } getActivity(ddcaAccountAddress, limit = 5, includeFailed = false) { return __awaiter(this, void 0, void 0, function* () { const ddcaAccount = yield this.program.account.ddcaAccount.fetch(ddcaAccountAddress); if (ddcaAccount === null) return []; if (typeof ddcaAccountAddress === "string") { ddcaAccountAddress = new web3_js_1.PublicKey(ddcaAccountAddress); } const confirmedSignatures = yield this.connection.getSignaturesForAddress(ddcaAccountAddress, { limit: limit }, 'finalized'); let confirmedTxs = null; try { confirmedTxs = yield this.connection.getParsedConfirmedTransactions(confirmedSignatures.map(tx => tx.signature), 'finalized'); } catch (error) { } if (!confirmedTxs || confirmedTxs.length === 0) { confirmedTxs = []; for (let sigInfo of confirmedSignatures) { const tx = yield this.connection.getParsedConfirmedTransaction(sigInfo.signature); confirmedTxs.push(tx); } } let ddcaActivities = Array(); for (let tx of confirmedTxs) { if (!tx) { continue; } try { let ddcaActivity = this.parseTransaction(tx, ddcaAccount); if (ddcaActivity && (includeFailed || ddcaActivity.succeeded)) { ddcaActivities.push(ddcaActivity); } } catch (error) { console.log(error); } } return ddcaActivities; }); } parseTransaction(tx, rawDdcaAccount) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; for (let ix of tx.transaction.message.instructions) { ix = ix; if (!(ix === null || ix === void 0 ? void 0 : ix.data)) { continue; } let sighash = bytes_1.bs58.encode(bytes_1.bs58.decode(ix.data).slice(0, 8)); const sighashLayouts = this.program.coder.instruction["sighashLayouts"]; const decoder = sighashLayouts.get(sighash); if (!decoder) { console.log("No decoder found for sighash:", sighash); return null; } // let buffer = bs58.decode(ix.data); // const decodedTxData = decoder.layout.decode(buffer); let fromMint = null; let fromUiAmountDelta = 0; let toMint = null; let toUiAmountDelta = 0; let action = "unknown"; const txAccountAddresses = tx.transaction.message.accountKeys.map(x => x.pubkey.toBase58()); let fromTokenAccountAddress = new web3_js_1.PublicKey(rawDdcaAccount.fromTaccAddr).toBase58(); let toTokenAccountAddress = new web3_js_1.PublicKey(rawDdcaAccount.toTaccAddr).toBase58(); switch (decoder.name) { case "create": action = "deposited"; [fromMint, fromUiAmountDelta] = calculateTokenBlanaceDelta(fromTokenAccountAddress, txAccountAddresses, (_a = tx.meta) === null || _a === void 0 ? void 0 : _a.preTokenBalances, (_b = tx.meta) === null || _b === void 0 ? void 0 : _b.postTokenBalances);