From 11eb0af732b00aca73830fc562004048fa266a84 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Sat, 20 Jan 2018 20:06:26 +0300 Subject: [PATCH] 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';