@mean-dao/payment-streaming
Version:
Mean Payment Streaming Typescript SDK
1,042 lines • 60 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());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listAccountActivity = exports.getStreamedUnits = exports.getStreamWithdrawableUnitsWhilePaused = exports.getStreamStartUtcInSeconds = exports.getStreamUnitsPerSecond = exports.isStreamManuallyPaused = exports.getStreamStatusCode = exports.getStreamWithdrawableAmount = exports.getStreamRemainingAllocation = exports.getFundsSentToBeneficiary = exports.getFundsLeftInStream = exports.getStreamCliffAmount = exports.getStreamEstDepletionDate = exports.parseStreamTemplateData = exports.parseAccountData = exports.parseRawStreamAccount = exports.getFilteredStreamAccounts = exports.calculateAllocationAmount = exports.calculateFeesForAction = exports.listAccounts = exports.findStreamTemplateAddress = exports.getStreamTemplate = exports.getAccount = exports.listStreamActivity = exports.listStreamsCached = exports.listStreams = exports.getStreamCached = exports.getStream = exports.createProgram = void 0;
exports.getStreamEventData = getStreamEventData;
exports.parseProgramTransactions = parseProgramTransactions;
exports.fundExistingWSolAccountInstructions = fundExistingWSolAccountInstructions;
exports.createAtaCreateInstructionIfNotExists = createAtaCreateInstructionIfNotExists;
exports.createAtaCreateInstruction = createAtaCreateInstruction;
exports.createWrapSolInstructions = createWrapSolInstructions;
exports.sleep = sleep;
exports.toUnixTimestamp = toUnixTimestamp;
const web3_js_1 = require("@solana/web3.js");
const anchor_1 = require("@project-serum/anchor");
const constants_1 = require("./constants");
const types_1 = require("./types");
const msp_idl_005_1 = require("./msp_idl_005"); // point to the latest IDL
// Events are not possible yet.
// See https://github.com/coral-xyz/anchor/issues/2050
// See https://github.com/coral-xyz/anchor/pull/2185
// type RawStreamEvent = IdlEvent<Msp>[];
const bytes_1 = require("@project-serum/anchor/dist/cjs/utils/bytes");
const provider_1 = require("@project-serum/anchor/dist/cjs/provider");
const spl_token_1 = require("@solana/spl-token");
const anchor = __importStar(require("@project-serum/anchor"));
const bignumber_js_1 = __importDefault(require("bignumber.js"));
String.prototype.toPublicKey = function () {
return new web3_js_1.PublicKey(this.toString());
};
const createProgram = (connection, programId) => {
// we are not confirming transactions here
const opts = {
preflightCommitment: connection.commitment,
commitment: connection.commitment,
};
// dummy wallet only to configure the provider, we are not doing any signing
const wallet = {
publicKey: constants_1.SIMULATION_PUBKEY,
signAllTransactions: (txs) => __awaiter(void 0, void 0, void 0, function* () { return txs; }),
signTransaction: (tx) => __awaiter(void 0, void 0, void 0, function* () { return tx; }),
};
const provider = new provider_1.AnchorProvider(connection, wallet, opts);
return new anchor_1.Program(msp_idl_005_1.IDL, programId, provider);
};
exports.createProgram = createProgram;
const getStream = (program, address) => __awaiter(void 0, void 0, void 0, function* () {
try {
const event = yield getStreamEventData(program, address);
if (!event)
return null;
const streamInfo = parseStreamEventData(event, address);
return streamInfo;
}
catch (error) {
console.log(error);
return null;
}
});
exports.getStream = getStream;
function getStreamEventData(program, address) {
return __awaiter(this, void 0, void 0, function* () {
try {
const streamEventResponse = yield program.simulate.getStream(constants_1.LATEST_IDL_FILE_VERSION, {
accounts: {
stream: address,
},
});
if (!streamEventResponse ||
!streamEventResponse.events ||
!streamEventResponse.events.length ||
!streamEventResponse.events[0].data) {
return null;
}
const event = streamEventResponse.events[0].data;
return event;
}
catch (error) {
return null;
}
});
}
const getStreamCached = (streamInfo) => __awaiter(void 0, void 0, void 0, function* () {
const timeDiff = streamInfo.lastRetrievedTimeInSeconds - streamInfo.lastRetrievedBlockTime;
const blocktime = parseInt((Date.now() / 1000).toString()) - timeDiff;
const parsedStream = (0, exports.parseRawStreamAccount)(streamInfo.data, streamInfo.id, blocktime);
parsedStream.createdBlockTime = streamInfo.createdBlockTime;
return parsedStream;
});
exports.getStreamCached = getStreamCached;
const listStreams = (program, psAccountOwner, psAccount, beneficiary, category, subCategory) => __awaiter(void 0, void 0, void 0, function* () {
const streamInfoList = [];
const accounts = yield (0, exports.getFilteredStreamAccounts)(program, psAccountOwner, psAccount, beneficiary, category, subCategory);
const slot = yield program.provider.connection.getSlot();
const blockTime = (yield program.provider.connection.getBlockTime(slot));
for (const item of accounts) {
if (item.account !== undefined) {
const parsedStream = (0, exports.parseRawStreamAccount)(item.account, item.publicKey, blockTime);
streamInfoList.push(parsedStream);
}
}
streamInfoList.sort((a, b) => {
if (a.createdBlockTime !== b.createdBlockTime) {
return b.createdBlockTime - a.createdBlockTime;
}
return a.name !== b.name
? a.name.localeCompare(b.name)
: a.id.toBase58().localeCompare(b.id.toBase58());
});
return streamInfoList;
});
exports.listStreams = listStreams;
const listStreamsCached = (streamInfoList) => __awaiter(void 0, void 0, void 0, function* () {
const streamList = [];
for (const streamInfo of streamInfoList) {
const timeDiff = streamInfo.lastRetrievedTimeInSeconds - streamInfo.lastRetrievedBlockTime;
const blockTime = parseInt((Date.now() / 1000).toString()) - timeDiff;
const parsedStream = (0, exports.parseRawStreamAccount)(streamInfo.data, streamInfo.id, blockTime);
parsedStream.createdBlockTime = streamInfo.createdBlockTime;
streamList.push(parsedStream);
}
return streamList;
});
exports.listStreamsCached = listStreamsCached;
const listStreamActivity = (program_1, address_1, ...args_1) => __awaiter(void 0, [program_1, address_1, ...args_1], void 0, function* (program, address, before = '', limit = 10, commitment) {
let activityRaw = [];
const finality = commitment !== undefined ? commitment : 'confirmed';
const filter = { limit };
if (before) {
filter['before'] = before;
}
const signatures = yield program.provider.connection.getSignaturesForAddress(address, filter, finality);
const txs = yield program.provider.connection.getParsedTransactions(signatures.map(s => s.signature), finality);
if (txs && txs.length) {
activityRaw = yield parseProgramTransactions(txs, program.programId, undefined, address);
activityRaw.sort((a, b) => { var _a, _b; return ((_a = b.blockTime) !== null && _a !== void 0 ? _a : 0) - ((_b = a.blockTime) !== null && _b !== void 0 ? _b : 0); });
}
// this mapping is kept here for backwards compatibility
const activity = activityRaw.map(i => {
var _a, _b, _c;
let actionText = '';
switch (i.action) {
case types_1.ActivityActionCode.StreamCreated:
actionText = ((_a = i.amount) === null || _a === void 0 ? void 0 : _a.gt(new anchor_1.BN(0))) ? 'deposited' : 'stream created';
break;
case types_1.ActivityActionCode.FundsAllocatedToStream:
actionText = 'deposited';
break;
default:
actionText = 'withdrew';
break;
}
return {
signature: i.signature,
initializer: (_b = i.initializer) === null || _b === void 0 ? void 0 : _b.toBase58(),
action: actionText,
actionCode: i.action,
amount: i.amount ? i.amount.toString() : '',
mint: (_c = i.mint) === null || _c === void 0 ? void 0 : _c.toBase58(),
blockTime: i.blockTime,
utcDate: i.utcDate,
};
});
return activity;
});
exports.listStreamActivity = listStreamActivity;
const getAccount = (program, address) => __awaiter(void 0, void 0, void 0, function* () {
const psAccount = yield program.account.treasury.fetch(address);
const parsedAccount = (0, exports.parseAccountData)(psAccount, address);
return parsedAccount;
});
exports.getAccount = getAccount;
const getStreamTemplate = (program, address) => __awaiter(void 0, void 0, void 0, function* () {
const template = yield program.account.streamTemplate.fetch(address);
return (0, exports.parseStreamTemplateData)(template, address);
});
exports.getStreamTemplate = getStreamTemplate;
const findStreamTemplateAddress = (psAccount, programId) => {
return web3_js_1.PublicKey.findProgramAddressSync([anchor.utils.bytes.utf8.encode('template'), psAccount.toBuffer()], programId);
};
exports.findStreamTemplateAddress = findStreamTemplateAddress;
const listAccounts = (program, owner, excludeAutoClose, category, subCategory) => __awaiter(void 0, void 0, void 0, function* () {
const psAccounts = [];
const memcmpFilters = [];
if (owner) {
memcmpFilters.push({
memcmp: { offset: 8 + 43, bytes: owner.toBase58() },
});
}
if (excludeAutoClose) {
memcmpFilters.push({
memcmp: { offset: 216, bytes: bytes_1.bs58.encode([0]) },
});
}
if (category !== undefined) {
memcmpFilters.push({
memcmp: { offset: 218, bytes: bytes_1.bs58.encode([category]) },
});
}
if (subCategory !== undefined) {
memcmpFilters.push({
memcmp: { offset: 219, bytes: bytes_1.bs58.encode([subCategory]) },
});
}
const accounts = yield program.account.treasury.all(memcmpFilters);
if (accounts.length) {
for (const item of accounts) {
if (item.account !== undefined) {
const parsedAccount = (0, exports.parseAccountData)(item.account, item.publicKey);
const info = Object.assign({}, parsedAccount);
if ((owner && owner.equals(info.owner)) || !owner) {
psAccounts.push(info);
}
}
}
}
const sortedAccounts = psAccounts.sort((a, b) => b.slot - a.slot);
return sortedAccounts;
});
exports.listAccounts = listAccounts;
const calculateFeesForAction = (action) => __awaiter(void 0, void 0, void 0, function* () {
const txFees = {
blockchainFee: 0.0,
mspFlatFee: 0.0,
mspPercentFee: 0.0,
};
let blockchainFee = 0;
switch (action) {
case types_1.ACTION_CODES.CreateAccount:
case types_1.ACTION_CODES.CreateStream: {
blockchainFee = 15000000;
txFees.mspFlatFee = 0.00001;
break;
}
case types_1.ACTION_CODES.CreateStreamWithFunds: {
blockchainFee = 20000000;
txFees.mspFlatFee = 0.000035;
break;
}
case types_1.ACTION_CODES.ScheduleOneTimePayment: {
blockchainFee = 15000000;
txFees.mspFlatFee = 0.000035;
break;
}
case types_1.ACTION_CODES.AddFundsToAccount: {
txFees.mspFlatFee = 0.000025;
break;
}
case types_1.ACTION_CODES.WithdrawFromStream: {
blockchainFee = 5000000;
txFees.mspPercentFee = 0.25;
break;
}
case types_1.ACTION_CODES.CloseStream: {
txFees.mspFlatFee = 0.00001;
txFees.mspPercentFee = 0.25;
break;
}
case types_1.ACTION_CODES.CloseAccount: {
txFees.mspFlatFee = 0.00001;
break;
}
case types_1.ACTION_CODES.TransferStream: {
blockchainFee = 5000;
txFees.mspFlatFee = 0.00001;
break;
}
case types_1.ACTION_CODES.WithdrawFromAccount: {
txFees.mspPercentFee = 0.25;
break;
}
default: {
break;
}
}
txFees.blockchainFee = blockchainFee / web3_js_1.LAMPORTS_PER_SOL;
return txFees;
});
exports.calculateFeesForAction = calculateFeesForAction;
const calculateAllocationAmount = (connection, psAccount, allocation) => __awaiter(void 0, void 0, void 0, function* () {
const fees = yield (0, exports.calculateFeesForAction)(types_1.ACTION_CODES.WithdrawFromStream);
//
const BASE_100_TO_BASE_1_MULTIPLIER = constants_1.CLIFF_PERCENT_NUMERATOR;
const feeNumerator = fees.mspPercentFee * BASE_100_TO_BASE_1_MULTIPLIER;
const feeDenaminator = constants_1.CLIFF_PERCENT_DENOMINATOR;
const unallocatedBalance = new anchor_1.BN(psAccount.balance).sub(new anchor_1.BN(psAccount.allocationAssigned));
const allocationAmountBn = new anchor_1.BN(allocation).add(unallocatedBalance);
const badStreamAllocationAmount = allocationAmountBn
.mul(new anchor_1.BN(feeDenaminator))
.div(new anchor_1.BN(feeNumerator + feeDenaminator));
const feeAmount = badStreamAllocationAmount
.mul(new anchor_1.BN(feeNumerator))
.div(new anchor_1.BN(feeDenaminator));
if (unallocatedBalance.gte(feeAmount)) {
return badStreamAllocationAmount;
}
const goodStreamMaxAllocation = allocationAmountBn.sub(feeAmount);
return goodStreamMaxAllocation;
});
exports.calculateAllocationAmount = calculateAllocationAmount;
const getFilteredStreamAccounts = (program, psAccountOwner, psAccount, beneficiary, category, subCategory) => __awaiter(void 0, void 0, void 0, function* () {
const accounts = [];
// category filters
const categoryFilters = [];
if (category !== undefined) {
categoryFilters.push({
memcmp: { offset: 339, bytes: bytes_1.bs58.encode([category]) },
});
}
if (subCategory !== undefined) {
categoryFilters.push({
memcmp: { offset: 340, bytes: bytes_1.bs58.encode([subCategory]) },
});
}
if (psAccount) {
const memcmpFilters = [
{ memcmp: { offset: 8 + 170, bytes: psAccount.toBase58() } },
...categoryFilters,
];
const accs = yield program.account.stream.all(memcmpFilters);
if (accs.length) {
accounts.push(...accs);
}
}
else {
if (psAccountOwner) {
const memcmpFilters = [
{ memcmp: { offset: 8 + 34, bytes: psAccountOwner.toBase58() } },
...categoryFilters,
];
const accs = yield program.account.stream.all(memcmpFilters);
if (accs.length) {
for (const acc of accs) {
if (accounts.indexOf(acc) === -1) {
accounts.push(acc);
}
}
}
}
if (beneficiary) {
const memcmpFilters = [
{ memcmp: { offset: 8 + 106, bytes: beneficiary.toBase58() } },
...categoryFilters,
];
const accs = yield program.account.stream.all(memcmpFilters);
if (accs.length) {
for (const acc of accs) {
if (accounts.indexOf(acc) === -1) {
accounts.push(acc);
}
}
}
}
}
return accounts;
});
exports.getFilteredStreamAccounts = getFilteredStreamAccounts;
/**
* Parses the event returned by the get_stream getter in the mps program.
* @param event
* @param address stream address
* @returns Stream
*/
const parseStreamEventData = (event, address) => {
const nameBuffer = Buffer.from(event.name);
const createdOnUtcInSeconds = event.createdOnUtc
? event.createdOnUtc.toNumber()
: 0;
const effectiveCreatedOnUtcInSeconds = createdOnUtcInSeconds > 0
? createdOnUtcInSeconds
: event.startUtc.toNumber();
const rawStream = {
version: event.version,
initialized: event.initialized,
name: [].slice.call(anchor.utils.bytes.utf8.encode(event.name)),
treasurerAddress: event.treasurerAddress,
rateAmountUnits: event.rateAmountUnits,
rateIntervalInSeconds: event.rateIntervalInSeconds,
startUtc: event.startUtc,
cliffVestAmountUnits: event.cliffVestAmountUnits,
cliffVestPercent: event.cliffVestPercent,
beneficiaryAddress: event.beneficiaryAddress,
beneficiaryAssociatedToken: event.beneficiaryAssociatedToken,
treasuryAddress: event.treasuryAddress,
allocationAssignedUnits: event.allocationAssignedUnits,
allocationReservedUnits: event.allocationReservedUnits,
totalWithdrawalsUnits: event.totalWithdrawalsUnits,
lastWithdrawalUnits: event.lastWithdrawalUnits,
lastWithdrawalSlot: event.lastWithdrawalSlot,
lastWithdrawalBlockTime: event.lastWithdrawalBlockTime,
lastManualStopWithdrawableUnitsSnap: event.lastManualStopWithdrawableUnitsSnap,
lastManualStopSlot: event.lastManualStopSlot,
lastManualStopBlockTime: event.lastManualStopBlockTime,
lastManualResumeRemainingAllocationUnitsSnap: event.lastManualResumeRemainingAllocationUnitsSnap,
lastManualResumeSlot: event.lastManualResumeSlot,
lastManualResumeBlockTime: event.lastManualResumeBlockTime,
lastKnownTotalSecondsInPausedStatus: event.lastKnownTotalSecondsInPausedStatus,
lastAutoStopBlockTime: event.lastAutoStopBlockTime,
feePayedByTreasurer: event.feePayedByTreasurer,
// startUtc is guaranteed to be in seconds for the getStream event
startUtcInSeconds: event.startUtc,
createdOnUtc: event.createdOnUtc,
category: event.category,
subCategory: event.subCategory,
};
let statusCode = types_1.STREAM_STATUS_CODE.Unknown;
switch (event.status) {
case 'Scheduled':
statusCode = types_1.STREAM_STATUS_CODE.Scheduled;
break;
case 'Running':
statusCode = types_1.STREAM_STATUS_CODE.Running;
break;
case 'Paused':
statusCode = types_1.STREAM_STATUS_CODE.Paused;
break;
default: {
break;
}
}
const stream = {
id: address,
version: event.version,
initialized: event.initialized,
name: new TextDecoder().decode(nameBuffer).trim(),
startUtc: new Date(event.startUtc.toNumber() * 1000).toString(), // event.startUtc is guaranteed to be in seconds
treasurer: event.treasurerAddress,
psAccountOwner: event.treasurerAddress,
treasury: event.treasuryAddress,
psAccount: event.treasuryAddress,
beneficiary: event.beneficiaryAddress,
mint: event.beneficiaryAssociatedToken,
cliffVestAmount: event.cliffVestAmountUnits,
cliffVestPercent: event.cliffVestPercent.toNumber() / constants_1.CLIFF_PERCENT_NUMERATOR,
allocationAssigned: event.allocationAssignedUnits,
secondsSinceStart: event.currentBlockTime
.sub(new anchor_1.BN(event.startUtc))
.toNumber(),
estimatedDepletionDate: new Date(event.estDepletionTime.toNumber() * 1000).toString(),
rateAmount: event.rateAmountUnits,
rateIntervalInSeconds: event.rateIntervalInSeconds.toNumber(),
totalWithdrawalsAmount: event.totalWithdrawalsUnits,
fundsLeftInStream: event.fundsLeftInStream,
fundsSentToBeneficiary: event.fundsSentToBeneficiary,
remainingAllocationAmount: event.beneficiaryRemainingAllocation,
withdrawableAmount: event.beneficiaryWithdrawableAmount,
streamUnitsPerSecond: (0, exports.getStreamUnitsPerSecond)(rawStream.rateAmountUnits, rawStream.rateIntervalInSeconds),
isManuallyPaused: event.isManualPause,
status: event.status,
statusCode: statusCode,
statusName: event.status,
lastRetrievedBlockTime: event.currentBlockTime.toNumber(),
lastRetrievedTimeInSeconds: parseInt((Date.now() / 1000).toString()),
feePayedByTreasurer: event.feePayedByTreasurer,
tokenFeePayedFromAccount: event.feePayedByTreasurer,
createdBlockTime: effectiveCreatedOnUtcInSeconds,
createdOnUtc: new Date(effectiveCreatedOnUtcInSeconds * 1000).toString(),
category: event.category,
subCategory: event.subCategory,
upgradeRequired: false,
data: rawStream,
};
return stream;
};
/**
* Parses program account items
* @param rawStream
* @param address
* @param blockTime
* @returns Stream
*/
const parseRawStreamAccount = (rawStream, address, blockTime) => {
const nameBuffer = Buffer.from(rawStream.name);
const createdOnUtcInSeconds = rawStream.createdOnUtc
? rawStream.createdOnUtc.toNumber()
: 0;
const startUtcInSeconds = (0, exports.getStreamStartUtcInSeconds)(rawStream);
const effectiveCreatedOnUtcInSeconds = createdOnUtcInSeconds > 0 ? createdOnUtcInSeconds : startUtcInSeconds;
const timeDiff = Math.round(Date.now() / 1000 - blockTime);
const startUtc = new Date(startUtcInSeconds * 1000);
const depletionDate = (0, exports.getStreamEstDepletionDate)(rawStream);
const streamStatus = (0, exports.getStreamStatusCode)(rawStream, timeDiff);
const streamWithdrawableAmount = (0, exports.getStreamWithdrawableAmount)(rawStream, timeDiff);
const parsedStream = {
id: address,
version: rawStream.version,
initialized: rawStream.initialized,
name: new TextDecoder().decode(nameBuffer).trim(),
startUtc: startUtc.toString(),
psAccountOwner: rawStream.treasurerAddress,
psAccount: rawStream.treasuryAddress,
beneficiary: rawStream.beneficiaryAddress,
mint: rawStream.beneficiaryAssociatedToken,
cliffVestAmount: rawStream.cliffVestAmountUnits,
cliffVestPercent: rawStream.cliffVestPercent.toNumber() / constants_1.CLIFF_PERCENT_NUMERATOR,
allocationAssigned: rawStream.allocationAssignedUnits,
secondsSinceStart: blockTime - startUtcInSeconds,
estimatedDepletionDate: depletionDate.toString(),
rateAmount: rawStream.rateAmountUnits,
rateIntervalInSeconds: rawStream.rateIntervalInSeconds.toNumber(),
totalWithdrawalsAmount: rawStream.totalWithdrawalsUnits,
fundsLeftInStream: (0, exports.getFundsLeftInStream)(rawStream, timeDiff),
fundsSentToBeneficiary: (0, exports.getFundsSentToBeneficiary)(rawStream, timeDiff),
remainingAllocationAmount: (0, exports.getStreamRemainingAllocation)(rawStream),
withdrawableAmount: streamWithdrawableAmount,
streamUnitsPerSecond: (0, exports.getStreamUnitsPerSecond)(rawStream.rateAmountUnits, rawStream.rateIntervalInSeconds),
isManuallyPaused: (0, exports.isStreamManuallyPaused)(rawStream),
statusCode: streamStatus,
statusName: types_1.STREAM_STATUS_CODE[streamStatus],
lastRetrievedBlockTime: blockTime,
lastRetrievedTimeInSeconds: parseInt((Date.now() / 1000).toString()),
feePayedByTreasurer: rawStream.feePayedByTreasurer,
tokenFeePayedFromAccount: rawStream.feePayedByTreasurer,
category: rawStream.category,
subCategory: rawStream.subCategory,
transactionSignature: '',
createdBlockTime: createdOnUtcInSeconds > 0 ? createdOnUtcInSeconds : startUtcInSeconds,
createdOnUtc: new Date(effectiveCreatedOnUtcInSeconds * 1000).toString(),
upgradeRequired: false,
data: rawStream,
};
return parsedStream;
};
exports.parseRawStreamAccount = parseRawStreamAccount;
const idls = {};
function parseProgramTransactions(transactions, programId, psAccountAddress, streamAddress) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const parsedActivities = [];
if (!transactions || transactions.length === 0)
return [];
for (let i = 0; i < transactions.length; i++) {
const tx = transactions[i];
const signature = tx.transaction.signatures[0];
for (let j = 0; j < tx.transaction.message.instructions.length; j++) {
const ix = tx.transaction.message.instructions[j];
if (!ix || !ix.data)
continue;
const decodedIxData = bytes_1.bs58.decode(ix.data);
const ixIdlFileVersion = decodedIxData.length >= 9 ? decodedIxData.subarray(8, 9)[0] : 0;
let activity = null;
if (ixIdlFileVersion > 0 && ixIdlFileVersion <= constants_1.LATEST_IDL_FILE_VERSION) {
activity = yield parseProgramInstruction(ix, signature, (_a = tx.blockTime) !== null && _a !== void 0 ? _a : 0, ixIdlFileVersion, programId, psAccountAddress, streamAddress);
}
if (!activity) {
continue;
}
parsedActivities.push(activity);
}
}
return parsedActivities;
});
}
const parseAccountData = (rawAccount, accountAddress) => {
const nameBuffer = Buffer.from(rawAccount.name);
const psAccountCreatedUtc = rawAccount.createdOnUtc.toString().length > 10
? parseInt(rawAccount.createdOnUtc.toString().substring(0, 10))
: rawAccount.createdOnUtc.toNumber();
return {
id: accountAddress,
version: rawAccount.version,
initialized: rawAccount.initialized,
name: new TextDecoder().decode(nameBuffer).trim(),
bump: rawAccount.bump,
slot: rawAccount.slot.toNumber(),
autoClose: rawAccount.autoClose,
createdOnUtc: new Date(psAccountCreatedUtc * 1000),
accountType: rawAccount.treasuryType === 0 ? types_1.AccountType.Open : types_1.AccountType.Lock,
owner: rawAccount.treasurerAddress,
mint: rawAccount.associatedTokenAddress,
balance: rawAccount.lastKnownBalanceUnits,
allocationAssigned: rawAccount.allocationAssignedUnits,
totalWithdrawals: rawAccount.totalWithdrawalsUnits,
totalStreams: rawAccount.totalStreams.toNumber(),
category: rawAccount.category,
subCategory: rawAccount.subCategory,
data: rawAccount,
};
};
exports.parseAccountData = parseAccountData;
const parseStreamTemplateData = (template, address) => {
return {
id: address,
version: template.version,
bump: template.bump,
durationNumberOfUnits: template.durationNumberOfUnits.toNumber(),
rateIntervalInSeconds: template.rateIntervalInSeconds.toNumber(),
startUtc: new Date(template.startUtcInSeconds.toNumber() * 1000).toString(),
cliffVestPercent: template.cliffVestPercent.toNumber(),
feePayedByTreasurer: template.feePayedByTreasurer,
};
};
exports.parseStreamTemplateData = parseStreamTemplateData;
const getStreamEstDepletionDate = (stream) => {
if (stream.rateIntervalInSeconds.isZero()) {
return new Date();
}
const cliffUnits = (0, exports.getStreamCliffAmount)(stream);
const streamableUnits = stream.allocationAssignedUnits.sub(cliffUnits);
const streamingSeconds = streamableUnits
.mul(stream.rateIntervalInSeconds)
.div(stream.rateAmountUnits);
const durationSpanSeconds = streamingSeconds.add(stream.lastKnownTotalSecondsInPausedStatus);
const startUtcInSeconds = (0, exports.getStreamStartUtcInSeconds)(stream);
const depletionTimestamp = (startUtcInSeconds + durationSpanSeconds.toNumber()) * 1000;
const depletionDate = new Date(depletionTimestamp);
if (depletionDate.toString() !== 'Invalid Date') {
return depletionDate;
}
return new Date();
};
exports.getStreamEstDepletionDate = getStreamEstDepletionDate;
const getStreamCliffAmount = (stream) => {
// Previously, cliff could be provided either as percentage or amount.
// Currently, cliff percent is not stored in the stream, when a stream is
// created with a cliff percent, it is converted to an absolute amount and
// stored in stream.cliffVestAmountUnits. Legacy stream might still use
// the percent flavor so we take care of those cases here
if (stream.cliffVestPercent.gtn(0)) {
return stream.cliffVestPercent
.mul(stream.allocationAssignedUnits)
.div(new anchor_1.BN(constants_1.CLIFF_PERCENT_DENOMINATOR));
}
return stream.cliffVestAmountUnits;
};
exports.getStreamCliffAmount = getStreamCliffAmount;
const getFundsLeftInStream = (stream, timeDiff = 0) => {
const withdrawableAmount = (0, exports.getStreamWithdrawableAmount)(stream, timeDiff);
const remainingAllocation = (0, exports.getStreamRemainingAllocation)(stream);
const fundsLeft = remainingAllocation.sub(withdrawableAmount);
return anchor_1.BN.max(new anchor_1.BN(0), fundsLeft);
};
exports.getFundsLeftInStream = getFundsLeftInStream;
const getFundsSentToBeneficiary = (stream, timeDiff = 0) => {
const withdrawableAmount = (0, exports.getStreamWithdrawableAmount)(stream, timeDiff);
const fundsSent = stream.totalWithdrawalsUnits.add(withdrawableAmount);
return fundsSent;
};
exports.getFundsSentToBeneficiary = getFundsSentToBeneficiary;
const getStreamRemainingAllocation = (stream) => {
const remainingAlloc = stream.allocationAssignedUnits.sub(stream.totalWithdrawalsUnits);
return anchor_1.BN.max(new anchor_1.BN(0), remainingAlloc);
};
exports.getStreamRemainingAllocation = getStreamRemainingAllocation;
const getStreamWithdrawableAmount = (stream, timeDiff = 0) => {
const remainingAllocation = (0, exports.getStreamRemainingAllocation)(stream);
if (remainingAllocation.isZero()) {
return new anchor_1.BN(0);
}
const status = (0, exports.getStreamStatusCode)(stream, timeDiff);
// Check if SCHEDULED
if (status === types_1.STREAM_STATUS_CODE.Scheduled) {
return new anchor_1.BN(0);
}
// Check if PAUSED
if (status === types_1.STREAM_STATUS_CODE.Paused) {
const manuallyPaused = (0, exports.isStreamManuallyPaused)(stream);
const withdrawableWhilePausedAmount = manuallyPaused
? stream.lastManualStopWithdrawableUnitsSnap
: remainingAllocation;
return anchor_1.BN.max(new anchor_1.BN(0), withdrawableWhilePausedAmount);
}
// Check if NOT RUNNING
if (stream.rateAmountUnits.isZero() ||
stream.rateIntervalInSeconds.isZero()) {
return new anchor_1.BN(0);
}
const cliffUnits = (0, exports.getStreamCliffAmount)(stream);
// Get the blockchain kind of "now" given the client timeDiff
const blocktimeRelativeNow = Math.round(Date.now() / 1000 - timeDiff);
const startUtcInSeconds = (0, exports.getStreamStartUtcInSeconds)(stream);
const secondsSinceStart = new anchor_1.BN(blocktimeRelativeNow - startUtcInSeconds);
const actualStreamedSeconds = secondsSinceStart.sub(stream.lastKnownTotalSecondsInPausedStatus);
const actualStreamedUnits = (0, exports.getStreamedUnits)(stream, actualStreamedSeconds);
let actualEarnedUnits = cliffUnits.add(actualStreamedUnits);
actualEarnedUnits = anchor_1.BN.max(actualEarnedUnits, stream.totalWithdrawalsUnits);
const withdrawableUnitsWhileRunning = actualEarnedUnits.sub(stream.totalWithdrawalsUnits);
const withdrawable = anchor_1.BN.min(remainingAllocation, withdrawableUnitsWhileRunning);
return withdrawable;
};
exports.getStreamWithdrawableAmount = getStreamWithdrawableAmount;
/**
* Mimics msp program -> `stream.get_status()`
* @param stream Raw stream as defined in IDL
* @param timeDiff
*/
const getStreamStatusCode = (stream, timeDiff) => {
// Get the blockchain kind of "now" given the client timeDiff
const blocktimeRelativeNow = Date.now() / 1000 - timeDiff;
const startUtcInSeconds = (0, exports.getStreamStartUtcInSeconds)(stream);
// Scheduled
if (startUtcInSeconds > blocktimeRelativeNow) {
return types_1.STREAM_STATUS_CODE.Scheduled;
}
// Manually paused
const manuallyPaused = (0, exports.isStreamManuallyPaused)(stream);
if (manuallyPaused) {
return types_1.STREAM_STATUS_CODE.Paused;
}
// Running or automatically paused (ran out of funds)
const cliffUnits = (0, exports.getStreamCliffAmount)(stream);
const secondsSinceStart = new anchor_1.BN(blocktimeRelativeNow - startUtcInSeconds);
const actualStreamedSeconds = secondsSinceStart.sub(stream.lastKnownTotalSecondsInPausedStatus);
const actualStreamedUnits = (0, exports.getStreamedUnits)(stream, actualStreamedSeconds);
const actualEarnedUnits = cliffUnits.add(actualStreamedUnits);
if (stream.allocationAssignedUnits.gt(actualEarnedUnits)) {
return types_1.STREAM_STATUS_CODE.Running;
}
// Automatically paused (ran out of funds)
return types_1.STREAM_STATUS_CODE.Paused;
};
exports.getStreamStatusCode = getStreamStatusCode;
const isStreamManuallyPaused = (stream) => {
return (stream.lastManualStopBlockTime.gtn(0) &&
stream.lastManualStopBlockTime.gt(stream.lastManualResumeBlockTime));
};
exports.isStreamManuallyPaused = isStreamManuallyPaused;
const getStreamUnitsPerSecond = (rateAmountUnits, rateIntervalInSeconds) => {
rateIntervalInSeconds = new anchor_1.BN(rateIntervalInSeconds);
if (rateIntervalInSeconds.isZero()) {
return 0;
}
rateAmountUnits = new anchor_1.BN(rateAmountUnits);
const streamUnitsPerSecond = new bignumber_js_1.default(rateAmountUnits.toString()).dividedBy(rateIntervalInSeconds.toString());
return streamUnitsPerSecond.toNumber();
};
exports.getStreamUnitsPerSecond = getStreamUnitsPerSecond;
const getStreamStartUtcInSeconds = (stream) => {
if (stream.startUtcInSeconds.gt(new anchor_1.BN(0))) {
return stream.startUtcInSeconds.toNumber();
}
// Some legacy streams were created with startUtc in miliseconds instead
// of seconds. In those cases we need to conver to seconds.
if (stream.startUtc.toString().length > 10) {
return stream.startUtc.div(new anchor_1.BN(1000)).toNumber();
}
return stream.startUtc.toNumber();
};
exports.getStreamStartUtcInSeconds = getStreamStartUtcInSeconds;
const getStreamWithdrawableUnitsWhilePaused = (stream) => {
let withdrawableWhilePaused = new anchor_1.BN(0);
const isManuallyPaused = (0, exports.isStreamManuallyPaused)(stream);
if (isManuallyPaused) {
withdrawableWhilePaused = stream.lastManualStopWithdrawableUnitsSnap;
}
else {
withdrawableWhilePaused = stream.allocationAssignedUnits.sub(stream.totalWithdrawalsUnits);
}
return anchor_1.BN.max(new anchor_1.BN(0), withdrawableWhilePaused);
};
exports.getStreamWithdrawableUnitsWhilePaused = getStreamWithdrawableUnitsWhilePaused;
const getStreamedUnits = (rawStream, seconds) => {
if (rawStream.rateIntervalInSeconds.isZero())
return new anchor_1.BN(0);
const cliffUnits = (0, exports.getStreamCliffAmount)(rawStream);
const streamableUnits = rawStream.allocationAssignedUnits.sub(cliffUnits);
const streamingSeconds = streamableUnits
.mul(rawStream.rateIntervalInSeconds)
.div(rawStream.rateAmountUnits);
if (seconds.gt(streamingSeconds))
return streamableUnits;
const streamableUnitsInGivenSeconds = rawStream.rateAmountUnits
.mul(seconds)
.div(rawStream.rateIntervalInSeconds);
return streamableUnitsInGivenSeconds;
};
exports.getStreamedUnits = getStreamedUnits;
function fundExistingWSolAccountInstructions(connection, owner, ownerWSolTokenAccount, payer, amountToWrapInLamports) {
return __awaiter(this, void 0, void 0, function* () {
// Allocate memory for the account
const minimumAccountBalance = yield spl_token_1.Token.getMinBalanceRentForExemptAccount(connection);
const newWrapAccount = web3_js_1.Keypair.generate();
const wrapIxs = [
web3_js_1.SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: newWrapAccount.publicKey,
lamports: minimumAccountBalance + amountToWrapInLamports,
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, owner),
spl_token_1.Token.createTransferInstruction(spl_token_1.TOKEN_PROGRAM_ID, newWrapAccount.publicKey, ownerWSolTokenAccount, owner, [], amountToWrapInLamports),
spl_token_1.Token.createCloseAccountInstruction(spl_token_1.TOKEN_PROGRAM_ID, newWrapAccount.publicKey, payer, owner, []),
];
return [wrapIxs, newWrapAccount];
});
}
function createAtaCreateInstructionIfNotExists(ataAddress, mintAddress, ownerAccountAddress, payerAddress, connection) {
return __awaiter(this, void 0, void 0, function* () {
try {
const ata = yield connection.getAccountInfo(ataAddress);
if (!ata) {
const [, createIx] = yield createAtaCreateInstruction(ataAddress, mintAddress, ownerAccountAddress, payerAddress);
return createIx;
}
return null;
}
catch (err) {
console.log('Unable to find associated account: %s', err);
throw Error('Unable to find associated account');
}
});
}
function createAtaCreateInstruction(ataAddress, mintAddress, ownerAccountAddress, payerAddress) {
return __awaiter(this, void 0, void 0, function* () {
if (ataAddress === null) {
ataAddress = yield spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mintAddress, ownerAccountAddress);
}
const ataCreateInstruction = spl_token_1.Token.createAssociatedTokenAccountInstruction(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, mintAddress, ataAddress, ownerAccountAddress, payerAddress);
return [ataAddress, ataCreateInstruction];
});
}
function createWrapSolInstructions(connection, wSolAmountInLamports, owner, ownerWSolTokenAccount, ownerWSolTokenAccountInfo) {
return __awaiter(this, void 0, void 0, function* () {
const ixs = [];
const signers = [];
const wSolAmountInLamportsBn = new anchor_1.BN(wSolAmountInLamports);
let ownerWSolAtaBalanceBn = new anchor_1.BN(0);
if (ownerWSolTokenAccountInfo) {
const ownerWSolAtaTokenAmount = (yield connection.getTokenAccountBalance(ownerWSolTokenAccount)).value;
ownerWSolAtaBalanceBn = new anchor_1.BN(ownerWSolAtaTokenAmount.amount);
}
else {
const ownerFromAtaCreateInstruction = yield createAtaCreateInstructionIfNotExists(ownerWSolTokenAccount, spl_token_1.NATIVE_MINT, owner, owner, connection);
if (ownerFromAtaCreateInstruction)
ixs.push(ownerFromAtaCreateInstruction);
}
if (wSolAmountInLamportsBn.gt(ownerWSolAtaBalanceBn)) {
const amountToWrapBn = wSolAmountInLamportsBn.sub(ownerWSolAtaBalanceBn);
const [wrapIxs, newWrapAccount] = yield fundExistingWSolAccountInstructions(connection, owner, ownerWSolTokenAccount, owner, amountToWrapBn.toNumber());
ixs.push(...wrapIxs);
signers.push(newWrapAccount);
}
return [ixs, signers];
});
}
// export async function createWrappedSolTokenAccountInstructions(
// connection: Connection,
// amountToWrapInLamports: number,
// owner: PublicKey,
// ownerWSolTokenAccount: PublicKey,
// ): Promise<[TransactionInstruction[], Keypair]> {
// // REF: https://github.com/solana-labs/solana-program-library/blob/3eccf25ece1c373a117fc9f6e6cbeb2216d86f03/token/ts/src/instructions/syncNative.ts#L28
// const wrapIxs = [
// Token.createAssociatedTokenAccountInstruction(
// payer.publicKey,
// associatedToken,
// owner,
// NATIVE_MINT,
// programId,
// ASSOCIATED_TOKEN_PROGRAM_ID
// ),
// SystemProgram.transfer({
// fromPubkey: payer.publicKey,
// toPubkey: associatedToken,
// lamports: amount,
// }),
// createSyncNativeInstruction(associatedToken, programId)
// ];
// return [wrapIxs, newWSolAccount];
// }
function sleep(ms) {
console.log('Sleeping for', ms / 1000, 'seconds');
return new Promise(resolve => setTimeout(resolve, ms));
}
const listAccountActivity = (program_1, address_1, ...args_1) => __awaiter(void 0, [program_1, address_1, ...args_1], void 0, function* (program, address, before = '', limit = 10, commitment) {
let activityRaw = [];
const finality = commitment !== undefined ? commitment : 'confirmed';
const filter = { limit };
if (before) {
filter['before'] = before;
}
const signatures = yield program.provider.connection.getSignaturesForAddress(address, filter, finality);
const txs = yield program.provider.connection.getParsedTransactions(signatures.map(s => s.signature), finality);
if (txs && txs.length) {
activityRaw = yield parseProgramTransactions(txs, program.programId, address);
activityRaw.sort((a, b) => { var _a, _b; return ((_a = b.blockTime) !== null && _a !== void 0 ? _a : 0) - ((_b = a.blockTime) !== null && _b !== void 0 ? _b : 0); });
}
const activity = activityRaw.map(i => {
var _a, _b, _c, _d, _e, _f, _g;
return {
signature: i.signature,
actionCode: i.action,
initializer: (_a = i.initializer) === null || _a === void 0 ? void 0 : _a.toBase58(),
mint: (_b = i.mint) === null || _b === void 0 ? void 0 : _b.toBase58(),
blockTime: i.blockTime,
amount: i.amount ? i.amount.toString() : '',
beneficiary: (_c = i.beneficiary) === null || _c === void 0 ? void 0 : _c.toBase58(),
destination: (_d = i.destination) === null || _d === void 0 ? void 0 : _d.toBase58(),
template: (_e = i.template) === null || _e === void 0 ? void 0 : _e.toBase58(),
destinationTokenAccount: (_f = i.destinationTokenAccount) === null || _f === void 0 ? void 0 : _f.toBase58(),
stream: (_g = i.stream) === null || _g === void 0 ? void 0 : _g.toBase58(),
utcDate: i.utcDate,
};
});
return activity;
});
exports.listAccountActivity = listAccountActivity;
function toUnixTimestamp(date) {
return Math.round(date.getTime() / 1000);
}
function parseProgramInstruction(ix, transactionSignature, transactionBlockTimeInSeconds, idlFileVersion, programId, psAccountAddress, streamAddress) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13;
if (!psAccountAddress && !streamAddress) {
throw new Error('At leaset one of psAccount or stream is required');
}
if (!ix.programId.equals(programId)) {
return null;
}
if (idlFileVersion <= 0 || idlFileVersion > constants_1.LATEST_IDL_FILE_VERSION) {
return null;
}
try {
if (!idls[idlFileVersion]) {
if (idlFileVersion === 1) {
// TODO: to avoid this if else, find a way to do dynamic imports passign concatenated paths
const importedIdl = yield Promise.resolve().then(() => __importStar(require('./msp_idl_001')));
idls[idlFileVersion] = importedIdl.IDL;
}
else if (idlFileVersion === 2) {
const importedIdl = yield Promise.resolve().then(() => __importStar(require('./msp_idl_002')));
idls[idlFileVersion] = importedIdl.IDL;
}
else if (idlFileVersion === 3) {
const importedIdl = yield Promise.resolve().then(() => __importStar(require('./msp_idl_003')));
idls[idlFileVersion] = importedIdl.IDL;
}
else if (idlFileVersion === 4) {
const importedIdl = yield Promise.resolve().then(() => __importStar(require('./msp_idl_004')));
idls[idlFileVersion] = importedIdl.IDL;
}
else if (idlFileVersion === 5) {
const importedIdl = yield Promise.resolve().then(() => __importStar(require('./msp_idl_005')));
idls[idlFileVersion] = importedIdl.IDL;
}
else {
return null;
}
}
const coder = new anchor_1.BorshInstructionCoder(idls[idlFileVersion]);
const decodedIx = coder.decode(ix.data, 'base58');
if (!decodedIx)
return null;
const accountOnlyIxs = [
'createTreasury',
'createTreasuryAndTemplate',
'modifyStreamTemplate',
'addFunds',
'treasuryWithdraw',
'refreshTreasuryData',
];
const accountAndStreamIxs = [
'createStream',
'createStreamPda',
'createStreamWithTemplate',
'createStreamPdaWithTemplate',
'allocate',
'pauseStream',
'resumeStream',
'withdraw',
'closeStream',
];
const ixName = decodedIx.name;
if (accountOnlyIxs.concat(accountAndStreamIxs).indexOf(ixName) === -1) {
return null;
}
if (streamAddress && accountAndStreamIxs.indexOf(ixName) === -1) {
return null;
}
const ixAccountMetas = ix.accounts.map(pk => {
return { pubkey: pk, isSigner: false, isWritable: false };
});
const formattedIx = coder.format(decodedIx, ixAccountMetas);
if (!formattedIx) {
return null;
}
let stream;
if (streamAddress) {
stream = (_a = formattedIx === null || formattedIx === void 0 ? void 0 : formattedIx.accounts.find(a => a.name === 'Stream')) === null || _a === void 0 ? void 0 : _a.pubkey;
if (!stream || !stream.equals(streamAddress)) {
return null;
}
}
// mult by 1000 to add milliseconds
const blockTime = transactionBlockTimeInSeconds * 1000;
let action = types_1.ActivityActionCode.Unknown;
let initializer;
let mint;
let amount;
let template;
let beneficiary;
let destination;
let destinationTokenAccount;
if (decodedIx.name === 'createStream' ||
decodedIx.name === 'createStreamPda' ||
decodedIx.name === 'createStreamWithTemplate' ||
decodedIx.name === 'createStreamPdaWithTemplate') {
action = types_1.ActivityActionCode.