From 11eb0af732b00aca73830fc562004048fa266a84 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Sat, 20 Jan 2018 20:06:26 +0300 Subject: [PATCH 1/8] offline signing poc --- react/src/actions/actionCreators.js | 1 + react/src/actions/actions/tools.js | 169 ++++++++++ .../dashboard/main/dashboard.render.js | 4 + .../dashboard/navbar/navbar.render.js | 8 + react/src/components/dashboard/tools/tools.js | 300 ++++++++++++++++++ .../src/components/dashboard/tools/tools.scss | 32 ++ react/src/styles/index.scss | 1 + 7 files changed, 515 insertions(+) create mode 100644 react/src/actions/actions/tools.js create mode 100644 react/src/components/dashboard/tools/tools.js create mode 100644 react/src/components/dashboard/tools/tools.scss diff --git a/react/src/actions/actionCreators.js b/react/src/actions/actionCreators.js index 16bdeff..506f0fa 100644 --- a/react/src/actions/actionCreators.js +++ b/react/src/actions/actionCreators.js @@ -50,6 +50,7 @@ export * from './actions/getTxDetails'; export * from './actions/electrum'; export * from './actions/mm'; export * from './actions/nativeNetwork'; +export * from './actions/tools'; export function changeActiveAddress(address) { return { diff --git a/react/src/actions/actions/tools.js b/react/src/actions/actions/tools.js new file mode 100644 index 0000000..ad94988 --- /dev/null +++ b/react/src/actions/actions/tools.js @@ -0,0 +1,169 @@ +import { translate } from '../../translate/translate'; +import Config from '../../config'; +import { + triggerToaster, +} from '../actionCreators'; +import Store from '../../store'; + +export function shepherdToolsSeedKeys(seed) { + return new Promise((resolve, reject) => { + fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/keys`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + seed, + active: true, + iguana: true, + token: Config.token, + }), + }) + .catch((error) => { + console.log(error); + Store.dispatch( + triggerToaster( + 'shepherdToolsSeedKeys', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(!json.result ? 'error' : json); + }); + }); +} + +export function shepherdToolsBalance(coin, address) { + return new Promise((resolve, reject) => { + fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/getbalance?coin=${coin}&address=${address}&token=${Config.token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .catch((error) => { + console.log(error); + dispatch( + triggerToaster( + 'shepherdToolsBalance', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(!json.result ? 'error' : json); + }); + }); +} + +export function shepherdToolsTransactions(coin, address) { + return new Promise((resolve, reject) => { + fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/listtransactions?coin=${coin}&address=${address}&full=true&maxlength=20&token=${Config.token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .catch((error) => { + console.log(error); + dispatch( + triggerToaster( + 'shepherdToolsTransactions', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(!json.result ? 'error' : json); + }); + }); +} + +export function shepherdToolsBuildUnsigned(coin, value, sendToAddress, changeAddress) { + value = Math.floor(value); + + return new Promise((resolve, reject) => { + return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&verify=false&push=false&offline=true&token=${Config.token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .catch((error) => { + console.log(error); + Store.dispatch( + triggerToaster( + 'shepherdToolsBuildUnsigned', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json); + }); + }); +} + +export function shepherdToolsListunspent(coin, address) { + return new Promise((resolve, reject) => { + fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/listunspent?coin=${coin}&address=${address}&full=true&token=${Config.token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .catch((error) => { + console.log(error); + Store.dispatch( + triggerToaster( + 'shepherdToolsListunspent', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(!json.result ? 'error' : json); + }); + }); +} + +export function shepherdToolsPushTx(network, rawtx) { + return new Promise((resolve, reject) => { + fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/pushtx`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + network, + rawtx, + token: Config.token, + }), + }) + .catch((error) => { + console.log(error); + Store.dispatch( + triggerToaster( + 'shepherdToolsPushTx', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(!json.result ? 'error' : json); + }); + }); +} \ No newline at end of file diff --git a/react/src/components/dashboard/main/dashboard.render.js b/react/src/components/dashboard/main/dashboard.render.js index 26849a6..71aa61b 100644 --- a/react/src/components/dashboard/main/dashboard.render.js +++ b/react/src/components/dashboard/main/dashboard.render.js @@ -13,6 +13,7 @@ import Settings from '../settings/settings'; import ReceiveCoin from '../receiveCoin/receiveCoin'; import About from '../about/about'; import Support from '../support/support'; +import Tools from '../tools/tools'; import WalletsMain from '../walletsMain/walletsMain'; import WalletsTxInfo from '../walletsTxInfo/walletsTxInfo'; import CoindDownModal from '../coindDownModal/coindDownModal'; @@ -56,6 +57,9 @@ const DashboardRender = function() { { this.isSectionActive('support') && } + { this.isSectionActive('tools') && + + } ); diff --git a/react/src/components/dashboard/navbar/navbar.render.js b/react/src/components/dashboard/navbar/navbar.render.js index 443144a..07d2ac9 100644 --- a/react/src/components/dashboard/navbar/navbar.render.js +++ b/react/src/components/dashboard/navbar/navbar.render.js @@ -2,6 +2,7 @@ import React from 'react'; import { translate } from '../../../translate/translate'; import mainWindow from '../../../util/mainWindow'; import ReactTooltip from 'react-tooltip'; +import Config from '../../../config'; const NavbarRender = function() { return ( @@ -81,6 +82,13 @@ const NavbarRender = function() { Explorer */ } + { Config.experimentalFeatures && +
  • + this.dashboardChangeSection('tools') }> + Tools + +
  • + } { !navigator.onLine &&
  • { + this.setState({ + txPushResult: res.result, + }); + }); + } + + getBalance() { + const _coin = this.state.selectedCoin.split('|'); + + shepherdToolsBalance(_coin[0], this.state.sendFrom) + .then((res) => { + if (res.msg === 'success') { + this.setState({ + balance: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Offline tx signing', + 'error' + ) + ); + } + }); + } + + getUnsignedTx() { + const _coin = this.state.selectedCoin.split('|'); + + shepherdToolsBuildUnsigned(_coin[0], this.state.amount * 100000000, this.state.sendTo, this.state.sendFrom) + .then((res) => { + console.warn(res); + + if (res.msg === 'success') { + let tx2qr = 'agtx:'; + res = res.result; + + tx2qr += (res.network === 'komodo' ? 'KMD' : res.network) + ':' + res.outputAddress + ':' + res.changeAddress + ':' + res.value + ':' + res.change + ':u:'; + + for (let i = 0; i < res.utxoSet.length; i++) { + tx2qr += res.utxoSet[i].txid + ':' + res.utxoSet[i].value + ':' + res.utxoSet[i].vout + (i === res.utxoSet.length -1 ? '' : '-'); + } + + console.warn(tx2qr); + console.warn('txqr length', tx2qr.length); + + // max 350 chars + + this.setState({ + tx2qr, + utxo: res.utxoSet, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Offline tx signing', + 'error' + ) + ); + } + }); + } + + setRecieverFromScan(receiver) { + this.setState({ + rawTx2Push: receiver, + }); + } + + closeQr() { + this.setState({ + tx2qr: null, + }); + } + + renderCoinOption(option) { + return ( +
    + { + { option.label } +
    + ); + } + + updateSelectedCoin(e, index) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + selectedCoin: e.value, + balance: null, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
    +
    +
    +
    +

    Tools

    +

    Offline Transaction Signing

    +
    + + +
    +
    + + { this.state.balance && + + } +
    +
    + + +
    +
    + + +
    +
    + +
    + { this.state.tx2qr && +
    + + +
    + } + { this.state.tx2qr && +
    + +
    + } +
    +
    + { this.state.tx2qr && +
    +
    +
    + +
    + +
    +
    + } +
    +
    +
    +

    Push QR transaction

    +
    +
    + +
    + { this.state.rawTx2Push && +
    + +
    + } + { this.state.txPushResult && +
    + { this.state.txPushResult } +
    + } +
    +
    +
    + ); + } +} + +export default Tools; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/tools.scss b/react/src/components/dashboard/tools/tools.scss new file mode 100644 index 0000000..3926f75 --- /dev/null +++ b/react/src/components/dashboard/tools/tools.scss @@ -0,0 +1,32 @@ +.tools { + padding: 0 30px 0 30px; + + &.background--white { + background: #fff; + height: 100vh; + } + + .form-control.col-sm-3 { + width: 33.3333%; + } + + label { + position: relative; + top: 7px; + } + + .offlinesig-qr { + position: fixed; + top: 50px; + left: 0; + background: #fff; + width: 100%; + height: 100%; + z-index: 100; + + .btn { + position: fixed; + bottom: 28px; + } + } +} \ No newline at end of file diff --git a/react/src/styles/index.scss b/react/src/styles/index.scss index 297baf6..5d942cd 100644 --- a/react/src/styles/index.scss +++ b/react/src/styles/index.scss @@ -52,6 +52,7 @@ @import '../components/dashboard/loginSettingsModal/loginSettingsModal.scss'; @import '../components/dashboard/zcparamsFetchModal/zcparamsFetchModal.scss'; @import '../components/dashboard/spinner/spinner.scss'; +@import '../components/dashboard/tools/tools.scss'; @import '../components/toaster/toaster.scss'; @import '~react-table/react-table.css'; @import '~react-select/dist/react-select.css'; From 23a64e771cb545f794add70fce68fc53ae5d01d1 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Sat, 20 Jan 2018 22:49:50 +0300 Subject: [PATCH 2/8] offline signing fix --- react/src/components/dashboard/tools/tools.js | 50 ++++++++++++------- .../src/components/dashboard/tools/tools.scss | 3 +- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/react/src/components/dashboard/tools/tools.js b/react/src/components/dashboard/tools/tools.js index e58b547..202f20e 100644 --- a/react/src/components/dashboard/tools/tools.js +++ b/react/src/components/dashboard/tools/tools.js @@ -31,19 +31,22 @@ class Tools extends React.Component { this.updateSelectedCoin = this.updateSelectedCoin.bind(this); this.getBalance = this.getBalance.bind(this); this.getUnsignedTx = this.getUnsignedTx.bind(this); - this.setRecieverFromScan = this.setRecieverFromScan.bind(this); + this.sendTx = this.sendTx.bind(this); this.closeQr = this.closeQr.bind(this); } - sendTx() { - let _txData = this.state.rawTx2Push.split(':'); + sendTx(rawTx2Push) { + let _txData = rawTx2Push.split(':'); console.warn(_txData); shepherdToolsPushTx(_txData[0], _txData[1]) .then((res) => { + console.warn(res); + this.setState({ txPushResult: res.result, + rawTx2Push, }); }); } @@ -107,12 +110,6 @@ class Tools extends React.Component { }); } - setRecieverFromScan(receiver) { - this.setState({ - rawTx2Push: receiver, - }); - } - closeQr() { this.setState({ tx2qr: null, @@ -188,7 +185,7 @@ class Tools extends React.Component {
    @@ -238,7 +235,11 @@ class Tools extends React.Component { { this.state.tx2qr &&
    - +
    } { this.state.tx2qr && @@ -254,18 +255,19 @@ class Tools extends React.Component {
    { this.state.tx2qr && + this.state.utxo.length < 4 &&
    -
    -
    +
    +
    + size={ 560 } />
    @@ -278,16 +280,28 @@ class Tools extends React.Component {
    + setRecieverFromScan={ this.sendTx } />
    { this.state.rawTx2Push &&
    - +
    } { this.state.txPushResult &&
    - { this.state.txPushResult } + { this.state.txPushResult.length === 64 && +
    +
    { this.state.rawTx2Push.split(':')[0].toUpperCase() } transaction pushed!
    +
    TxID { this.state.txPushResult }
    +
    + } + { this.state.txPushResult.length !== 64 && +
    Error: { this.state.txPushResult }
    + }
    }
    diff --git a/react/src/components/dashboard/tools/tools.scss b/react/src/components/dashboard/tools/tools.scss index 3926f75..fb3fa58 100644 --- a/react/src/components/dashboard/tools/tools.scss +++ b/react/src/components/dashboard/tools/tools.scss @@ -17,7 +17,7 @@ .offlinesig-qr { position: fixed; - top: 50px; + top: 68px; left: 0; background: #fff; width: 100%; @@ -27,6 +27,7 @@ .btn { position: fixed; bottom: 28px; + right: 10px; } } } \ No newline at end of file From 565db5b828943c8e1ac92cc7663e8e63d131340d Mon Sep 17 00:00:00 2001 From: pbca26 Date: Mon, 22 Jan 2018 13:58:09 +0300 Subject: [PATCH 3/8] tools string to qr --- react/src/components/dashboard/tools/tools.js | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/react/src/components/dashboard/tools/tools.js b/react/src/components/dashboard/tools/tools.js index 202f20e..84ed680 100644 --- a/react/src/components/dashboard/tools/tools.js +++ b/react/src/components/dashboard/tools/tools.js @@ -26,6 +26,7 @@ class Tools extends React.Component { utxo: null, rawTx2Push: null, txPushResult: null, + string2qr: null, }; this.updateInput = this.updateInput.bind(this); this.updateSelectedCoin = this.updateSelectedCoin.bind(this); @@ -246,10 +247,10 @@ class Tools extends React.Component {
    + { this.state.utxo.length > 3 && +
    cant encode a qr tx larger than 3 utxos!
    + }
    }
    @@ -305,6 +306,29 @@ class Tools extends React.Component { } +
    +
    +
    +

    String to QR

    +
    +
    + +
    + { this.state.string2qr && +
    + +
    + } +
    ); From 9797db361e06107a2ce7bfe66791eac072da5ef5 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Tue, 23 Jan 2018 13:38:42 +0300 Subject: [PATCH 4/8] no valid utxo fix --- react/src/components/dashboard/sendCoin/sendCoin.render.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react/src/components/dashboard/sendCoin/sendCoin.render.js b/react/src/components/dashboard/sendCoin/sendCoin.render.js index 010e98e..84e5d40 100644 --- a/react/src/components/dashboard/sendCoin/sendCoin.render.js +++ b/react/src/components/dashboard/sendCoin/sendCoin.render.js @@ -353,7 +353,9 @@ export const SendRender = function() { this.state.lastSendToResponse.raw.txid &&
    { this.state.lastSendToResponse.raw.txid.replace(/\[.*\]/, '') }
    } - { this.state.lastSendToResponse.raw.txid.indexOf('bad-txns-inputs-spent') > -1 && + { this.state.lastSendToResponse.raw && + this.state.lastSendToResponse.raw.txid && + this.state.lastSendToResponse.raw.txid.indexOf('bad-txns-inputs-spent') > -1 &&
    { translate('SEND.BAD_TXN_SPENT_ERR1') }