@mean-dao/payment-streaming
Version:
Mean Payment Streaming Typescript SDK
937 lines (936 loc) • 60.2 kB
JavaScript
"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) {