UNPKG

@mean-dao/payment-streaming

Version:
1,042 lines 60 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()); }); }; 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.