UNPKG

scpx-wallet

Version:

Scoop Core Wallet: dual-signature timelock crypto wallet - multi-asset, cross-platform and open-source

345 lines (301 loc) 14.5 kB
// Distributed under AGPLv3 license: see /LICENSE for terms. Copyright 2019-2021 Dominic Morris. const _ = require('lodash') const { userData_SaveAll } = require('../actions/user-data') const { getUserData_FromEncryptedJson } = require('../actions/user-data-helpers') const { USERDATA_SET_FROM_SERVER, USERDATA_UPDATE_LASTLOAD, USERDATA_UPDATE_TOTP_SECRET, USERDATA_UPDATE_OPTION, USERDATA_UPDATE_FBASE, USERDATA_UPDATE_AUTOCONVERT, USERDATA_UPDATE_ADDRBOOK, } = require('../actions') const { XS_SET_EXCHANGE_ASSET, XS_SET_RECEIVE_ASSET, XS_SET_MINMAX_AMOUNT, XS_SET_EST_RECEIVE_AMOUNT, XS_SET_FIXED_RECEIVE_AMOUNT, XS_UPDATE_EXCHANGE_TX, XS_SET_CURRENCIES } = require('../actions') const utilsWallet = require('../utils') const { createReducer } = require('./utils') const initialState = { t_f3: "42-def1", t_f4: "42-def2", // address book addrBook: { // e.g. // BTC: [ // { addr, name, email } // ], ... }, // exchange autoconvert settings autoConvertSettings: { // e.g. // ZEC: { // fromBlockNo: undefined, // toSymbol: undefined, // }, ... }, // ndx 0 - current login // ndx 1 - previous login loadHistory: [ { browser: false, server: false, datetime: undefined }, { browser: false, server: false, datetime: undefined } ], // random 32 char entropy - uncorrelated with all other account identifiers totpSecret: undefined, fbaseCloudLoginSaved: { email: null, photoURL: null, }, // user app settings options: [ { key: "OPT_CLOUD_PWD", value: false }, { key: "OPT_AUTOLOGOUT", value: true }, { key: "OPT_NIGHTSHIFT", value: true }, { key: "OPT_NOPATCH_MPK", value: true }, { key: "OPT_BETA_TESTER", value: true }, { key: "OPT_TOTP", value: false }, { key: "OPT_ALL_ASSETS", value: false }, ], // exchange service - current and history records exchange: { // UI "active" - really should be state in Exchange screen cur_fromSymbol: undefined, cur_toSymbol: undefined, cur_minAmount: 0.00, cur_maxAmount: 0.00, cur_fixedRateId: undefined, cur_estReceiveAmount: 0.00, // transient - 3PXS current states currencies: [], // current XS data cur_xsTx: { // e.g. // ZEC: { txid: 'bafd224be48a65d2b87dc7bc67dbd297831fd1437fd7edad1833b20cf9070f82', // sentAt: 1581854132982, // xs: // { id: '9giaknetruwxndp4', // createdAt: 1581854078, // type: 'fixed', // moneyReceived: 1581854171, // moneySent: 1581855179, // rate: '0.00635068', // payinConfirmations: '0', // status: 'finished', // currencyFrom: 'zec', // currencyTo: 'btc', // payinAddress: 't1estHVAkNYPzvcARRj5zRWNN3FYowKev7H', // payinExtraId: null, // payinExtraIdName: null, // payinHash: // 'bafd224be48a65d2b87dc7bc67dbd297831fd1437fd7edad1833b20cf9070f82', // payoutHashLink: // 'https://www.blockchain.com/btc/tx/13332ddb2848dcdeea7330b748d3d820b5bfacf8aaa9d893fec38414fa630ea7', // refundHashLink: null, // amountExpectedFrom: '0.715', // payoutAddress: '1NGwcdq26vDRT3kErPN83Zx2AwW9UBFffN', // payoutExtraId: null, // payoutExtraIdName: null, // payoutHash: // '13332ddb2848dcdeea7330b748d3d820b5bfacf8aaa9d893fec38414fa630ea7', // refundHash: null, // amountFrom: '0.715', // amountTo: '0.00454074', // amountExpectedTo: '0.00454074', // networkFee: '0.00025', // changellyFee: '0.5', // apiExtraFee: '0.50', // totalFee: '0.00025', // fiatProviderId: null, // fiatProvider: null, // fiatProviderRedirect: null }, // fromSymbol: 'ZEC', // toSymbol: 'BTC', // amountSent: 0.715, // cur_estReceiveAmount: 0.0045407483192499995, // fixedRateId: // 'ebcc48106d63b65b898e5f0c38274ecc940d89c5fcb5a95e08d52b7b903f1775', // cur_xsTxStatus: 'done', // finalized: true }, // } //... }, // todo? -> cur_xsTx.eth -> cur_xsTx.eth[] -- i.e. current *and* history combined? // * creating new --> append only (not replace) ... // * updating --> find, update in place // * removing --> nop } } const handlers = { // assign state from server [USERDATA_SET_FROM_SERVER]: (state, action) => { var dataJson = action.dataJson if (dataJson !== undefined && dataJson !== "" && dataJson.length > 0) { var serverUserData = getUserData_FromEncryptedJson(dataJson) if (!serverUserData) { // sanity check -- have seen corrupted settings saved to server during dev cycles; ignore if so return state } // don't nuke local options from server, instead merge var mergedOptions = {...state.options, ...serverUserData.options} // server wins on conflict (right hand side of spread operator) var mergedOptionsArray = Array.from(Object.values(mergedOptions)) // also merge top level fields of settings, so we can add new fields anytime on client and they get preserved on server var newUserData = {...state, ...serverUserData} // server wins on conflict newUserData.options = mergedOptionsArray //userData_SaveAll({ userData: newUserData, hideToast: action.hideToast || false }) return newUserData } }, // set TOTP secret key [USERDATA_UPDATE_TOTP_SECRET]: (state, action) => { if (action.payload.owner === utilsWallet.getStorageContext().owner) { var newState = _.cloneDeep(state) newState.totpSecret = action.payload.newValue //userData_SaveAll({ userData: newState, hideToast: false }) return newState } }, // set load history [USERDATA_UPDATE_LASTLOAD]: (state, action) => { if (action.payload.owner === utilsWallet.getStorageContext().owner) { var newState = _.cloneDeep(state) newState.loadHistory[1] = newState.loadHistory[0] // assign previous login (old current) newState.loadHistory[0] = action.payload.newValue // assign current login //userData_SaveAll({ userData: newState, hideToast: true }) return newState } }, // user settings - options [USERDATA_UPDATE_OPTION]: (state, action) => { var ndx = state.options.findIndex((p) => p.key === action.key) if (action.payload.owner === utilsWallet.getStorageContext().owner) { // disregard actions that originate from a different logged on user (this action is propagated by redux-state-sync) var newState = _.cloneDeep(state) newState.options[ndx].value = action.payload.newValue if (action.payload.save) { userData_SaveAll({ userData: newState, hideToast: false }) } return newState } }, // user data - address book [USERDATA_UPDATE_ADDRBOOK]: (state, action) => { if (action.payload.owner === utilsWallet.getStorageContext().owner) { var newState = _.cloneDeep(state) // validate if (!action.payload.symbol || !action.payload.addr || !action.payload.name) { console.error('USERDATA_UPDATE_ADDRBOOK - invalid params') return newState } // update or insert if (newState.addrBook[action.payload.symbol] === undefined) { newState.addrBook[action.payload.symbol] = [] } const book = newState.addrBook[action.payload.symbol] var entry = book.find(p => p.addr === action.payload.addr) // keyed by addr if (!entry) { entry = {} book.push(entry) } const updated = (entry.addr != action.payload.addr || entry.name != action.payload.name || entry.email != action.payload.email) entry.addr = action.payload.addr entry.name = action.payload.name entry.email = action.payload.email if (updated) { utilsWallet.logMajor('orange','black', `USERDATA_UPDATE_ADDRBOOK`, newState, { logServerConsole: true }) userData_SaveAll({ userData: newState, hideToast: true }) } return newState } }, // user data - autoconvert [USERDATA_UPDATE_AUTOCONVERT]: (state, action) => { if (action.payload.owner === utilsWallet.getStorageContext().owner) { var newState = _.cloneDeep(state) // validate if (!action.payload.fromSymbol || !action.payload.fromSyncInfo) { console.error('USERDATA_UPDATE_AUTOCONVERT - invalid params') return newState } if (action.payload.fromSyncInfo.receivedBlockNo <= 0) { console.error('USERDATA_UPDATE_AUTOCONVERT - invalid params (receivedBlockNo)', action.payload.fromSyncInfo) return newState } // update if (newState.autoConvertSettings[action.payload.fromSymbol] === undefined) { newState.autoConvertSettings[action.payload.fromSymbol] = {} } newState.autoConvertSettings[action.payload.fromSymbol].toSymbol = action.payload.toSymbol newState.autoConvertSettings[action.payload.fromSymbol].fromBlockNo = action.payload.toSymbol !== undefined ? action.payload.fromSyncInfo.receivedBlockNo : undefined utilsWallet.logMajor('orange','black', `USERDATA_UPDATE_AUTOCONVERT`, newState, { logServerConsole: true }) userData_SaveAll({ userData: newState, hideToast: false }) return newState } }, // fbase logged-in status [USERDATA_UPDATE_FBASE]: (state, action) => { var newState = _.cloneDeep(state) newState.fbaseCloudLoginSaved = { email: action.payload.email, photoURL: action.payload.photoURL } userData_SaveAll({ userData: newState, hideToast: false }) return newState }, // // exchange service (XS) // [XS_SET_EXCHANGE_ASSET]: (state, action) => { utilsWallet.logMajor('orange','black', `XS_SET_EXCHANGE_ASSET`, action.payload, { logServerConsole: true }) return {...state, exchange: {...state.exchange, cur_fromSymbol: action.payload } } }, [XS_SET_RECEIVE_ASSET]: (state, action) => { utilsWallet.logMajor('orange','black', `XS_SET_RECEIVE_ASSET`, action.payload, { logServerConsole: true }) return {...state, exchange: {...state.exchange, cur_toSymbol: action.payload } } }, [XS_SET_MINMAX_AMOUNT]: (state, action) => { utilsWallet.logMajor('orange','black', `XS_SET_MINMAX_AMOUNT`, action.payload, { logServerConsole: true }) return {...state, exchange: {...state.exchange, cur_minAmount: action.payload.min, cur_maxAmount: action.payload.max, cur_minAmountErr: undefined } } }, [XS_SET_EST_RECEIVE_AMOUNT]: (state, action) => { utilsWallet.logMajor('orange','black', `XS_SET_EST_RECEIVE_AMOUNT`, action.payload, { logServerConsole: true }) return {...state, exchange: {...state.exchange, cur_estReceiveAmount: action.payload.result, cur_fixedRateId: undefined } } }, [XS_SET_FIXED_RECEIVE_AMOUNT]: (state, action) => { utilsWallet.logMajor('orange','black', `XS_SET_FIXED_RECEIVE_AMOUNT`, action.payload, { logServerConsole: true }) return {...state, exchange: {...state.exchange, cur_estReceiveAmount: action.payload.derivedExpected, cur_fixedRateId: action.payload.rateId }} }, [XS_UPDATE_EXCHANGE_TX]: (state, action) => { if (action.payload.owner === utilsWallet.getStorageContext().owner) { // redux-state-sync const asset = Object.keys(action.payload.data)[0] var newUserData = _.cloneDeep(state) // skip update and DSC save unless actually changed - avoids intermittent DSC "duplicate transaction" exceptions if (_.isEqual(state.exchange.cur_xsTx[asset], action.payload.data[asset]) == false) { utilsWallet.logMajor('orange','black', `XS_UPDATE_EXCHANGE_TX`, action.payload, { logServerConsole: true }) newUserData.exchange.cur_xsTx[asset] = {...newUserData.exchange.cur_xsTx[asset], ...action.payload.data[asset] } userData_SaveAll({ userData: newUserData, hideToast: true }) } return newUserData } }, [XS_SET_CURRENCIES]: (state, action) => { utilsWallet.logMajor('orange','black', `XS_SET_CURRENCIES`, action.payload.length, { logServerConsole: true }) return {...state, exchange: {...state.exchange, currencies: action.payload } } }, } //export default module.exports = { userData: createReducer(initialState, handlers), initialState }