UNPKG

@mean-dao/payment-streaming

Version:
937 lines (936 loc) 60.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PaymentStreaming = void 0; /** * Solana */ const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const anchor_1 = require("@project-serum/anchor"); /** * MSP */ const types_1 = require("./types"); const utils_1 = require("./utils"); const constants_1 = require("./constants"); const instructions = __importStar(require("./instructions")); /** * TS Client to interact with the Payment Streaming (PS) program. */ class PaymentStreaming { /** * Creates a Payment Streaming client * * @param connection Connectin to use * @param programId Payment Streaming program ID. By default is the mainnet ID * @param blockhashCommitment Commitment used to fetch the latest `blockhash` * and corresponding `lastValidBlockHeight` */ constructor(connection, programId = constants_1.PAYMENT_STREAMING_PROGRAM_ID, blockhashCommitment) { this.connection = connection; this.blockhashCommitment = blockhashCommitment !== null && blockhashCommitment !== void 0 ? blockhashCommitment : 'confirmed'; this.program = (0, utils_1.createProgram)(this.connection, programId); } getStream(id) { return __awaiter(this, void 0, void 0, function* () { return (0, utils_1.getStream)(this.program, id); }); } getStreamRaw(id) { return __awaiter(this, void 0, void 0, function* () { return (0, utils_1.getStreamEventData)(this.program, id); }); } refreshStream(streamInfo_1) { return __awaiter(this, arguments, void 0, function* (streamInfo, hardUpdate = false) { const copyStreamInfo = Object.assign({}, streamInfo); if (hardUpdate) { const streamId = typeof copyStreamInfo.id === 'string' ? new web3_js_1.PublicKey(copyStreamInfo.id) : copyStreamInfo.id; return (0, utils_1.getStream)(this.program, streamId); } return (0, utils_1.getStreamCached)(copyStreamInfo); }); } listStreams(_a) { return __awaiter(this, arguments, void 0, function* ({ psAccountOwner, psAccount, beneficiary, category = undefined, subCategory = undefined, }) { return (0, utils_1.listStreams)(this.program, psAccountOwner, psAccount, beneficiary, category, subCategory); }); } refreshStreams(streamInfoList_1, psAccountOwner_1, psAccount_1, beneficiary_1) { return __awaiter(this, arguments, void 0, function* (streamInfoList, psAccountOwner, psAccount, beneficiary, hardUpdate = false) { if (hardUpdate) { yield (0, utils_1.listStreams)(this.program, psAccountOwner, psAccount, beneficiary); } return (0, utils_1.listStreamsCached)(streamInfoList); }); } /** * * @param id The address of the stream * @param before The signature to start searching backwards from. * @param limit The max amount of elements to retrieve * @param commitment Commitment to query the stream activity * @returns */ listStreamActivity(id_1) { return __awaiter(this, arguments, void 0, function* (id, before = '', limit = 10, commitment) { const accountInfo = yield this.connection.getAccountInfo(id, commitment); if (!accountInfo) { throw Error('Stream not found'); } return (0, utils_1.listStreamActivity)(this.program, id, before, limit, commitment); }); } getAccount(id, commitment) { return __awaiter(this, void 0, void 0, function* () { const accountInfo = yield this.program.account.treasury.getAccountInfo(id, commitment); if (!accountInfo) { throw Error('Payment Streaming account not found'); } return (0, utils_1.getAccount)(this.program, id); }); } listAccounts(owner, excludeAutoClose, category, subCategory) { return __awaiter(this, void 0, void 0, function* () { return (0, utils_1.listAccounts)(this.program, owner, excludeAutoClose, category, subCategory); }); } getStreamTemplate(psAccount) { return __awaiter(this, void 0, void 0, function* () { const [template] = (0, utils_1.findStreamTemplateAddress)(psAccount, this.program.programId); return yield (0, utils_1.getStreamTemplate)(this.program, template); }); } prepareTransaction(transaction, feePayer) { return __awaiter(this, void 0, void 0, function* () { transaction.feePayer = feePayer; const { blockhash, lastValidBlockHeight } = yield this.connection.getLatestBlockhash(this.blockhashCommitment); transaction.recentBlockhash = blockhash; transaction.lastValidBlockHeight = lastValidBlockHeight; }); } createTransaction(instructions, feePayer, partialSigners) { return __awaiter(this, void 0, void 0, function* () { const transaction = new web3_js_1.Transaction().add(...instructions); yield this.prepareTransaction(transaction, feePayer); if (partialSigners === null || partialSigners === void 0 ? void 0 : partialSigners.length) { transaction.partialSign(...partialSigners); } return transaction; }); } /** * Contructs a transaction to perform a simple transfer of tokens to a * beneficiary using the Token program. * * @param accounts - Transaction accounts * @param amount - The token amount to be sent */ buildTransferTransaction(_a, amount_1) { return __awaiter(this, arguments, void 0, function* ({ sender, feePayer, beneficiary, mint }, amount) { const ixs = []; const amountBN = new anchor_1.BN(amount); feePayer = feePayer || sender; if (mint.equals(constants_1.NATIVE_SOL_MINT)) { ixs.push(web3_js_1.SystemProgram.transfer({ fromPubkey: sender, toPubkey: beneficiary, lamports: BigInt(amountBN.toString()), })); } else { const senderToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mint, sender, true); const senderTokenInfo = yield this.connection.getAccountInfo(senderToken); if (!senderTokenInfo) { throw Error('Sender token account not found'); } let beneficiaryToken = beneficiary; const beneficiaryAccountInfo = yield this.connection.getAccountInfo(beneficiary); if (!beneficiaryAccountInfo || !beneficiaryAccountInfo.owner.equals(spl_token_1.TOKEN_PROGRAM_ID)) { beneficiaryToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mint, beneficiary, true); const beneficiaryTokenAccountInfo = yield this.connection.getAccountInfo(beneficiaryToken); if (!beneficiaryTokenAccountInfo) { ixs.push(spl_token_1.Token.createAssociatedTokenAccountInstruction(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mint, beneficiaryToken, beneficiary, sender)); } } else { // At this point the beneficiaryToken is either a mint or a token account // Let's make sure it is a token account of the passed mint const tokenClient = new spl_token_1.Token(this.connection, mint, spl_token_1.TOKEN_PROGRAM_ID, web3_js_1.Keypair.generate()); try { const beneficiaryTokenInfo = yield tokenClient.getAccountInfo(beneficiaryToken); if (!beneficiaryTokenInfo) throw Error('Reciever is not a token account'); } catch (error) { throw Error('Reciever is not a token account'); } } ixs.push(spl_token_1.Token.createTransferInstruction(spl_token_1.TOKEN_PROGRAM_ID, senderToken, beneficiaryToken, sender, [], new spl_token_1.u64(amountBN.toString()))); } const tx = yield this.createTransaction(ixs, feePayer); return { transaction: tx }; }); } /** * Returns a transaction for scheduling a transfer as a stream without rate. * * @param accounts - Transaction accoutns * @param amount - The token amount to be allocated to the stream * @param startUtc - The date on which the transfer will be executed * @param streamName - The name of the transfer * @param tokenFeePayedByOwner - If true, the protocol token fees will be paid by the * {@link owner}, otherwise by the {@link beneficiary} at withdraw time */ buildScheduleTransferTransaction(_a, amount_1, startUtc_1, streamName_1) { return __awaiter(this, arguments, void 0, function* ({ owner, feePayer, beneficiary, mint }, amount, startUtc, streamName, tokenFeePayedByOwner = false) { feePayer = feePayer || owner; let autoWSol = false; if (mint.equals(constants_1.NATIVE_SOL_MINT)) { mint = constants_1.NATIVE_WSOL_MINT; autoWSol = true; } const ixs = []; const txSigners = []; const ownerToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mint, owner, true); const ownerTokenInfo = yield this.connection.getAccountInfo(ownerToken); yield this.ensureAutoWrapSolInstructions(autoWSol, amount, owner, ownerToken, ownerTokenInfo, ixs, txSigners); // Add create PS account instruction const { instruction: createAccountIx, psAccount: psAccount, psAccountToken: psAccountToken, } = yield instructions.buildCreateAccountInstruction(this.program, { owner, feePayer, mint, }, streamName ? `${streamName} (account)` : streamName, types_1.AccountType.Open, true, false); ixs.push(createAccountIx); // Add add funds instruction const { instruction: addFundsIx, feeAccountToken } = yield instructions.buildAddFundsInstruction(this.program, { psAccount, psAccountMint: mint, contributor: owner, feePayer: owner, psAccountToken, contributorToken: ownerToken, }, new anchor_1.BN(amount)); ixs.push(addFundsIx); // Add CreateStream instruction since the OTP is scheduled const now = new Date(); const start = !startUtc || startUtc.getTime() < now.getTime() ? now : startUtc; const startUtcInSeconds = Math.round(start.getTime() / 1000); const { instruction: createStreamIx, stream, streamKey, } = yield instructions.buildCreateStreamInstruction(this.program, { psAccount, psAccountMint: mint, owner, feePayer, beneficiary, feeAccountToken, }, streamName !== null && streamName !== void 0 ? streamName : '', new anchor_1.BN(0), new anchor_1.BN(0), new anchor_1.BN(amount), new anchor_1.BN(startUtcInSeconds), new anchor_1.BN(amount), new anchor_1.BN(0), tokenFeePayedByOwner, false); ixs.push(createStreamIx); if (streamKey) txSigners.push(streamKey); const tx = yield this.createTransaction(ixs, feePayer, txSigners); return { transaction: tx, stream }; }); } /** * Constructs a transaction to create a recurring payment at a given rate to * start immediately or scheduled. * * @param owner - Transaction accounts * @param streamName - The name of the transfer * @param rateAmount - Token amount that will be streamed in every * {@link rateIntervalInSeconds} period * @param rateIntervalInSeconds - Period of time in seconds in which the * {@link rateAmount} will be streamed progressively second by second * @param allocationAssigned - The token amount to be allocated to the stream * @param startUtc - The date and time on which the transfer will be executed * @param tokenFeePayedByOwner - If true, the protocol token fees will be * paid from {@link psAccountToken} and deposited upfront by the owner. If * false, the beneficiary will paid the token fees at withdraw time */ buildStreamPaymentTransaction(_a, streamName_1, rateAmount_1, rateIntervalInSeconds_1, allocationAssigned_1, startUtc_1) { return __awaiter(this, arguments, void 0, function* ({ owner, feePayer, beneficiary, mint }, streamName, rateAmount, rateIntervalInSeconds, allocationAssigned, startUtc, tokenFeePayedByOwner = false) { feePayer = feePayer || owner; if (owner.equals(beneficiary)) { throw Error('Beneficiary can not be the same account owner'); } let autoWSol = false; if (mint.equals(constants_1.NATIVE_SOL_MINT)) { mint = constants_1.NATIVE_WSOL_MINT; autoWSol = true; } const ixs = []; const txSigners = []; // Add create PS account instruction const { instruction: createAccountIx, psAccount, psAccountToken, } = yield instructions.buildCreateAccountInstruction(this.program, { owner, feePayer, mint, }, streamName ? `${streamName} (account)` : streamName, types_1.AccountType.Open, true, false); ixs.push(createAccountIx); // Get the PS account owner token account const ownerToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mint, owner, true); const ownerTokenInfo = yield this.connection.getAccountInfo(ownerToken); yield this.ensureAutoWrapSolInstructions(autoWSol, new anchor_1.BN(allocationAssigned), owner, ownerToken, ownerTokenInfo, ixs, txSigners); // add AddFunds instruction const { instruction: addFundsIx } = yield instructions.buildAddFundsInstruction(this.program, { psAccount, psAccountMint: mint, psAccountToken, contributor: owner, feePayer, }, new anchor_1.BN(allocationAssigned)); ixs.push(addFundsIx); // Add CreateStream instruction const now = new Date(); const start = !startUtc || startUtc.getTime() < Date.now() ? now : startUtc; const startUtcInSeconds = Math.round(start.getTime() / 1000); const { instruction: createStreamIx, stream, streamKey, } = yield instructions.buildCreateStreamInstruction(this.program, { psAccount, psAccountMint: mint, owner, beneficiary, feePayer, }, streamName !== null && streamName !== void 0 ? streamName : '', new anchor_1.BN(rateAmount), new anchor_1.BN(rateIntervalInSeconds), new anchor_1.BN(allocationAssigned), new anchor_1.BN(startUtcInSeconds), new anchor_1.BN(0), new anchor_1.BN(0), tokenFeePayedByOwner, false); ixs.push(createStreamIx); if (streamKey) txSigners.push(streamKey); const tx = yield this.createTransaction(ixs, feePayer, txSigners); return { transaction: tx, psAccount, psAccountToken, stream }; }); } /** * Constructs a transaction for creating a PS account. * * @param accounts - Transaction accounts * @param name - Name for the new account * @param type - Either Open or Lock. Under locked accounts, once a stream * starts it cannot be paused or closed, they will run until out of funds * @param solFeePayedFromAccount * @param category * @param subCategory * @returns */ buildCreateAccountTransaction(_a, name_1, type_1) { return __awaiter(this, arguments, void 0, function* ({ owner, feePayer, mint }, name, type, solFeePayedFromAccount = false, category = types_1.Category.default, subCategory = types_1.SubCategory.default) { feePayer = feePayer || owner; if (mint.equals(constants_1.NATIVE_SOL_MINT)) { mint = constants_1.NATIVE_WSOL_MINT; } // Add create PS account instruction const { instruction: createAccountIx, psAccount, psAccountToken, } = yield instructions.buildCreateAccountInstruction(this.program, { owner, feePayer, mint, }, name || '', type, false, solFeePayedFromAccount, category, subCategory); const tx = yield this.createTransaction([createAccountIx], feePayer); return { transaction: tx, psAccount, psAccountToken }; }); } /** * Constructs a transaction for creating a stream under a PS account. * * @param accounts - Transaction accounts * @param streamName - A name for the new stream * @param rateAmount - Token amount that will be streamed in every * {@link rateIntervalInSeconds} period * @param rateIntervalInSeconds - Period of time in seconds in which the * {@link rateAmount} will be streamed progressively second by second * @param allocationAssigned - Total token amount allocated to the new stream * out of the containing PS account's unallocated balance * @param startUtc - Date and time when the stream will start * @param cliffVestAmount - Token amount that is immediatelly withdrawable * by the {@link beneficiary} as soon as the stream starts. When * {@link cliffVestPercent} is greater than zero, this value will be ignored * @param cliffVestPercent - The 0-100 percentage of * {@link allocationAssigned} that is immediatelly withdrawable by the * {@link beneficiary} as soon as the stream starts. It takes precedence over * {@link cliffVestAmount}, i.e. when this value is greater than zero, * {@link cliffVestAmount} will be ignored * @param tokenFeePayedFromAccount - If true, the protocol token fees will be * paid from the PS account ATA and deposited upfront during stream * creation or allocation. If false, the beneficiary will pay for token fees * at withdraw time * @param usePda - If true, the new stream will be created at an address * derived from the program */ buildCreateStreamTransaction(_a, streamName_1, rateAmount_1, rateIntervalInSeconds_1, allocationAssigned_1, startUtc_1) { return __awaiter(this, arguments, void 0, function* ({ psAccount, owner, feePayer, beneficiary, }, streamName, rateAmount, rateIntervalInSeconds, allocationAssigned, startUtc, cliffVestAmount = 0, cliffVestPercent = 0, tokenFeePayedFromAccount = false, usePda = false) { feePayer = feePayer || owner; if (owner.equals(beneficiary)) { throw Error('Beneficiary can not be the same as the account owner'); } if (cliffVestPercent < 0 || cliffVestPercent > 100) { throw Error('Invalid cliffVestPercent'); } const psAccountInfo = yield (0, utils_1.getAccount)(this.program, psAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } const psAccountMint = new web3_js_1.PublicKey(psAccountInfo.mint); const cliffVestPercentValue = Math.round(cliffVestPercent * constants_1.CLIFF_PERCENT_NUMERATOR); const now = new Date(); const startDate = startUtc && startUtc.getTime() >= now.getTime() ? startUtc : now; const startUnixTimestamp = (0, utils_1.toUnixTimestamp)(startDate); const { instruction: createStreamIx, streamKey, stream, } = yield instructions.buildCreateStreamInstruction(this.program, { psAccount, psAccountMint, owner, feePayer, beneficiary }, streamName, new anchor_1.BN(rateAmount), new anchor_1.BN(rateIntervalInSeconds), new anchor_1.BN(allocationAssigned), new anchor_1.BN(startUnixTimestamp), new anchor_1.BN(cliffVestAmount), new anchor_1.BN(cliffVestPercentValue), tokenFeePayedFromAccount, usePda); const tx = yield this.createTransaction([createStreamIx], feePayer, streamKey ? [streamKey] : undefined); return { transaction: tx, stream: stream, }; }); } /** * Constructs a transaction to create vesting contract account together with a * configuration account (template) for creating vesting streams. * * @param accounts - Transaction accounts * @param name - Name for the vesting contract account * @param type - Either Open or Lock. Under locked accounts, once a stream * starts it cannot be paused or closed, they will run until out of funds * @param solFeePayedFromAccount - If true, protocol SOL fees will be payed * from the newly created account, otherwise from the {@link feePayer} account * @param numberOfIntervals - Number of intervals of duration * {@link rateIntervalInSeconds} in which the allocation assigned will be * streamed * @param intervalUnit - Duration of each interval (E.g. for 1 minute * intervals pass {@link TimeUnit.Minute}, for 1 hour intervals pass * {@link TimeUnit.Hour} and so on). See {@link TimeUnit} enum * @param fundingAmount - The token amount to fund the newly creawted * vesting account if a value greater than 0 is provided * @param vestingCategory - Category for the vesting contract account * @param startUtc - The vesting contract start date * @param cliffVestPercent - When a vesting stream is created using this * template, this is the 0-100 percentage of the allocation assigned to the * stream that is immediatelly withdrawable by the beneficiary as soon as the * vesting stream starts * @param tokenFeePayedFromAccount - If true, the protocol token fees will be * paid from PS account ATA and deposited upfront during stream * creation or allocation. If false, the beneficiary will pay for token fees * at withdraw time */ buildCreateVestingAccountTransaction(_a, name_1, type_1, solFeePayedFromAccount_1, numberOfIntervals_1, intervalUnit_1, fundingAmount_1, vestingCategory_1, startUtc_1) { return __awaiter(this, arguments, void 0, function* ({ owner, feePayer, mint }, name, type, solFeePayedFromAccount, numberOfIntervals, intervalUnit, fundingAmount, vestingCategory, startUtc, cliffVestPercent = 0, tokenFeePayedFromAccount = false) { feePayer = feePayer || owner; // convert interval unit to seconds const rateIntervalInSeconds = intervalUnit; let autoWSol = false; if (mint.equals(constants_1.NATIVE_SOL_MINT)) { mint = constants_1.NATIVE_WSOL_MINT; autoWSol = true; } const ixs = []; const txSigners = []; const cliffVestPercentValue = cliffVestPercent ? cliffVestPercent * constants_1.CLIFF_PERCENT_NUMERATOR : 0; const now = new Date(); const startDate = startUtc && startUtc.getTime() >= now.getTime() ? startUtc : now; const startTs = (0, utils_1.toUnixTimestamp)(startDate); const { instruction: createAccountAndTemplateInstruction, psAccount, psAccountToken, template, } = yield instructions.buildCreateAccountAndTemplateInstruction(this.program, { owner, mint, feePayer }, name, type, solFeePayedFromAccount, { rateIntervalInSeconds: new anchor_1.BN(rateIntervalInSeconds), numberOfIntervals: new anchor_1.BN(numberOfIntervals), startTs: new anchor_1.BN(startTs), cliffVestPercent: new anchor_1.BN(cliffVestPercentValue), tokenFeePayedFromAccount, }, types_1.Category.vesting, vestingCategory); ixs.push(createAccountAndTemplateInstruction); const fundingAmountBN = new anchor_1.BN(fundingAmount); if (fundingAmountBN.gtn(0)) { const ownerToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mint, owner, true); const ownerTokenInfo = yield this.connection.getAccountInfo(ownerToken, 'recent'); yield this.ensureAutoWrapSolInstructions(autoWSol, fundingAmount, owner, ownerToken, ownerTokenInfo, ixs, txSigners); // add AddFunds instruction const { instruction: addFundsToVestingAccountIx } = yield instructions.buildAddFundsInstruction(this.program, { psAccount, psAccountToken, psAccountMint: mint, contributor: owner, contributorToken: ownerToken, feePayer, }, fundingAmountBN); ixs.push(addFundsToVestingAccountIx); } const tx = yield this.createTransaction(ixs, feePayer, txSigners); return { transaction: tx, vestingAccount: psAccount, vestingAccountToken: psAccountToken, template, }; }); } /** * Constructs a transaction for updating values of vesting account * template if no streams have been created yet. * * @param accounts - Transaction accounts * @param numberOfIntervals - The new number of intervals * @param intervalUnit - The new interval unit * @param startUtc - The new start date * @param cliffVestPercent - The new cliff percentage * @param tokenFeePayedFromAccount - The new tokenFeePayedFromAccount */ buildUpdateVestingTemplateTransaction(_a, numberOfIntervals_1, intervalUnit_1, startUtc_1, cliffVestPercent_1, tokenFeePayedFromAccount_1) { return __awaiter(this, arguments, void 0, function* ({ owner, feePayer, vestingAccount, }, numberOfIntervals, intervalUnit, startUtc, cliffVestPercent, tokenFeePayedFromAccount) { feePayer = feePayer || owner; const psAccountInfo = yield (0, utils_1.getAccount)(this.program, vestingAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } // Get the template const [template] = (0, utils_1.findStreamTemplateAddress)(vestingAccount, this.program.programId); const templateInfo = yield (0, utils_1.getStreamTemplate)(this.program, template); if (!templateInfo) { throw Error('Template account not found'); } if (psAccountInfo.totalStreams > 0) { throw Error('Cannot modify vesting account after streams have been created'); } if (numberOfIntervals && !intervalUnit) { throw Error('Interval unit is required'); } if (intervalUnit && !numberOfIntervals) { throw Error('Number of intervals is required'); } let newRateIntervalInSeconds = templateInfo.rateIntervalInSeconds; let newNumberOfIntervals = templateInfo.durationNumberOfUnits; if (numberOfIntervals && intervalUnit) { newRateIntervalInSeconds = intervalUnit; newNumberOfIntervals = numberOfIntervals; } const newClifPercentValue = cliffVestPercent !== undefined ? cliffVestPercent * constants_1.CLIFF_PERCENT_NUMERATOR : templateInfo.cliffVestPercent; let newStartTs = (0, utils_1.toUnixTimestamp)(new Date(templateInfo.startUtc)); if (startUtc) { const now = new Date(); const startDate = startUtc.getTime() >= now.getTime() ? startUtc : now; newStartTs = (0, utils_1.toUnixTimestamp)(startDate); } const newTokenFeePayedFromVestingAccount = tokenFeePayedFromAccount !== undefined ? tokenFeePayedFromAccount : templateInfo.feePayedByTreasurer; const { instruction: updateTemplateInstruction } = yield instructions.buildUpdateStreamTemplateInstruction(this.program, { psAccount: vestingAccount, template, owner, feePayer }, new anchor_1.BN(newRateIntervalInSeconds), new anchor_1.BN(newNumberOfIntervals), new anchor_1.BN(newStartTs), new anchor_1.BN(newClifPercentValue), newTokenFeePayedFromVestingAccount); const tx = yield this.createTransaction([updateTemplateInstruction], feePayer); return { transaction: tx }; }); } /** * Returns a list with the activity of a vesting account. * * @param vestingAccount - The vesting account * @param before - The signature to start searching backwards from. * @param limit - The max amount of elements to retrieve * @param commitment - Commitment to query the vesting account activity */ listAccountActivity(vestingAccount_1, before_1) { return __awaiter(this, arguments, void 0, function* (vestingAccount, before, limit = 10, commitment) { const accountInfo = yield this.connection.getAccountInfo(vestingAccount, commitment); if (!accountInfo) { throw Error("Vesting account doesn't exists"); } return (0, utils_1.listAccountActivity)(this.program, vestingAccount, before, limit, commitment); }); } /** * Gets the flowing rate of a vesting contract. * * @param vestingAccount - The address of the vesting contract account * @param onlyRunning - If true, only running streams will be accounted */ getVestingAccountFlowRate(vestingAccount_1) { return __awaiter(this, arguments, void 0, function* (vestingAccount, onlyRunning = true) { const psAccountInfo = yield (0, utils_1.getAccount)(this.program, vestingAccount); if (!psAccountInfo) { throw Error('Vesting account not found'); } // Get the template const [templateAddress] = (0, utils_1.findStreamTemplateAddress)(vestingAccount, this.program.programId); const templateInfo = yield (0, utils_1.getStreamTemplate)(this.program, templateAddress); if (!templateInfo) { throw Error('Stream template not found'); } if (psAccountInfo.totalStreams === 0) { return { rateAmount: new anchor_1.BN(0), intervalUnit: templateInfo.rateIntervalInSeconds, totalAllocation: new anchor_1.BN(0), }; } const streams = yield (0, utils_1.listStreams)(this.program, undefined, vestingAccount, undefined, types_1.Category.vesting); let totalAllocation = new anchor_1.BN(0); let streamRate = new anchor_1.BN(0); for (const stream of streams) { totalAllocation = totalAllocation.add(stream.allocationAssigned); switch (stream.statusCode) { case types_1.STREAM_STATUS_CODE.Paused: case types_1.STREAM_STATUS_CODE.Scheduled: if (onlyRunning) continue; } if (stream.remainingAllocationAmount.lten(0)) { // all streamed continue; } const percentDenominator = new anchor_1.BN(constants_1.CLIFF_PERCENT_DENOMINATOR); const allocationTotal = new anchor_1.BN(stream.allocationAssigned); const cliffAmount = allocationTotal .mul(new anchor_1.BN(templateInfo.cliffVestPercent)) .div(percentDenominator); const allocationAfterCliff = allocationTotal.sub(cliffAmount); const rateAmount = allocationAfterCliff.div(new anchor_1.BN(templateInfo.durationNumberOfUnits)); streamRate = streamRate.add(rateAmount); } return { rateAmount: streamRate, intervalUnit: templateInfo.rateIntervalInSeconds, totalAllocation: totalAllocation, }; }); } /** * Creates a vesting stream based on the vesting contract template. * * @param accounts - Transaction accounts * @param streamName - A name for the new stream * @param allocationAssigned - Total token amount allocated to the new stream * out of the containing vesting account's unallocated balance */ buildCreateVestingStreamTransaction(_a, allocationAssigned_1) { return __awaiter(this, arguments, void 0, function* ({ vestingAccount, owner, feePayer, beneficiary, }, allocationAssigned, streamName = '', usePda = false) { if (owner.equals(beneficiary)) { throw Error('Beneficiary can not be the same as owner'); } const psAccountInfo = yield (0, utils_1.getAccount)(this.program, vestingAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } const psAccountMint = new web3_js_1.PublicKey(psAccountInfo.mint); // Get the template const [template] = (0, utils_1.findStreamTemplateAddress)(vestingAccount, this.program.programId); const templateInfo = yield (0, utils_1.getStreamTemplate)(this.program, template); if (!templateInfo) { throw Error("Stream template doesn't exist"); } const { instruction: createStreamIx, streamKey, stream, } = yield instructions.buildCreateStreamWithTemplateInstruction(this.program, { psAccount: vestingAccount, psAccountMint, template, owner, feePayer, beneficiary, }, new anchor_1.BN(allocationAssigned), streamName, usePda); const tx = yield this.createTransaction([createStreamIx], feePayer, streamKey ? [streamKey] : undefined); return { transaction: tx, stream: stream, template: template, }; }); } /** * Constructs a transaction to add funds to a PS account. The funds are * added as unallocated balance. * * @param accounts - Transaction accounts * @param amount - Token amount to add */ buildAddFundsToAccountTransaction(_a, amount_1) { return __awaiter(this, arguments, void 0, function* ({ psAccount, psAccountMint, contributor, feePayer, }, amount) { feePayer = feePayer || contributor; if (!amount) { throw Error('Amount should be greater than 0'); } let autoWSol = false; if (psAccountMint.equals(constants_1.NATIVE_SOL_MINT)) { psAccountMint = constants_1.NATIVE_WSOL_MINT; autoWSol = true; } const contributorToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, psAccountMint, contributor, true); const contributorTokenInfo = yield this.connection.getAccountInfo(contributorToken, 'recent'); const ixs = []; const txSigners = []; yield this.ensureAutoWrapSolInstructions(autoWSol, amount, contributor, contributorToken, contributorTokenInfo, ixs, txSigners); const psAccountToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, psAccountMint, psAccount, true); const { instruction: createAddFundsInstruction } = yield instructions.buildAddFundsInstruction(this.program, { psAccount, psAccountMint, psAccountToken, contributor, contributorToken, feePayer, }, new anchor_1.BN(amount)); ixs.push(createAddFundsInstruction); const tx = yield this.createTransaction(ixs, feePayer, txSigners); return { transaction: tx, }; }); } /** * Constructs a transaction to allocate funds to a stream from the PS * account unallocated balance. * * @param accounts - Transaction accounts * @param amount - Token amount to allocate */ buildAllocateFundsToStreamTransaction(_a, amount_1) { return __awaiter(this, arguments, void 0, function* ({ psAccount, owner, feePayer, stream, }, amount) { feePayer = feePayer || owner; if (!amount) { throw Error('Amount must be greater than 0'); } const psAccountInfo = yield (0, utils_1.getAccount)(this.program, psAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } if (!psAccountInfo.owner.equals(owner)) { throw Error('Invalid account owner'); } const streamInfo = yield this.getStream(stream); if (!streamInfo) { throw Error('Stream account not found'); } if (!psAccountInfo.mint.equals(streamInfo.mint)) { throw Error('Invalid stream mint'); } const psAccountToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, psAccountInfo.mint, psAccount, true); const { instruction: allocateFundsToStreamInstruction } = yield instructions.buildAllocateFundsToStreamInstruction(this.program, { psAccount, psAccountMint: psAccountInfo.mint, psAccountToken, owner, feePayer, stream, }, new anchor_1.BN(amount)); const tx = yield this.createTransaction([allocateFundsToStreamInstruction], feePayer); return { transaction: tx, }; }); } /** * Constructs a transaction which does both: adding funds to a PS account * and allocating the funds to the specified stream. * * @param accounts - Transaction accounts * @param amount * @param autoWSol - Whether a wrap SOL instruction should be included in * the transaction if necessary */ buildFundStreamTransaction(_a, amount_1) { return __awaiter(this, arguments, void 0, function* ({ psAccount, owner, feePayer, stream }, amount, autoWSol = false) { feePayer = feePayer || owner; const ixs = []; const txSigners = []; const amountBN = new anchor_1.BN(amount || 0); if (!amount || amountBN.isZero()) { throw Error('Amount must be greater than 0'); } const psAccountInfo = yield (0, utils_1.getAccount)(this.program, psAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } const streamInfo = (yield this.getStream(stream)); if (!streamInfo) { throw Error('Stream account not found'); } if (!psAccountInfo.mint.equals(streamInfo.mint)) { throw Error('Invalid stream mint'); } const ownerToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, psAccountInfo.mint, owner, true); const ownerTokenInfo = yield this.connection.getAccountInfo(ownerToken); yield this.ensureAutoWrapSolInstructions(autoWSol, amount, owner, ownerToken, ownerTokenInfo, ixs, txSigners); // add AddFunds intruction const { instruction: addFundsInstruction, psAccountToken } = yield instructions.buildAddFundsInstruction(this.program, { psAccount, psAccountMint: psAccountInfo.mint, contributor: owner, contributorToken: ownerToken, feePayer, }, new anchor_1.BN(amount)); ixs.push(addFundsInstruction); // calculate fee if are payed from the PS account to deduct it from the amount let allocationAmountBn = new anchor_1.BN(amount); if (streamInfo.tokenFeePayedFromAccount) { allocationAmountBn = yield (0, utils_1.calculateAllocationAmount)(this.program.provider.connection, psAccountInfo, amount); } // add allocate instruction const { instruction: allocateInstruction } = yield instructions.buildAllocateFundsToStreamInstruction(this.program, { psAccount, psAccountMint: psAccountInfo.mint, psAccountToken, owner, feePayer, stream, }, allocationAmountBn); ixs.push(allocateInstruction); const tx = yield this.createTransaction(ixs, feePayer, txSigners); return { transaction: tx, }; }); } /** * Constructs a transaction to withdraw funds from a Payment Streaming * account. * * @param accounts - Transaction accounts * @param amount - Token amount to withdraw * @param autoWSol - Whether a wrap SOL instruction should be included in * the transaction if necessary */ buildWithdrawFromAccountTransaction(_a, amount_1) { return __awaiter(this, arguments, void 0, function* ({ psAccount, feePayer, destination, }, amount, autoWSol = false) { const amountBn = new anchor_1.BN(amount); if (!amountBn.gt(new anchor_1.BN(0))) { throw Error('Amount to withdraw must be positive'); } const psAccountInfo = yield (0, utils_1.getAccount)(this.program, psAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } feePayer = feePayer || psAccountInfo.owner; const ixs = []; const { instruction: withdrawFromAccountInstruction, destinationToken } = yield instructions.buildWithdrawFromAccountInstruction(this.program, { psAccount, psAccountMint: psAccountInfo.mint, owner: psAccountInfo.owner, feePayer, destination, }, amountBn); ixs.push(withdrawFromAccountInstruction); if (autoWSol && psAccountInfo.mint.equals(constants_1.NATIVE_WSOL_MINT) && destination.equals(psAccountInfo.owner) // the ata authority needs to be signer for the unwrap to work ) { const closeWSolIx = spl_token_1.Token.createCloseAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, destinationToken, destination, destination, []); ixs.push(closeWSolIx); } const tx = yield this.createTransaction(ixs, feePayer); return { transaction: tx, }; }); } /** * Constructs a transaction to refresh a Payment Streaming account after * funds are sent to it from outside of the program, i.e. using the * Token program directly. * * @param accounts - Transaction accounts */ buildRefreshAccountDataTransaction(_a) { return __awaiter(this, arguments, void 0, function* ({ psAccount, feePayer, }) { const psAccountInfo = yield (0, utils_1.getAccount)(this.program, psAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } const { instruction: refreshAccountDataInstruction } = yield instructions.buildRefreshAccountDataInstruction(this.program, { psAccount, psAccountMint: psAccountInfo.mint, }); const tx = yield this.createTransaction([refreshAccountDataInstruction], feePayer); return { transaction: tx, }; }); } /** * Constructs a transaction to close a Payment Streaming account. * * @param accounts - Transaction accounts * @param autoWSol - Whether a wrap SOL instruction should be included in * the transaction if necessary */ buildCloseAccountTransaction(_a) { return __awaiter(this, arguments, void 0, function* ({ psAccount, feePayer, destination }, autoWSol = false) { const psAccountInfo = yield (0, utils_1.getAccount)(this.program, psAccount); if (!psAccountInfo) { throw Error('Payment Streaming account not found'); } destination = destination || psAccountInfo.owner; feePayer = feePayer || psAccountInfo.owner; // just send any mint to close an account without a mint set const psAccountMint = psAccountInfo.mint.equals(web3_js_1.PublicKey.default) ? new web3_js_1.PublicKey(constants_1.NATIVE_WSOL_MINT) : psAccountInfo.mint; const destinationToken = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, psAccountMint, destination, true); const ixs = []; const { instruction: closeAccountInstruction } = yield instructions.buildCloseAccountInstruction(this.program, { psAccount, psAccountMint, owner: psAccountInfo.owner, feePayer, destination, }); ixs.push(closeAccountInstruction); if (autoWSol && psAccountMint.equals(constants_1.NATIVE_WSOL_MINT) && destination.equals(psAccountInfo.owner) // the ata authority needs to be signer for the unwrap to work ) { const closeWSolIx = spl_token_1.Token.createCloseAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, destinationToken, destination, destination, []); ixs.push(closeWSolIx); } const tx = yield this.createTransaction(ixs, feePayer); return { transaction: tx, }; }); } /** * Constructs a transaction to withdraw funds from a stream. * * @param accounts - Transaction accounts * @param amount - The token amount to withdraw * @param autoWSol - Whether a wrap SOL instruction should be included in * the transaction if necessary */ buildWithdrawFromStreamTransaction(_a, amount_1) { return __awaiter(this, arguments, void 0, function* ({ stream, feePayer }, amount, autoWSol = false) { if (!amount) {