UNPKG

@owstack/ows-wallet-servlet-coinbase

Version:

An OWS Wallet servlet plugin for Coinbase.

339 lines (267 loc) 9.99 kB
'use strict'; angular.module('owsWalletPlugin.services').factory('monitorService', function($rootScope, $log, lodash, coinbaseService, /* @namespace owsWalletPluginClient.api */ Session, /* @namespace owsWalletPluginClient.api */ Storage) { var root = {}; /** * Buy transction monitoring * * A buy transaction is composed of two stages with the following flow: * * Stage 1 > stage 2 > complete * * Stage 1 - Place a buy commit at Coinbase. * - Requires monitoring a Coinbase buy transaction. * - The buy may be instant or require significant time to transfer funds from a bank (for example). * * Stage 2 - Send bought funds from Coinbase to OWS wallet. * - Requires monitoring a blockchain transaction. * * Sell transction monitoring * * A sell transaction is composed of two stages with the following flow: * * Stage 1 > stage 2 > complete * * Stage 1 - Send funds to sell from OWS wallet to Coinbase account. * - Requires monitoring a blockchain transaction. * * Stage 2 - Place a sell commit at Coinbase. * - Requires monitoring a Coinbase sell transaction. * - Requires a user defined 'price sensitivity' check. If price has changed beyond user guidance then the sell is halted. */ var session = Session.getInstance(); // Get our storage instances for this monitor. var storage = new Storage([ 'monitor', ], session.plugin.header.id); // This is my id. // Map Coinbase transaction status to montor status. var statusMap = { 'failed': 'failed', 'expired': 'expired', 'canceled': 'canceled', 'completed': 'complete' } init(); // monitor = { // // // User specified // accountId: <string> - Coinbase account id for subject transaction // walletId: <string> - OWS wallet id for subject transaction // txId: <string> - Coinbase subject transaction; n/a when creating sell monitor // txHash: <string> tx.id - Blockchain transaction id for subject transaction; applies to only to sell orders // priceStopLimitAmount: <number> - The price below which the order will be halted; applies only to sell orders // pluginId: <string> - The plugin id of the transaction requestor // action: <string> - Either 'buy or 'sell' // // // Internal // status: <string> - 'failed', 'expired', 'canceled', 'complete' // stage: <number> - 1 or 2 // created: <date> // updated: <date> // log [{ // Data copied from the body of the monitor // txHash: // txId: // status: // stage: // timestamp: // }] // } root.addMonitor = function(monitor) { // Add a new transaction to monitor. var mtxs = storage.getMonitor(); if (!monitor.created) { // Create new monitor. monitor.status = 'pending'; monitor.stage = 1; monitor.created = timestamp(); monitor.log = []; mtxs.push(monitor); } else { // Update existing monitor. monitor.stage += monitor.stage; monitor.updated = timestamp(); } storage.setMonitor(mtxs); $rootScope.$emit('Local/MonitorActive', true); monitorNow(); }; root.getMonitors = function() { // Return an array of monitors. return storage.getMonitor(); }; /** * Private functions */ function init() { // Execute monitor at startup. monitorNow(); // Execute the monitor when a new block is seen. owswallet.Plugin.onEvent('host.new-block', monitorNow); }; function monitorNow() { // Monitor all transactions now. var mtxs = storage.getMonitor(); if (mtxs.length > 0) { lodash.forEach(mtxs, function(mtx) { monitor(mtx); }); $rootScope.$emit('Local/MonitorActive', true); } }; function monitor(mtx) { if (mtx.txId) { // Currently monitoring a Coinbase transaction. coinbaseService.getTransactions(mtx.accountId, mtx.txId).then(function(tx) { if (tx.status == 'completed') { switch (tx.type) { case 'buy': if (mtx.stage == 1) { // BUY STAGE 1 COMPLETE // // Completed buy transactions have their amount sent to a specified wallet. var note = tx.title + ' ' + tx.subtitle; coinbaseService.sendToWallet(mtx.accountId, mtx.walletId, note).then(function(tx) { // Switch from monitoring buy stage 1 to buy stage 2. // Create monitored tx log entry for stage 1. Reconfigure for stage 2 and start monitoring. mtx.log.push({ txId: mtx.txId, status: 'complete', stage: mtx.stage, timestamp: timestamp() }); delete mtx.txId; mtx.txHash = tx.network.hash; if (!mtx.txHash) { throw {message: 'No network transaction hash after send to wallet from Coinbase account'}; } root.startMonitor(mtx); }).catch(function(error) { stopMonitor(mtx, 'complete', { id: 'SEND_TO_WALLET_FAILED', message: error.message }); }); } else if (mtx.stage == 2) { // BUY STAGE 2 COMPLETE // // Log the completed transaction. mtx.log.push({ txId: mtx.txId, status: 'complete', stage: mtx.stage, timestamp: timestamp() }); delete mtx.txId; stopMonitor(mtx, 'complete'); } break; case 'sell': // SELL STAGE 2 COMPLETE // // Log the completed transaction. mtx.log.push({ txId: mtx.txId, status: 'complete', stage: mtx.stage, date: timestamp() }); delete mtx.txId; stopMonitor(mtx, 'complete'); break; } } else if (tx.status == 'failed' || tx.status == 'expired' || tx.status == 'canceled') { // Coinbase status ends our monitoring. // Log the transaction final status. mtx.log.push({ txId: mtx.txId, status: statusMap[tx.status], stage: mtx.stage, timestamp: timestamp() }); delete mtx.txId; stopMonitor(mtx, status); } }).catch(function(error) { $log.error('Failed to get pending transaction from Coinbase: ' + error.message); }); } else if (mtx.txHash) { // Currently monitoring a wallet transaction (only sell orders will come here). coinbaseService.getTransactions(mtx.accountId).then(function(coinbaseTxs) { // Try to find the wallet transaction at Coinbase by searching transactions for the hash. var cbTx = lodash.find(coinbaseTxs, function(cbTx) { return lodash.get(cbTx, 'cbTx.network.hash') != undefined; }); if (cbTx && cbTx.status == 'completed') { // SELL STAGE 1 COMPLETE // // Create a Coinbase sell request and check price stop limit. If price stop limit check passes then // commit the sell order. coinbaseService.sellRequest(mtx.accountId, { amount: cbTx.amount.amount, currency: cbTx.amount.currency, paymentMethodId: '' //TODO - deposit funds to USD account (or users primary account for sells??) }).then(function(sellRequest) { var totalAmount = parseFloat(sellRequest.total.amount); if (totalAmount >= mtx.priceStopLimitAmount) { // Commit the sell order. return coinbaseService.sell(cbTx.accountId, cbTx.sellId); } }).then(function(sellTx) { // Switch from monitoring sell stage 1 to sell stage 2. // Create monitored tx log entry for stage 1. Reconfigure for stage 2 and start monitoring. mtx.log.push({ txHash: mtx.txHash, status: 'complete', stage: mtx.stage, timestamp: timestamp() }); delete mtx.txHash; mtx.txId = sellTx.id; root.startMonitor(mtx); }).catch(function(error) { stopMonitor(mtx, 'complete', { id: 'SELL_COMMIT_FAILED', message: error.message }); }); } else if (cbTx && (cbTx.status == 'failed' || cbTx.status == 'expired' || cbTx.status == 'canceled')) { // Coinbase status ends our monitoring. // Log the transaction final status. mtx.log.push({ txId: mtx.txHash, status: statusMap[cbTx.status], stage: mtx.stage, timestamp: timestamp() }); delete mtx.txHash; stopMonitor(mtx, cbTx.status); } }); } }; function stopMonitor(mtx, status, error) { mtx.status = status; mtx.error = error; // Update the monitored transaction by removing the existing entry and creating a new entry. var mtxs = storage.getMonitor(); lodash.remove(mtxs, function(storedMtx) { return storedMtx.created == mtx.created; }); mtxs.push(mtx); storage.setMonitor(mtxs); }; function removeMonitor(mtx) { // Remove a transaction from the monitor. var mtxs = storage.getMonitor(); lodash.remove(mtxs, function(tx) { return tx.created == mtx.created; }); storage.setMonitor(mtxs); $rootScope.$emit('Local/MonitorActive', mtxs.length > 0); }; function timestamp() { return new Date() / 1000; }; return root; });