UNPKG

ae-channel-manager

Version:

A javascript library for managing AEternity payment channels for Pay-per-API call protocol

330 lines (302 loc) 12.7 kB
const { Channel, TxBuilder } = require('imaginea-aepp-sdk') const { Account } = require('./account') /** * ChannelManager class to manage multiple channels * @param {Object} network - Network object with url, interurl, channelUrl etc. */ function ChannelManager(network){ if(!network) throw new Error('please pass network and keypair in constructor') this.network = network const wallet = (keypair) => new Account(keypair, this.network).wallet(); const sign = (keypair) => (tag, tx) => wallet(keypair).then(wallet => wallet.signTransaction(tx)); const channelList = {} const waitForChannelStatus = channel => status => new Promise((resolve, reject) => { channel.on('statusChanged', currStatus => { console.log(`current status: '${currStatus}' waiting for: '${status}'`); if (currStatus === status) { resolve(channel); } }); channel.on('error', reject); }); const channelState = async (channel) => { return { channelId: channel.id(), round: channel.round(), status: channel.status(), state: await channel.state(), fsmId: channel.fsmId() } } const getChannel = (channelId, publicKey) => { if(!channelList.hasOwnProperty(channelId)) return Promise.reject(`no channel found with id: ${channelId}`) if(publicKey == undefined){ return channelList[channelId][Object.keys(channelList[channelId])[0]] } return channelList[channelId][publicKey] } this.Role = Object.freeze({ INITIATOR: "initiator", RESPONDER: "responder" }) /** * Retrieves balance of accounts in a channel. * @async * @method * @param {String} channelId - Channel Id * @returns {Array} List of balances of both accounts in a channel */ this.balance = async (channelId) => { const { channel,params } = getChannel(channelId) const balances = await channel.balances([params.initiatorId, params.responderId]); const account = new Account() balances[params.initiatorId] = account.toAe(balances[params.initiatorId]) balances[params.responderId] = account.toAe(balances[params.responderId]) return balances }; /** * Retrieves status of a channel. * @async * @method * @param {String} channelId Channel Id * @returns {String} status.state.channelId Id of channel * @returns {String} status.state.round Number of rounds in channel * @returns {String} status.state.status Status of channel * @returns {String} status.state.state State of channel * @returns {String} status.state.fsmId Fsm Id * @returns {Object} status.balances Balances of passed accounts */ this.status = async (channelId) => { if(!channelId) return Promise.reject('please send the channelId') const { channel } = getChannel(channelId) const status = { state: await channelState(channel), balances: await this.balance(channelId) } return status } this.setListener = (channelId, event, callback) => { if(!channelId) return Promise.reject('please send the channelId') const channel = getChannel(channelId); if (!callback || !(callback instanceof Function)) { return new Promise (resolve => channel.on(event, resolve)); } channel.on(event, callback); } /** * Retrieves list of active channel Ids. * @method * @returns {Array} List of channel Ids */ this.getChannelIds = () => { return Object.keys(channelList) } /** * Creates new channel * @async * @method * @param {String} config.initiatorId Publickey of initiator * @param {String} config.responderId Publickey of responder * @param {Int} config.initiatorDeposit Amount that initiator wants to deposit * @param {Int} config.responderDeposit Amount that responder wants to deposit * @param {String} config.host Hostname of responder * @param {Int} config.port Port of responder * @param {Role} config.role Role [INITIATOR | RESPONDER] * @param {Object} config.keypair Private and Public key pair * @returns {String} state.channelId Id of channel * @returns {String} state.round Number of rounds in channel * @returns {String} state.status Status of channel * @returns {String} state.state State of channel * @returns {String} state.fsmId Fsm Id */ this.add = (config) => { const {initiatorId, responderId, initiatorDeposit, responderDeposit, host, port, role, keypair} = config; if (!initiatorId || !responderId || !initiatorDeposit || !host || !port || !role) { return Promise.reject(`please send all the config parameters {network: (${JSON.stringify(network)}), initiatorId: (${JSON.stringify(initiatorId)}), responderId: (${JSON.stringify(responderId)}), initiatorDeposit: (${JSON.stringify(initiatorDeposit)}),host: (${JSON.stringify(host)}), port: (${JSON.stringify(port)}), role: (${JSON.stringify(role)}), keypair: ${JSON.stringify(keypair)}}`) } const account = new Account() const params = { url: this.network.channelUrl, initiatorId: initiatorId, responderId: responderId, initiatorAmount: account.toAettos(initiatorDeposit), responderAmount: account.toAettos(responderDeposit), pushAmount: 0, channelReserve: 0, ttl: 1000, host: host, port: port, lockPeriod: 10, minimumDepth: 0, role: role, } return Channel({ ...params, sign: sign(keypair) }) .then(channel => waitForChannelStatus(channel)('open')) .then(channel => { const channelId = channel.id(); console.log('channelId: ', channelId); if(channelList[channelId] == undefined) channelList[channelId] = {} channelList[channelId][keypair.publicKey] = { params: params, channel: channel } return channelState(channel) }) }; /** * Sends money from one account to another * @async * @method * @param {String} params.channelId Channel Id * @param {Object} params.keypair Private and Public key pair * @param {String} params.senderPublicKey Public key of spender's * @param {String} params.receiverPublicKey Public key of beneficiary * @param {Int} params.amount Amount to be spent (in Aetoss) * @param {String} params.memo Message * @returns {String} result.txstatus Status of the transaction * @returns {String} result.signedTx Signed transaction * @returns {String} status.round Number of rounds in channel * @returns {String} status.status Status of channel * @returns {String} status.state State of channel * @returns {String} status.fsmId Fsm Id * @returns {Object} status.balances Balances of passed accounts */ this.update = async (params) => { const { channelId, keypair, senderPublicKey, receiverPublicKey, amount, memo } = params if(!channelId || !senderPublicKey || !receiverPublicKey || !amount) return Promise.reject(`either of these parameters are empty: channelId, senderPublicKey, receiverPublicKey, amount`) const { channel } = getChannel(channelId, keypair.publicKey) const account = new Account(keypair, this.network) const wallet = await account.wallet() // from, to, amount, sign, metadata return channel.update( senderPublicKey, receiverPublicKey, account.toAettos(amount), async (tx) => wallet.signTransaction(tx), [memo] ).then(async (result) => { return { result: result, status: await this.status(channelId) } }) } this.update_old = async (params) => { const { channelId, keypair, from, to, amount, signedTx, block_hash, memo } = params if(!channelId || !from || !to || !amount || !signedTx) return Promise.reject(`either of these parameters are empty: channelId, updates, signedTx`) const { channel } = getChannel(channelId, keypair.publicKey) const account = new Account(keypair, this.network) const wallet = await account.wallet() return channel.update_old( from, to, amount, signedTx, block_hash, async (tx) => wallet.signTransaction(tx), [memo] ).then(async (result) => { return { result: result, status: await this.status(channelId) } }) } this.update_full = async (params) => { const { channelId, keypair, signedTx, memo } = params if(!channelId || !signedTx) return Promise.reject(`either of these parameters are empty: channelId, signedTx`) const { channel } = getChannel(channelId, keypair.publicKey) const account = new Account(keypair, this.network) const wallet = await account.wallet() return channel.update_full( signedTx, async (tx) => wallet.signTransaction(tx), [memo] ).then(async (result) => { return { result: result, status: await this.status(channelId) } }) } /** * Disconnect the channel * @async * @method * @param {String} channelId Channel Id * @returns {String} result.status Id of channel * @returns {String} result.signedTx Number of rounds in channel */ this.leave = async (channelId, publicKey) => { if(!channelId) return Promise.reject('please send the channelId') if(!publicKey) return Promise.reject('please send the publicKey') const {channel} = getChannel(channelId, publicKey) const result = await channel.leave(); channelList[channelId][publicKey].offchainTx = result.signedTx return result } /** * Reconnect the channel * @async * @method * @param {String} channelId Channel Id * @param {Object} keypair Private and Public key pair * @returns {Boolean} isConnected True or false */ this.reconnect = async (channelId, keypair) => { if(!channelId) return Promise.reject('please send the channelId') const { params, channel, offchainTx } = getChannel(channelId, keypair.publicKey) if(!offchainTx) return Promise.reject('can not reconnect before leaving the channel') const round = channel.round(); const fsmId = channel.fsmId(); const account = new Account(keypair, this.network) const wallet = await account.wallet() return Channel({ url: params.url, host: params.host, port: params.port, role: params.role, round, existingChannelId: channelId, offchainTx, existingFsmId: fsmId, sign: async (tx) => wallet.signTransaction(tx) }).then(channel => { return waitForChannelStatus(channel)('open') }).then(async (ch) => { channelList[channelId][keypair.publicKey].channel = ch return true }) } /** * Close the channel * @async * @method * @param {String} channelId Channel Id * @param {Object} keypair Private and Public key pair * @returns {String} txType Type of transaction (Ex. mutualClose) * @returns {String} tx The transaction */ this.close = async (channelId, keypair) => { if(!channelId || !keypair) return Promise.reject('please send the channelId and keypair') const {channel} = getChannel(channelId, keypair.publicKey) const account = new Account(keypair, this.network) const wallet = await account.wallet() const result = await channel.shutdown( async (tx) => await wallet.signTransaction(tx) ) delete channelList[channelId] const { txType, tx } = TxBuilder.unpackTx(result) return { result, txType, tx } } } module.exports = { ChannelManager }