@mean-dao/ddca
Version:
Typescript library to interact with the Decentralized DCA program
790 lines (789 loc) • 57.6 kB
JavaScript
"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);