melis-api-js
Version:
Melis Javascript API bindings
1,559 lines (1,385 loc) • 88.2 kB
JavaScript
require('isomorphic-fetch');
const Q = require('q')
const events = require('events')
const Stomp = require('webstomp-client')
const WebSocketClient = require('ws')
const SockJS = require('sockjs-client')
const Bitcoin = require('bitcoinjs-lib')
const isNode = (require('detect-node') && !('electron' in global.process.versions))
const randomBytes = require('randombytes')
const sjcl = require('sjcl-all')
const C = require("./cm-constants")
const FeeApi = require("./fee-api")
const BC_APIS = require("./blockchain-apis")
const CoinDrivers = require("./drivers")
const logger = require("./logger")
const MelisErrorModule = require("./melis-error")
const MelisError = MelisErrorModule.MelisError
const throwUnexpectedEx = MelisErrorModule.throwUnexpectedEx
function walletOpen(target, hd, serverWalletData, isSingleAccount) {
if (!hd || !serverWalletData)
throwUnexpectedEx("No data opening wallet")
const accounts = {}, balances = {}, infos = {}, keys = {}
serverWalletData.accounts.forEach((a, i) => {
accounts[a.pubId] = a
balances[a.pubId] = serverWalletData.balances[i]
infos[a.pubId] = serverWalletData.accountInfos[i]
if (isSingleAccount)
keys[a.num] = hd
else
keys[a.num] = target.deriveAccountHdKey(hd, a.num, a.coin)
})
if (isSingleAccount)
target.hdAccount = hd
else
target.hdWallet = hd
target.walletData = {
accounts, balances, infos, keys
}
// Transforms arrays in objects
serverWalletData.accounts = accounts
serverWalletData.balances = balances
serverWalletData.accountInfos = infos
emitEvent(target, C.EVENT_WALLET_OPENED, serverWalletData)
}
function walletClose(target) {
target.hdWallet = null
target.hdAccount = null
target.walletData = null
}
function updateWalletInfo(target, info) {
target.walletData.info = info
}
function updateAccount(target, accountData, hdWallet) {
const account = accountData.account
if (!target.walletData.accounts[account.pubId])
target.walletData.accounts[account.pubId] = account
target.walletData.balances[account.pubId] = accountData.balance
if (accountData.accountInfo)
target.walletData.infos[account.pubId] = accountData.accountInfo
if (hdWallet)
target.walletData.keys[account.num] = target.deriveAccountHdKey(hdWallet, account.num, account.coin)
}
function updateServerConfig(target, config) {
if (config.message)
logger.log("Server message status: " + config.message)
target.useTestPaths = !(!config.platform || config.platform === "production")
target.cmConfiguration = config
target.lastBlocks = config.topBlocks
target.platform = config.platform
}
function possiblyIncompleteAccountInfo(info) {
return !info || !info.account || info.account.status == C.STATUS_WAITING_COSIGNERS
|| (info.cosigners && info.cosigners.length > 1 && !info.scriptParams)
}
function emitEvent(target, event, params) {
target.lastReceivedMsgDate = new Date()
if (event === C.EVENT_DISCONNECT_REQ) {
logger.log("Server requested to disconnect:", params)
return handleConnectionLoss(target, true)
}
if (event === C.EVENT_PING)
target.stompClient.send(C.UTILS_PONG, {}, {})
if (event !== C.EVENT_RPC_ACTIVITY_END && event !== C.EVENT_RPC_ACTIVITY_START)
logger.log("[CM emitEvent] " + event + " params: " + JSON.stringify(params))
if (event === C.EVENT_CONFIG)
updateServerConfig(target, params)
if (event === C.EVENT_ACCOUNT_UPDATED)
updateAccount(target, params)
var listeners = target.listeners(event)
if (!listeners.length) {
//logger.log("[CM emitEvent] nessun listener per l'evento '" + event + "'")
target.emit(C.UNHANDLED_EVENT, { name: event, params: params })
} else {
target.emit(event, params)
}
}
function buildMelisErrorFromServerEx(err) {
const res = new MelisError(err.ex, err.msg)
for (var prop in err) {
if (prop !== 'ex' && prop !== 'msg')
res[prop] = err[prop]
}
return res
}
function buildBadParamEx(paramName, msg) {
//return {ex: 'CmBadParamException', param: paramName, msg: msg}
const err = new MelisError('CmBadParamException', msg)
err.param = paramName
return err
}
function buildInvalidAddressEx(address, msg) {
return new MelisError('CmInvalidAddressException', msg)
}
function buildConnectionFailureEx(msg) {
return new MelisError('ConnectionFailureException', msg)
}
function failPromiseWithEx(ex) {
return Q.reject(ex)
}
function failPromiseWithBadParam(paramName, msg) {
return Q.reject(buildBadParamEx(paramName, msg))
}
function throwBadParamEx(paramName, msg) {
throw buildBadParamEx(paramName, msg)
}
function throwInvalidSignatureEx(msg) {
throw new MelisError('CmInvalidSignatureException', msg)
}
function throwConnectionEx(msg) {
throw buildConnectionFailureEx(msg)
}
function initializePrivateFields(target) {
target.rpcCounter = 0
if (target.waitingReplies) {
for (var d in target.waitingReplies) {
var deferred = target.waitingReplies[d].deferred
deferred.reject(C.EVENT_DISCONNECT)
}
}
target.waitingReplies = {}
target.hdWallet = null
target.accountExtendedKey = null
target.walletData = null
target.lastBlocks = {}
target.lastOpenParams = null
target.cmConfiguration = null // Got from server at connect
target.connected = false
target.connecting = false
target.paused = false
target.stompClient = null
}
function addPagingInfo(pars, pagingInfo) {
if (pagingInfo) {
pars.page = pagingInfo.page || 0
pars.size = pagingInfo.size || 20
if (pagingInfo.sortField) {
pars.sortField = pagingInfo.sortField
pars.sortDir = pagingInfo.sortDir
}
}
return pars
}
function simpleRandomInt(max) {
return Math.floor(Math.random() * max)
}
function handleConnectionLoss(target) {
if (!target.connected)
return Q()
var deferred = Q.defer()
target.stompClient.disconnect(function (res) {
stompDisconnected(target, res, null)
deferred.resolve()
})
return deferred.promise
}
function keepAliveFunction(target) {
var nowTime = new Date().getTime()
var lastMsgTime = target.lastReceivedMsgDate ? target.lastReceivedMsgDate.getTime() : nowTime - 1
var secsElapsed = (nowTime - lastMsgTime) / 1000 + 1
// console.log("[KEEPALIVE] elapsed from last msg: " + secsElapsed + " minKeepAlive: " + target.maxKeepAliveSeconds)
if (secsElapsed >= target.maxKeepAliveSeconds + 20) {
logger.logWarning("No response from server since " + secsElapsed + " seconds: DISCONNECTING")
handleConnectionLoss(target)
} else if (secsElapsed >= target.maxKeepAliveSeconds / 2) {
target.ping()
}
}
function rpcReplyHandler(target, res) {
// logger.log("[STOMP] Ricevuta risposta RPC: " + res)
//var messageId = res.headers.myId
target.lastReceivedMsgDate = new Date()
var message = JSON.parse(res.body)
var messageId = message.id
//logger.log("[STOMP] rpcReplyHandler message: " + JSON.stringify(message))
if (messageId) {
var rpcData = target.waitingReplies[messageId]
delete target.waitingReplies[messageId]
if (rpcData)
rpcData.deferred.resolve(message.m)
else
logger.logError("[STOMP] RPC reply con ID: " + messageId + " non trovato in coda")
} else {
logger.logWarning("[STOMP] RPC reply senza ID:", message)
}
}
function rpcErrorHandler(target, res) {
logger.log("[STOMP] RPC Exception:", res)
target.lastReceivedMsgDate = new Date()
//var messageId = res.headers.myId
const message = JSON.parse(res.body)
const messageId = message.id
if (messageId) {
const rpcData = target.waitingReplies[messageId]
delete target.waitingReplies[messageId]
if (rpcData) {
if (message.ex === C.EX_TOO_MANY_REQUESTS && rpcData.numRetries < target.rpcMaxRetries) {
var rpcRetryDelay = target.rpcRetryDelay * rpcData.numRetries
logger.log("Server requested to slow down requests -- retry #" + rpcData.numRetries + " waiting " + rpcRetryDelay + "ms")
setTimeout(function () {
logger.log("Preparing new request")
target.rpc(rpcData.queue, rpcData.data, rpcData.headers, rpcData.numRetries + 1).then(function (res) {
rpcData.deferred.resolve(res)
}).catch(function (res) {
logger.log("RE-REQUEST FAILED:", res)
rpcData.deferred.reject(buildMelisErrorFromServerEx(res))
})
}, rpcRetryDelay)
} else
rpcData.deferred.reject(buildMelisErrorFromServerEx(message))
} else {
logger.logError("[STOMP] RPC Error -- Unable to find request with ID: " + messageId)
}
}
}
function CM(config) {
if (!config)
config = {}
if (config.useTestPaths)
this.useTestPaths = config.useTestPaths
if (config.stompEndpoint || process.env.MELIS_ENDPOINT)
this.stompEndpoint = process.env.MELIS_ENDPOINT || config.stompEndpoint
this.apiDiscoveryUrl = process.env.MELIS_DISCOVER || config.apiDiscoveryUrl || C.MELIS_DISCOVER
this.apiUrls = null
this.rpcTimeout = config.rpcTimeout >= 100 ? config.rpcTimeout : 60000
this.rpcRetryDelay = config.rpcRetryDelay >= 10 ? config.rpcRetryDelay : 1500
this.rpcMaxRetries = config.rpcMaxRetries >= 1 ? config.rpcMaxRetries : 10
this.autoReconnectDelay = config.autoReconnectDelay >= 0 ? config.autoReconnectDelay : 30
this.maxKeepAliveSeconds = config.maxKeepAliveSeconds >= 20 ? config.maxKeepAliveSeconds : 60
this.disableKeepAlive = config.disableKeepAlive === true
this.connected = false
this.autoReconnectFunc = null
this.stompClient = null
this.externalTxValidator = null
this.minutesBetweenNetworkFeesUpdates = 60
this.feeApi = new FeeApi({ melis: this })
initializePrivateFields(this)
}
CM.prototype = Object.create(events.EventEmitter.prototype)
// by default f is console
CM.prototype.setLogger = function (f) {
if (f)
logger.setLogObject(f)
else
logger.setLogObject({
log: (a, b) => { },
logWarning: (a, b) => { },
logError: (a, b) => { }
})
}
CM.prototype.getRpcTimeout = function () {
return this.rpcTimeout
}
CM.prototype.setRpcTimeout = function (ms) {
if (ms >= 1 && ms <= 1000000)
this.rpcTimeout = ms
else
throwBadParamEx(ms, "Timeout ms must be between 1 and 1000000")
}
CM.prototype.isProdNet = function () {
return !this.useTestPaths
}
//
// Coin dependent functions
//
function getSupportedCoins() {
return Object.keys(CoinDrivers)
}
function getDriver(coin) {
const driver = CoinDrivers[coin]
if (!driver)
throw new MelisError("Unknown coin: " + coin)
return driver
}
CM.prototype.getDefaultPlatformCoin = function (coin) {
return this.isProdNet() ? C.COIN_PROD_BTC : C.COIN_TEST_BTC
}
CM.prototype.getCoinDriver = function (coin) {
return getDriver(coin)
}
CM.prototype.decodeCoinAddress = function (coin, address) {
return getDriver(coin).decodeCoinAddress(address)
}
CM.prototype.hashForSignature = function (coin, tx, index, redeemScript, amount, hashFlags) {
return getDriver(coin).hashForSignature(tx, index, redeemScript, amount, hashFlags)
}
CM.prototype.isValidAddress = function (coin, address) {
return getDriver(coin).isValidAddress(address)
}
CM.prototype.toScriptSignature = function (coin, signature, hashFlags) {
return getDriver(coin).toScriptSignature(signature, hashFlags)
}
CM.prototype.toOutputScript = function (coin, address) {
return getDriver(coin).toOutputScript(address)
}
CM.prototype.wifToEcPair = function (coin, wif) {
return getDriver(coin).wifToEcPair(wif)
}
CM.prototype.signMessageWithKP = function (coin, keyPair, message) {
return getDriver(coin).signMessageWithKP(keyPair, message)
}
CM.prototype.verifyMessageSignature = function (coin, address, signature, message) {
return getDriver(coin).verifyMessageSignature(address, signature, message)
}
CM.prototype.signMessageWithAA = function (account, aa, message) {
if (account.type !== C.TYPE_PLAIN_HD)
throw new MelisError('CmBadParamException', 'Only single signature accounts can sign messages')
const key = this.deriveAddressKey(account.num, aa.chain, aa.hdindex)
return this.signMessageWithKP(account.coin, key.keyPair, message)
}
CM.prototype.buildAddressFromScript = function (coin, script) {
return getDriver(coin).buildAddressFromScript(script)
}
CM.prototype.pubkeyToAddress = function (coin, key) {
return getDriver(coin).pubkeyToAddress(key)
}
CM.prototype.prepareAddressSignature = function (coin, keyPair, prefix) {
return getDriver(coin).prepareAddressSignature(keyPair, prefix)
}
CM.prototype.extractPubKeyFromOutputScript = function (coin, script) {
return getDriver(coin).extractPubKeyFromOutputScript(script)
}
CM.prototype.calcP2SH = function (coin, accountInfo, chain, hdIndex) {
return getDriver(coin).calcP2SH(accountInfo, chain, hdIndex)
}
CM.prototype.derivePubKeys = function (coin, xpubs, chain, hdIndex) {
return getDriver(coin).derivePubKeys(xpubs, chain, hdIndex)
}
CM.prototype.hdNodeFromHexSeed = function (seed, coin) {
if (!coin)
coin = this.getDefaultPlatformCoin()
return getDriver(coin).hdNodeFromHexSeed(seed)
}
CM.prototype.hdNodeFromBase58 = function (xpub, coin) {
if (!coin)
coin = this.getDefaultPlatformCoin()
return getDriver(coin).hdNodeFromBase58(xpub)
}
CM.prototype.hdNodeToBase58Xpub = function (hd, coin) {
if (!coin)
coin = this.getDefaultPlatformCoin()
return getDriver(coin).hdNodeToBase58Xpub(hd)
}
CM.prototype.exportAccountMasterKeyToBase58 = function (account) {
const hd = this.peekAccountMasterKey(account)
return getDriver(account.coin).hdNodeToExtendedBase58(hd)
}
CM.prototype.updateNetworkFees = function (coin) {
var self = this
if (!self.feeInfos)
self.feeInfos = {}
if (self.feeInfos[coin] && self.feeInfos[coin].lastUpdated) {
const msToLastUpdate = new Date() - self.feeInfos[coin].lastUpdated
if (msToLastUpdate > 1000 * 60 * 15) // Update fees not more than once every 15 minutes
return self.feeInfos[coin]
}
const provider = coin.endsWith(C.COIN_PROD_BTC) ? 'melis' : 'hardcoded'
return self.feeApi.getFeesByProvider(coin, provider)().then(res => {
return self.feeInfos[coin] = res
})
}
CM.prototype.setAutoReconnectDelay = function (seconds) {
if (seconds >= 0)
this.autoReconnectDelay = seconds
else
this.autoReconnectDelay = 0
}
CM.prototype.randomBytes = function (n) {
return randomBytes(n)
}
CM.prototype.randomHexBytes = function (n) {
return this.randomBytes(n).toString('hex')
}
CM.prototype.random32HexBytes = function () {
return this.randomHexBytes(32)
}
CM.prototype.isConnected = function () {
return this.connected
}
CM.prototype.isReady = function () {
return !(!this.cmConfiguration)
}
CM.prototype.parseBIP32Path = function (path, radix) {
if (!radix)
radix = 10
if (path.indexOf("m/") === 0)
path = path.substring(2)
var result = []
var pathElems = path.split("/")
for (var i = 0; i < pathElems.length; i++) {
var hardened = false
var val = pathElems[i]
if (val.charAt(val.length - 1) === '\'') {
hardened = true
val = val.substring(0, val.length - 1)
}
val = parseInt(val, radix)
if (val >= 0x80000000)
throwBadParamEx('path', "Invalid path element: " + val)
result.push((hardened ? (0x80000000) | val : val) >>> 0)
}
return result
}
CM.prototype.getLoginPath = function () {
const product = 31337 // CM
const path = this.isProdNet() ? 0 : 1 // Use another path for I2P/TOR?
return [
((0x80000000) | product) >>> 0,
((0x80000000) | path) >>> 0
]
}
CM.prototype.deriveKeyFromPath = function (hdnode, path) {
if (!path || path.length === 0)
return hdnode
let key = hdnode
for (let i = 0; i < path.length; i++) {
const index = path[i]
if (index & 0x80000000) {
const v = index & 0x7FFFFFFF
key = key.deriveHardened(v)
} else {
key = key.derive(index)
}
}
return key
}
// BIP44 standard derivation
CM.prototype.deriveAccountHdKey = function (hd, accountNum, coin) {
const subTree = this.isProdNet() ? 0 : 1
let key = hd.deriveHardened(44)
key = key.deriveHardened(subTree)
key = key.deriveHardened(accountNum)
if (coin)
getDriver(coin).fixKeyNetworkParameters(key)
return key
}
CM.prototype.deriveChainIndex = function (accountKey, chain, index) {
return accountKey.derive(chain).derive(index)
}
CM.prototype.deriveAddressKey = function (accountNum, chain, index) {
const accountHd = this.walletData.keys[accountNum]
return accountHd.derive(chain).derive(index)
}
CM.prototype.peekAccountMasterKey = function (account) {
return this.walletData.keys[account.num]
}
CM.prototype.exportAddressKeyToWIF = function (account, aa) {
const key = this.deriveAddressKey(account.num, aa.chain, aa.hdindex)
return key.keyPair.toWIF()
}
CM.prototype.rpc = function (queue, data, headers, numRetries) {
logger.log("[RPC] q: " + queue + (headers ? " h: " + JSON.stringify(headers) : " no headers") + (data ? " data: " + JSON.stringify(data) : " no data"))
if (!this.connected)
return Q.reject(buildConnectionFailureEx("RPC call without connection"))
if (!queue)
return Q.reject(buildBadParamEx('queue', "RPC call without defined queue"))
var deferred = Q.defer()
this.rpcCounter++
if (Object.keys(this.waitingReplies).length === 0) {
emitEvent(this, C.EVENT_RPC_ACTIVITY_START)
}
this.pendingRPC++
var rpcCounter = this.rpcCounter
this.waitingReplies[rpcCounter] = {
deferred: deferred,
queue: queue,
headers: headers,
data: data,
numRetries: numRetries || 1
}
// logger.log("[STOMP] queue: " + queue + " data: " + JSON.stringify(data) + " typeof(data): " + typeof data)
if (!headers)
headers = {}
headers.id = rpcCounter
if (data !== undefined && data !== null)
this.stompClient.send(queue, typeof data === "object" ? JSON.stringify(data) : data, headers)
else
this.stompClient.send(queue, "{}", headers)
var self = this
return deferred.promise.timeout(this.rpcTimeout).catch(function (err) {
logger.log("[RPC] Ex or Timeout -- res: ", err)
var ex
if (err.code && err.code === 'ETIMEDOUT') {
ex = new MelisError('RpcTimeoutException', 'RPC call timeout after ' + self.rpcTimeout + 'ms')
//ex = {ex: "rpcTimeout", msg: 'RPC call timeout after ' + self.rpcTimeout + 'ms'}
delete self.waitingReplies[rpcCounter]
} else
ex = buildMelisErrorFromServerEx(err)
return Q.reject(ex)
}).finally(function () {
if (Object.keys(self.waitingReplies).length === 0) {
emitEvent(self, C.EVENT_RPC_ACTIVITY_END)
}
})
}
CM.prototype.simpleRpcSlice = function (queue, data) {
return this.rpc(queue, data).then(res => {
return res.slice
})
}
// Fetch the STOMP endpoint from the melis discover server
function fetchStompEndpoint(self) {
var discoveryUrl = self.apiDiscoveryUrl
logger.log("Discovering STOMP endpoint using: ", discoveryUrl)
return fetch(discoveryUrl, {
headers: { "user-agent": "melis-js-api/" + C.CLIENT_API_VERSION }
}).then(function (res) {
if (res.status !== 200)
throw new MelisError('DiscoveryEx', 'Bad status code: ' + res.status)
return res.json()
}).then(function (discovered) {
logger.log("Discovery result: ", discovered)
self.apiUrls = discovered
if (discovered.publicUrlPrefix || discovered.stompEndpoint)
return discovered
throw new MelisError('DiscoveryEx', 'Missing discovery data from ' + discoveryUrl)
}).catch(function (res) {
if (res.ex === "DiscoveryEx")
throw res
var stringMsg = "" + res
if (stringMsg.includes("SyntaxError: Unexpected token"))
throw new MelisError('DiscoveryEx', 'Unable to discover stompEndpoint from ' + discoveryUrl)
else
throw new MelisError('DiscoveryEx', stringMsg)
})
}
function enableKeepAliveFunc(self) {
logger.log("[enableKeepAliveFunc] self.keepAliveFunc: " + self.keepAliveFunc)
if (self.disableKeepAlive || self.keepAliveFunc)
return
self.keepAliveFunc = setInterval(function () {
keepAliveFunction(self)
}, (self.maxKeepAliveSeconds / 2 + 1) * 1000)
}
function disableKeepAliveFunc(self) {
logger.log("[disableKeepAliveFunc] self.keepAliveFunc: " + self.keepAliveFunc)
if (self.keepAliveFunc) {
clearInterval(self.keepAliveFunc)
self.keepAliveFunc = null
}
}
function disableAutoReconnect(self) {
if (self.autoReconnectFunc) {
clearTimeout(self.autoReconnectFunc)
self.autoReconnectFunc = null
}
}
function stompDisconnected(self, frame, deferred) {
var wasConnected = self.connected
var wasPaused = self.paused
logger.log("[CM] stompDisconnected wasConnected: " + wasConnected + " wasPaused: " + wasPaused)// + " err.code: " + frame.code + " err.wasClean: " + frame.wasClean)
self.stompClient = null
self.connected = false
self.connecting = false
self.paused = false
self.cmConfiguration = null
disableKeepAliveFunc(self)
if (deferred)
deferred.reject(frame)
//logger.log("Open requests: ", Object.keys(self.waitingReplies))
Object.keys(self.waitingReplies).forEach(function (i) {
var rpcData = self.waitingReplies[i]
delete self.waitingReplies[i]
logger.log('[CM] Cancelling open rpc request:', rpcData)
rpcData.deferred.reject(buildConnectionFailureEx("Disconnected"))
})
self.waitinReplies = {}
emitEvent(self, C.EVENT_DISCONNECT)
if (wasPaused || !wasConnected)
return
if (self.autoReconnectDelay > 0 && self.autoReconnectFunc === null) {
var timeout = 10 + Math.random() * 10 + Math.random() * (self.autoReconnectDelay / 10)
logger.log("[CM] NEXT AUTO RECONNECT in " + timeout + " seconds")
self.autoReconnectFunc = setTimeout(function () {
self.autoReconnectFunc = null
self.connect(self.lastConfig)
}, timeout * 1000)
}
}
function retryConnect(self, config, errorMessage) {
logger.log(errorMessage)
if (self.autoReconnectDelay > 0) {
var timeout = 10 + Math.random() * 10 + Math.random() * (self.autoReconnectDelay / 10)
logger.log("[CM] retryConnect in " + timeout + " seconds")
return Q.delay(timeout * 1000).then(function () {
self.connecting = false
return self.connect(config)
})
} else
throwConnectionEx(errorMessage)
}
CM.prototype.connect = function (config) {
const self = this
if (this.connecting)
return Q()
this.paused = false
this.connecting = true
if (this.autoReconnectFunc) {
clearTimeout(this.autoReconnectFunc)
this.autoReconnectFunc = null
}
if (this.stompClient !== null) {
if (this.connected)
return Q(self.cmConfiguration)
this.stompClient.disconnect()
this.stompClient = null
}
const discoverer = self.stompEndpoint ?
Q(self.stompEndpoint) :
Q(fetchStompEndpoint(self, config)).then(function (discovered) {
return discovered.stompEndpoint
})
return discoverer.then(stompEndpoint => {
return self.connect_internal(stompEndpoint, config)
}).catch(err => {
logger.log("Discover err:", err)
const errMsg = 'Unable to connect: ' + err.ex + " : " + err.msg
const callback = config ? config.connectProgressCallback : null
if (callback && typeof callback === 'function')
callback({ errMsg: errMsg, err: err })
if (config && config.autoRetry)
return retryConnect(self, config, errMsg)
else {
self.connecting = false
return Q.reject(err)
}
})
}
CM.prototype.connect_internal = function (stompEndpoint, config) {
const self = this
const deferred = Q.defer()
const options = { debug: false, heartbeat: false, protocols: ['v12.stomp'] }
if ((/^wss?:\/\//).test(stompEndpoint)) {
if (isNode) {
logger.log("[STOMP] Opening websocket (node):", stompEndpoint)
const ws = new WebSocketClient(stompEndpoint)
ws.on('error', function (error) {
logger.log('[connect_internal] CONNECT ERROR:' + error.code)
deferred.reject(error)
})
this.stompClient = Stomp.over(ws, options)
} else {
logger.log("[STOMP] Opening websocket (browser) to " + stompEndpoint + " options:", options)
this.stompClient = Stomp.client(stompEndpoint, options)
}
} else {
logger.log("[STOMP] Opening sockjs:", stompEndpoint)
this.stompClient = Stomp.over(new SockJS(stompEndpoint), options)
}
this.stompClient.debug = function (str) {
//logger.log(str)
}
var headers = {}
if (config && config.userAgent)
headers.userAgent = JSON.stringify(config.userAgent)
if (config && config.locale)
headers.locale = config.locale
if (config && config.currency)
headers.currency = config.currency
this.lastConfig = config
this.stompClient.connect(headers, function (frame) {
logger.log("[CM] Connected to websocket: " + frame)
self.connected = true
self.connecting = false
self.paused = false
self.stompClient.subscribe(C.QUEUE_RPC_REPLY, function (message) {
rpcReplyHandler(self, message)
})
self.stompClient.subscribe(C.QUEUE_RPC_ERROR, function (message) {
rpcErrorHandler(self, message)
})
self.stompClient.subscribe(C.QUEUE_SERVER_EVENTS, function (message) {
//logger.log("[CM] Server event: " + message.body)
var msg = JSON.parse(message.body)
emitEvent(self, msg.type, msg.params)
})
self.stompClient.subscribe(C.QUEUE_PUBLIC_MSGS, function (message) {
var msg = JSON.parse(message.body)
if (msg.type && msg.type === C.EVENT_PING)
emitEvent(self, C.EVENT_PING, msg)
else
emitEvent(self, C.EVENT_PUBLIC_MESSAGE, msg)
})
self.stompClient.subscribe(C.QUEUE_BLOCKS, function (message) {
const msg = JSON.parse(message.body)
self.lastBlocks[msg.coin] = msg
emitEvent(self, C.EVENT_BLOCK, msg)
})
self.stompClient.subscribe(C.QUEUE_CONFIG, function (message) {
var initialEvents = JSON.parse(message.body)
for (var i = 0; i < initialEvents.length; i++) {
var event = initialEvents[i]
emitEvent(self, event.type, event.params)
}
if (self.lastOpenParams) {
if (self.lastOpenParams.accountExtendedKey) {
self.accountOpen(self.lastOpenParams.accountExtendedKey, self.lastOpenParams).then(wallet => {
emitEvent(self, C.EVENT_SESSION_RESTORED, wallet)
})
} else if (self.lastOpenParams.seed) {
self.walletOpen(self.lastOpenParams.seed, self.lastOpenParams).then(wallet => {
emitEvent(self, C.EVENT_SESSION_RESTORED, wallet)
})
}
}
if (self.cmConfiguration.maxKeepAliveSeconds && self.cmConfiguration.maxKeepAliveSeconds < self.maxKeepAliveSeconds)
self.maxKeepAliveSeconds = self.cmConfiguration.maxKeepAliveSeconds
enableKeepAliveFunc(self)
emitEvent(self, C.EVENT_CONNECT)
deferred.resolve(self.cmConfiguration)
})
}, function (frame) {
stompDisconnected(self, frame, deferred)
})
return deferred.promise
}
CM.prototype.disconnect = function () {
const self = this
disableKeepAliveFunc(self)
disableAutoReconnect(self)
if (!this.connected)
return Q()
var deferred = Q.defer()
this.stompClient.disconnect(res => {
logger.log("[CM] STOMP Client disconnect: " + res)
this.stompClient = null
initializePrivateFields(self)
deferred.resolve(res)
})
return deferred.promise
}
CM.prototype.networkOnline = function () {
if (this.autoReconnectDelay > 0)
return this.connect()
else
return Q()
}
CM.prototype.networkOffline = function () {
disableKeepAliveFunc(this)
this.paused = true
return handleConnectionLoss(this)
}
CM.prototype.hintDevicePaused = function () {
disableKeepAliveFunc(this)
this.paused = true
if (this.connected)
this.sessionSetParams({ paused: true })
return Q()
}
CM.prototype.verifyConnectionEstablished = function (timeout) {
var self = this
this.paused = false
if (!timeout || timeout < 0)
timeout = 5
if (timeout > this.maxKeepAliveSeconds)
timeout = this.maxKeepAliveSeconds
logger.log("[verifyConnectionEstablished] connected: " + this.connected + " timeout: " + timeout + " stompClient: " + (this.stompClient ? "yes" : "no"))
if (!this.stompClient)
return Q()
if (!this.connected)
return this.connect()
return this.ping().timeout(timeout * 1000).catch(function (err) {
logger.log("[verifyConnectionEstablished] ping timeout after " + timeout + " seconds")
return handleConnectionLoss(self)
}).then(function () {
enableKeepAliveFunc(self)
})
}
CM.prototype.subscribe = function (queue, callback, headers) {
if (!queue || !callback)
throwBadParamEx('queue', "Call to subscribe without defined queue or callback")
var self = this
return this.stompClient.subscribe(queue, function (res) {
// logger.log("[CM] message to queue " + queue + " : ", res)
var msg = JSON.parse(res.body)
callback(msg)
}, headers)
}
CM.prototype.subscribeToTickers = function (currency, callback) {
if (!currency || !callback)
throwBadParamEx('currency', "Missing currency or callback while subscribing to tickers")
return this.subscribe(C.QUEUE_TICKERS_PREFIX + currency, callback)
}
CM.prototype.subscribeToTickersHistory = function (period, currency, callback) {
if (!period || !currency || !callback)
throwBadParamEx('currency', "Missing period, currency or callback while subscribing to history: " + currency)
var path = C.QUEUE_TICKERS_HISTORY_PREFIX + period + "/" + currency
return this.subscribe(path, callback)
}
//
// PUBLIC METHODS
//
CM.prototype.getPaymentAddressForAccount = function (accountIdOrAlias, param) {
const opts = { name: accountIdOrAlias }
if (param) {
if (param.memo)
opts.data = param.memo
if (param.address)
opts.address = param.address
}
return this.rpc(C.GET_PAYMENT_ADDRESS, opts).then(function (res) {
//logger.log("[CM] getPaymentAddress: ", res)
return res.address
})
}
CM.prototype.accountGetPublicInfo = function (params) {
return this.rpc(C.GET_ACCOUNT_PUBLIC_INFO, { name: params.name, code: params.code }).then(function (res) {
//logger.log("[CM] accountGetPublicInfo: " + JSON.stringify(res))
return res.account
})
}
CM.prototype.getLoginChallenge = function () {
return this.rpc(C.GET_CHALLENGE)
}
//
// UTILITIES
//
CM.prototype.decodeTxFromBuffer = function (buf) {
return Bitcoin.Transaction.fromBuffer(buf)
}
CM.prototype.pushTx = function (coin, hex) {
return this.rpc(C.UTILS_PUSH_TX, { coin, hex })
}
CM.prototype.getFeeInfo = function (coin) {
return this.rpc(C.UTILS_FEE_INFO + "/" + coin)
}
CM.prototype.ping = function () {
return this.rpc(C.UTILS_PING)
}
CM.prototype.logException = function (account, data, deviceId, agent) {
return this.rpc(C.UTILS_LOG_EX, {
pubId: account ? account.pubId : null,
data: data,
deviceId: deviceId,
ua: typeof agent === "object" ? agent : { application: agent }
})
}
CM.prototype.logData = function (account, data, deviceId, agent) {
return this.rpc(C.UTILS_LOG_DATA, {
pubId: account.pubId,
data: data,
deviceId: deviceId,
ua: typeof agent === "object" ? agent : { application: agent }
})
}
CM.prototype.deviceSetPassword = function (deviceName, pin) {
if (!deviceName || !pin)
return failPromiseWithBadParam(deviceName ? "pin" : "deviceName", "missing deviceName or pin")
var self = this
return this.rpc(C.WALLET_DEVICE_SET_PASSWORD, {
deviceName: deviceName,
userPin: pin
}).then(function (res) {
// The result is base64 encoded
logger.log("[CM] setDeviceName:", res)
return { deviceId: res.info }
})
}
CM.prototype.deviceGetPassword = function (deviceId, pin) {
if (!deviceId || !pin)
return failPromiseWithBadParam(deviceId ? "pin" : "deviceId", "missing deviceId or pin")
var self = this
return this.rpc(C.WALLET_DEVICE_GET_PASSWORD, {
deviceId: deviceId,
userPin: pin
}).then(function (res) {
logger.log("[CM] getDevicePassword:", res)
return res.info
})
}
CM.prototype.deviceUpdate = function (deviceId, newName) {
if (!deviceId || !newName)
return failPromiseWithBadParam("deviceId|newName", "missing deviceId or newName")
var self = this
return this.rpc(C.WALLET_DEVICE_UPDATE, {
deviceId: deviceId,
deviceName: newName
}).then(function (res) {
return res.info
})
}
CM.prototype.deviceChangePin = function (deviceId, oldPin, newPin) {
if (!deviceId || !oldPin || !newPin)
return failPromiseWithBadParam("deviceId|oldPin|newPin", "missing deviceId, newPin or oldPin")
var self = this
return this.rpc(C.WALLET_DEVICE_CHANGE_PIN, {
deviceId: deviceId,
userPin: oldPin,
newPin: newPin
}).then(function (res) {
return res.info
})
}
CM.prototype.devicePromoteToPrimary = function (deviceId, tfa) {
if (!deviceId)
return failPromiseWithBadParam("deviceId", "missing deviceId")
return this.rpc(C.WALLET_DEVICE_PROMOTE_TO_PRIMARY, {
deviceId: deviceId,
tfa: tfa
})
}
CM.prototype.deviceCancelPromotion = function (tfa) {
return this.rpc(C.WALLET_DEVICE_CANCEL_PROMOTION, { tfa })
}
CM.prototype.deviceGetRecoveryHours = function () {
return this.rpc(C.WALLET_DEVICE_GET_RECOVERY_HOURS)
}
CM.prototype.deviceSetRecoveryHours = function (hours, tfa) {
return this.rpc(C.WALLET_DEVICE_SET_RECOVERY_HOURS, {
data: hours, tfa: tfa
})
}
CM.prototype.devicesGet = function () {
return this.simpleRpcSlice(C.WALLET_DEVICES_GET)
}
CM.prototype.devicesDelete = function (param) {
var data = {}
if (param instanceof Array)
data.deviceIds = param
else
data.deviceId = param
return this.rpc(C.WALLET_DEVICES_DELETE, data)
}
CM.prototype.devicesDeleteAll = function (deviceId) {
var data = {}
if (deviceId)
data.deviceId = deviceId
return this.rpc(C.WALLET_DEVICES_DELETE_ALL, data)
}
//
// WALLET functions
//
CM.prototype.walletLogin = function (seed, params) {
throw ('todo')
}
CM.prototype.walletOpen = function (seed, params) {
const self = this
if (!params)
params = {}
return this.getLoginChallenge().then(res => {
//logger.log("[CM] walletOpen challenge: " + challengeHex + " seed: " + seed + " isProduction:"+self.isProdNet())
const hd = self.hdNodeFromHexSeed(seed)
// Keep the public key for ourselves
const loginId = self.deriveKeyFromPath(hd, self.getLoginPath())
const chainCode = Buffer.alloc(32, "42", 'hex')
const loginHD = new Bitcoin.HDNode(loginId.keyPair, chainCode)
const loginPath = [simpleRandomInt(C.MAX_SUBPATH), simpleRandomInt(C.MAX_SUBPATH)]
const loginKey = self.deriveKeyFromPath(loginHD, loginPath)
const signature = loginKey.sign(Buffer.from(res.challenge, 'hex'))
//logger.log("child: " + child.getPublicKeyBuffer().toString('hex')() + " sig: " + signature)
//logger.log("pubKey: " + masterPubKey + " r: " + signature.r.toString() + " s: " + signature.s.toString())
return self.rpc(C.WALLET_OPEN, {
id: loginId.getPublicKeyBuffer().toString('hex'),
loginPath,
signatureR: signature.r.toString(), signatureS: signature.s.toString(),
sessionName: params.sessionName,
deviceId: params.deviceId,
usePinAsTfa: params.usePinAsTfa,
supportedCoins: getSupportedCoins()
}).then(res => {
const wallet = res.wallet
logger.log("[CM] walletOpen pubKey:" + wallet.pubKey + self.isProdNet() + " #accounts: " + Object.keys(wallet.accounts).length + " isProdNet: ")
walletOpen(self, hd, wallet)
self.lastOpenParams = { seed: seed, sessionName: params.sessionName, deviceId: params.deviceId }
return wallet
})
})
}
CM.prototype.accountOpen = function (extendedKey, params) {
const self = this
if (!params)
params = {}
const accountHd = self.hdNodeFromBase58(extendedKey, params.coin)
return this.getLoginChallenge().then(res => {
const loginPath = [simpleRandomInt(C.MAX_SUBPATH), simpleRandomInt(C.MAX_SUBPATH)]
const loginKey = self.deriveKeyFromPath(accountHd, loginPath)
const challenge = Buffer.from(res.challenge, 'hex')
const signature = loginKey.sign(challenge)
//logger.log("child: " + child.getPublicKeyBuffer().toString('hex')() + " sig: " + signature)
//logger.log("pubKey: " + masterPubKey + " r: " + signature.r.toString() + " s: " + signature.s.toString())
return self.rpc(C.ACCOUNT_OPEN, {
coin: params.coin,
xpub: self.hdNodeToBase58Xpub(accountHd),
loginPath,
signatureR: signature.r.toString(), signatureS: signature.s.toString(),
sessionName: params.sessionName,
deviceId: params.deviceId,
usePinAsTfa: params.usePinAsTfa
}).then(res => {
const wallet = res.wallet
const accountData = res.accountData
logger.log("[CM] accountOpen isProdNet: " + self.isProdNet + " wallet: ", wallet)
wallet.accounts = [accountData.account]
wallet.balances = [accountData.balance]
wallet.accountInfos = [accountData.accountInfo]
walletOpen(self, accountHd, wallet, true)
self.lastOpenParams = { accountExtendedKey: extendedKey, sessionName: params.sessionName, deviceId: params.deviceId }
return res
})
})
}
CM.prototype.walletRegister = function (seed, params) {
var self = this
if (!params)
params = {}
var loginKey
var self = this
try {
//var hd = self.hdNodeFromHexSeed(self.isProdNet() ? C.COIN_PROD_BTC : C.COIN_TEST_BTC, seed)
var hd = self.hdNodeFromHexSeed(seed)
loginKey = self.deriveKeyFromPath(hd, self.getLoginPath())
//logger.log('REGISTER hd: ', hd, ' loginKey: ', loginKey)
} catch (error) {
var ex = { ex: "clientAssertFailure", msg: error.message }
logger.log(ex)
return Q.reject(ex)
}
return self.rpc(C.WALLET_REGISTER, {
xpub: self.hdNodeToBase58Xpub(loginKey),
sessionName: params.sessionName,
deviceId: params.deviceId,
usePinAsTfa: params.usePinAsTfa,
supportedCoins: getSupportedCoins()
}).then(res => {
logger.log("[CM] walletRegister: ", res)
walletOpen(self, hd, res.wallet)
self.lastOpenParams = { seed: seed, sessionName: params.sessionName, deviceId: params.deviceId }
return res.wallet
})
}
CM.prototype.walletClose = function () {
const self = this
return self.rpc(C.WALLET_CLOSE).then(function (res) {
walletClose(self)
return res
})
}
CM.prototype.accountsGet = function (pagingInfo) {
const pars = addPagingInfo({}, pagingInfo)
return this.simpleRpcSlice(C.WALLET_ACCOUNTS_GET, pars)
}
CM.prototype.walletGetNumSessions = function () {
return this.rpc(C.WALLET_GET_NUM_SESSIONS).then(function (res) {
//console.log("[CM] number of sessions with wallet open: " + JSON.stringify(res))
return res.numWalletSessions
})
}
CM.prototype.walletGetNotifications = function (fromDate, pagingInfo) {
const pars = addPagingInfo({ fromDate: fromDate }, pagingInfo)
return this.simpleRpcSlice(C.WALLET_GET_NOTIFICATIONS, pars)
}
CM.prototype.walletGetAccountsStatus = async function (pubIds, fromDate, pagingInfo) {
if (!pubIds || pubIds.length == 0)
return failPromiseWithBadParam("pubIds", "missing pubIds")
const pars = addPagingInfo({ pubIds, fromDate }, pagingInfo)
const res = await this.rpc(C.WALLET_GET_ACCOUNTS_STATUS, pars)
return res.accounts
}
CM.prototype.walletGetInfo = function () {
var self = this
return this.rpc(C.WALLET_GET_INFO).then(function (res) {
logger.log("walletGetInfo:", res)
updateWalletInfo(self, res.info)
return res.info
})
}
// Creates random account numbers
// in order to be unable to guess hidden account numbers
CM.prototype.getFreeAccountNum = function () {
return this.rpc(C.WALLET_GET_FREE_ACCOUNT_NUM).then(function (res) {
return res.accountNum // + simpleRandomInt(2)
})
}
CM.prototype.addPushTokenGoogle = function (token) {
return this.rpc(C.WALLET_PUSH_REGISTER_GOOGLE, { data: token })
}
CM.prototype.aliasGetInfo = function (account) {
return this.rpc(C.ACCOUNT_ALIAS_INFO, { pubId: account.pubId })
}
CM.prototype.aliasIsAvailable = function (alias) {
return this.rpc(C.ACCOUNT_ALIAS_AVAILABLE, { name: alias })
}
CM.prototype.aliasDefine = function (account, alias) {
return this.rpc(C.ACCOUNT_ALIAS_DEFINE, { pubId: account.pubId, name: alias })
}
CM.prototype.walletMetaSet = function (name, value) {
return this.rpc(C.WALLET_META_SET, { name: name, meta: value })
}
CM.prototype.walletMetaGet = function (param) {
if (Array.isArray(param))
return this.rpc(C.WALLET_META_GET, { names: param })
else
return this.rpc(C.WALLET_META_GET, { name: param }).then(function (res) {
return res.meta
})
}
CM.prototype.walletMetasGet = function (pagingInfo) {
var pars = addPagingInfo({}, pagingInfo)
return this.simpleRpcSlice(C.WALLET_METAS_GET, pars)
}
CM.prototype.walletMetaDelete = function (name) {
return this.rpc(C.WALLET_META_DELETE, { name: name })
}
//
// Account functions
//
/*
* Parameters:
* type
* accountNum
* meta
* hidden
* cosigners
* minSignatures
* mandatorySignature
*/
CM.prototype.accountCreate = function (params) {
if (!params || !params.type)
throwBadParamEx('params', "Bad parameters")
let numPromise
if (params.accountNum === undefined)
numPromise = this.getFreeAccountNum()
else
numPromise = Q(params.accountNum)
const self = this
return numPromise.then(accountNum => {
logger.log("[CM] accountCreate coin: " + params.coin + " accountNum: " + params.accountNum)
params.accountNum = accountNum
const accountHd = self.deriveAccountHdKey(self.hdWallet, accountNum, params.coin)
params.xpub = self.hdNodeToBase58Xpub(accountHd, params.coin)
return self.rpc(C.ACCOUNT_REGISTER, params)
}).then(res => {
updateAccount(self, res, self.hdWallet)
return res
})
}
CM.prototype.accountJoin = function (params) {
const self = this
logger.log("[CM] joinWallet params:", params)
var numPromise = Q(params.accountNum)
if (params.accountNum === undefined)
numPromise = self.getFreeAccountNum().then(num => {
params.accountNum = num
})
var coinPromise = Q(params.coin)
if (!params.coin)
coinPromise = this.joinCodeGetInfo(params.code).then(res => {
params.coin = res.info.coin
})
return numPromise.then(coinPromise).then(() => {
logger.log("[CM] joinWallet coin: " + params.coin + " accountNum: " + params.accountNum)
const accountHd = self.deriveAccountHdKey(self.hdWallet, params.accountNum, params.coin)
return self.rpc(C.ACCOUNT_JOIN, {
code: params.code,
accountNum: params.accountNum,
xpub: self.hdNodeToBase58Xpub(accountHd, params.coin),
meta: params.meta
})
}).then(res => {
updateAccount(self, res, self.hdWallet)
return res
})
}
CM.prototype.accountRefresh = function (account) {
var self = this
return this.rpc(C.ACCOUNT_REFRESH, {
pubId: account.pubId
}).then(res => {
if (res.account && res.balance && res.accountInfo)
updateAccount(self, res)
return res
})
}
CM.prototype.accountUpdate = function (account, options) {
if (!options || typeof options !== 'object')
return
logger.log("[accountUpdate] " + account.pubId + " :", options)
var self = this
return this.rpc(C.ACCOUNT_UPDATE, {
pubId: account.pubId,
hidden: options.hidden,
meta: options.meta,
tfa: options.tfa,
pubMeta: options.pubMeta
}).then(res => {
updateAccount(self, res)
return res
})
}
CM.prototype.accountDelete = function (account, tfa) {
const self = this
return this.rpc(C.ACCOUNT_DELETE, { pubId: account.pubId , tfa}).then(res => {
delete self.walletData.accounts[account.pubId]
delete self.walletData.balances[account.pubId]
delete self.walletData.infos[account.pubId]
return res
})
}
CM.prototype.joinCodeGetInfo = function (code) {
return this.rpc(C.ACCOUNT_GET_JOIN_CODE_INFO, { code: code })
}
CM.prototype.getLocktimeDays = function (account) {
return this.rpc(C.ACCOUNT_GET_LOCKTIME_DAYS, {
pubId: account.pubId
})
}
CM.prototype.setLocktimeDays = function (account, days, tfa) {
return this.rpc(C.ACCOUNT_SET_LOCKTIME_DAYS, {
pubId: account.pubId, data: days, tfa: tfa
})
}
CM.prototype.getRecoveryInfo = function (account, fromDate) {
return this.rpc(C.ACCOUNT_GET_RECOVERY_INFO, {
pubId: account.pubId,
fromDate: fromDate
})
}
CM.prototype.getUnusedAddress = function (account, address, labels, meta) {
const self = this
if (meta && Object.keys(meta).length === 0)
meta = null
if (labels && labels.length === 0)
labels = null
const promise = possiblyIncompleteAccountInfo(self.peekAccountInfo(account)) ?
self.accountRefresh(account).then(res => res.account) : Q(account)
return promise.then(account => {
return this.rpc(C.ACCOUNT_GET_UNUSED_ADDRESS, {
pubId: account.pubId,
address: address,
labels: labels,
meta: meta
})
}).then(res => {
const aa = res.address
if (!self.isAddressOfAccount(account, aa))
return failPromiseWithEx(buildInvalidAddressEx(aa.address, "Received address not matching account definition! addr:" + aa.address + " pubId: " + account.pubId))
return aa
})
}
CM.prototype.addressUpdate = function (account, address, labels, meta) {
if (meta && Object.keys(meta).length === 0)
meta = null
if (labels && labels.length === 0)
labels = null
return this.rpc(C.ACCOUNT_ADDRESS_UPDATE, {
pubId: account.pubId,
address: address,
labels: labels,
meta: meta
}).then(res => res.address)
}
CM.prototype.addressRelease = function (account, address) {
return this.rpc(C.ACCOUNT_ADDRESS_RELEASE, {
pubId: account.pubId,
address: address
}).then(res => res.address)
}
CM.prototype.addressGet = function (account, address, optionsAndPaging) {
var pars = addPagingInfo({ pubId: account.pubId, address: address }, optionsAndPaging)
if (optionsAndPaging && optionsAndPaging.includeTxInfos)
pars.includeTxInfos = optionsAndPaging.includeTxInfos
return this.rpc(C.ACCOUNT_ADDRESS_GET, pars)
}
CM.prototype.addressesGet = function (account, optionsAndPaging) {
var pars = addPagingInfo({ pubId: account.pubId }, optionsAndPaging)
if (optionsAndPaging && optionsAndPaging.onlyActives)
pars.onlyActives = optionsAndPaging.onlyActives
if (optionsAndPaging && optionsAndPaging.chain >= 0)
pars.chain = optionsAndPaging.chain
return this.simpleRpcSlice(C.ACCOUNT_ADDRESSES_GET, pars)
}
CM.prototype.addLegacyAddress = function (account, keyPair, params) {
var data = this.prepareAddressSignature(account.coin, keyPair, C.MSG_PREFIX_LEGACY_ADDR)
return this.rpc(C.WALLET_ADD_LEGACY_ADDRESS, {
pubId: account.pubId,
address: data.address,
data: data.base64Sig,
labels: params ? params.labels : null,
meta: params ? params.meta : null
})
}
CM.prototype.accountGetNotifications = function (account, fromDate, pagingInfo) {
var pars = addPagingInfo({ pubId: account.pubId, fromDate: fromDate }, pagingInfo)
return this.simpleRpcSlice(C.ACCOUNT_GET_NOTIFICATIONS, pars)
}
CM.prototype.txInfosGet = function (account, filter, pagingInfo) {
if (!filter)
filter = {}
var pars = addPagingInfo({
pubId: account.pubId,
fromDate: filter.fromDate,
txDate: filter.txDate,
direction: filter.direction
}, pagingInfo)
return this.simpleRpcSlice(C.ACCOUNT_GET_TX_INFOS, pars)
}
CM.prototype.txInfoGet = function (id) {
return this.rpc(C.ACCOUNT_GET_TX_INFO, {
data: id
}).then(function (res) {
return res.txInfo
})
}
CM.prototype.txInfoSet = function (id, labels, meta) {
return this.rpc(C.ACCOUNT_SET_TX_INFO, {
data: id, labels: labels, meta: meta
}).then(function (res) {
//console.log("[CM SET_TX_INFO] res: " + JSON.stringify(res))
return res.txInfo
})
}
CM.prototype.getAllLabels = function (account) {
return this.rpc(C.ACCOUNT_GET_ALL_LABELS, { pubId: account ? account.pubId : null })
}
CM.prototype.ptxPrepare = function (account, recipients, options) {
logger.log("[CM ptxPrepare] account: " + account.pubId + " to: " + JSON.stringify(recipients) + " opts: ", options)
options = options || {}
var params = {
pubId: account.pubId,
recipients: recipients,
unspents: options.unspents,
tfa: options.tfa,
ptxOptions: {}
}
Object.keys(options)
.filter(k => k !== 'autoSignIfValidated')
.forEach(k => params.ptxOptions[k] = options[k])
logger.log("[CM ptxPrepare] params:", params)
return this.rpc(C.ACCOUNT_PTX_PREPARE, params)
}
CM.prototype.ptxFeeBump = function (id, options) {
const ptxOptions = { feeMultiplier: options.feeMultiplier, satoshisPerByte: options.satoshisPerByte}
const params = { data: id, ptxOptions: ptxOptions}
return this.rpc(C.ACCOUNT_PTX_FEE_BUMP, params)
}
CM.prototype.ptxGetById = function (id) {
return this.rpc(C.ACCOUNT_PTX_GET, { data: id })
}
CM.prototype.ptxGetByHash = function (account, hash) {
return this.rpc(C.ACCOUNT_PTX_GET, { pubId: account.pubId, hash: hash })
}
CM.prototype.ptxCancel = function (ptx) {
return this.rpc(C.ACCOUNT_PTX_CANCEL, { data: ptx.id })
}
CM.prototype.ptxSignFields = function (account, ptx) {
const num1 = simpleRandomInt(C.MAX_SUBPATH), num2 = simpleRandomInt(C.MAX_SUBPATH)
const key = this.deriveAddressKey(account.num, num1, num2)
var sig = this.signMessageWithKP(account.coin, key.keyPair, ptx.rawTx)
return this.rpc(C.ACCOUNT_PTX_SIGN_FIELDS, {
data: ptx.id,
num1: num1,
num2: num2,
signatures: [sig]
})
}
CM.prototype.ptxHasFieldsSignature = function (ptx) {
if (!ptx.meta)
return false
var keyMessage = ptx.meta.ownerSig
return !!(keyMessage && keyMessage.keyPath && keyMessage.ptxSig)
}
// TODO: add verification of cosigners public key
CM.prototype.ptxVerifyFieldsSignature = function (account, ptx) {
const self = this
return this.ensureAccountInfo(account).then(function (account) {
i