diff --git a/build.sbt b/build.sbt
index 526db6be7b..43026541c1 100644
--- a/build.sbt
+++ b/build.sbt
@@ -24,7 +24,7 @@ lazy val commonSettings = Seq(
publishTo := sonatypePublishToBundle.value,
)
-val scorexVersion = "master-07d30caa-SNAPSHOT"
+val scorexVersion = "master-bb48da3a-SNAPSHOT"
val sigmaStateVersion = "3.2.1"
// for testing current sigmastate build (see sigmastate-ergo-it jenkins job)
@@ -42,8 +42,7 @@ libraryDependencies ++= Seq(
"org.iq80.leveldb" % "leveldb" % "0.12",
("org.scorexfoundation" %% "scorex-core" % scorexVersion).exclude("ch.qos.logback", "logback-classic"),
-
- "org.typelevel" %% "cats-free" % "1.6.0",
+
"javax.xml.bind" % "jaxb-api" % "2.4.0-b180830.0359",
"com.iheart" %% "ficus" % "1.4.7",
"ch.qos.logback" % "logback-classic" % "1.2.3",
diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala
index b0e74a0b17..e6b3f91096 100644
--- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala
+++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/boxes/TrackedBox.scala
@@ -20,14 +20,13 @@ import org.ergoplatform.ErgoBoxAssets
* @param box - Underlying Ergo box
* @param scans - Identifiers of scans the box refers to
*/
-final case class TrackedBox(creationTxId: ModifierId,
- creationOutIndex: Short,
- inclusionHeightOpt: Option[Int],
- spendingTxIdOpt: Option[ModifierId],
- spendingHeightOpt: Option[Int],
- box: ErgoBox,
- scans: Set[ScanId]) extends ErgoBoxAssets {
-
+case class TrackedBox(creationTxId: ModifierId,
+ creationOutIndex: Short,
+ inclusionHeightOpt: Option[Int],
+ spendingTxIdOpt: Option[ModifierId],
+ spendingHeightOpt: Option[Int],
+ box: ErgoBox,
+ scans: Set[ScanId]) extends ErgoBoxAssets {
/**
* Whether the box is spent or not
@@ -86,6 +85,17 @@ object TrackedBox {
box: ErgoBox, appStatuses: Set[ScanId]): TrackedBox =
apply(creationTx.id, creationOutIndex, creationHeight, None, None, box, appStatuses)
+ /**
+ * Creates unspent box with given inclusion height and scans the box is associated with
+ * @param box
+ * @param inclusionHeight
+ * @param scans
+ * @return
+ */
+ def apply(box: ErgoBox, inclusionHeight: Int, scans: Set[ScanId]): TrackedBox = {
+ new TrackedBox(box.transactionId, box.index, Some(inclusionHeight), None, None, box, scans)
+ }
+
}
object TrackedBoxSerializer extends ErgoWalletSerializer[TrackedBox] {
diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml
index 8039955fc6..084dacd7d1 100644
--- a/src/main/resources/api/openapi.yaml
+++ b/src/main/resources/api/openapi.yaml
@@ -850,6 +850,20 @@ components:
boxId:
$ref: '#/components/schemas/TransactionBoxId'
+ ScanIdsBox:
+ description: Ergo box with associated scans (their respective identifiers)
+ type: object
+ required:
+ - scanIds
+ - box
+ properties:
+ scanIds:
+ type: array
+ items:
+ type: integer
+ box:
+ $ref: '#/components/schemas/ErgoTransactionOutput'
+
PaymentRequest:
description: Request for generation of payment transaction to a given address
type: object
@@ -902,11 +916,6 @@ components:
type: integer
format: int32
example: 8
- fee:
- description: Optional, default transaction fee from settings will be used if not defined
- type: integer
- format: int64
- example: 1000000
registers:
description: Optional, possible values for registers R7...R9
$ref: '#/components/schemas/Registers'
@@ -2516,6 +2525,24 @@ paths:
schema:
$ref: '#/components/schemas/ApiError'
+ /wallet/rescan:
+ get:
+ security:
+ - ApiKeyAuth: [api_key]
+ summary: Rescan wallet (all the available full blocks)
+ operationId: walletRescan
+ tags:
+ - wallet
+ responses:
+ '200':
+ description: Wallet storage recreated
+ default:
+ description: Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ApiError'
+
/wallet/status:
get:
security:
@@ -3579,6 +3606,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ScanIdBoxId'
+ default:
+ description: Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ApiError'
+
+ /scan/addBox:
+ post:
+ security:
+ - ApiKeyAuth: [api_key]
+ summary: Adds a box to scans, writes box to database if it is not there. You can use scan number 10 to add a box to the wallet.
+ operationId: addBox
+ tags:
+ - scan
+ - wallet
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ScanIdsBox'
+ responses:
+ '200':
+ description: It the box is added successfully, then its id is returned
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/TransactionId'
default:
description: Error
content:
diff --git a/src/main/resources/devnet.conf b/src/main/resources/devnet.conf
index 3d05d7cb4b..32f863b1a4 100644
--- a/src/main/resources/devnet.conf
+++ b/src/main/resources/devnet.conf
@@ -10,10 +10,6 @@ ergo {
initialDifficultyHex = "0001d4c0"
}
wallet.secretStorage.secretDir = ${ergo.directory}"/wallet/keystore"
- bootstrap {
- resourceUri = "http://188.166.109.25:5000/bootSettings"
- pollDelay = 10s
- }
}
scorex {
network {
diff --git a/src/main/resources/panel/asset-manifest.json b/src/main/resources/panel/asset-manifest.json
index ea2531a76a..f44856b100 100644
--- a/src/main/resources/panel/asset-manifest.json
+++ b/src/main/resources/panel/asset-manifest.json
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "/static/css/main.f6c8bfb4.chunk.css",
- "main.js": "/static/js/main.5a370586.chunk.js",
- "main.js.map": "/static/js/main.5a370586.chunk.js.map",
+ "main.js": "/static/js/main.d2ee9699.chunk.js",
+ "main.js.map": "/static/js/main.d2ee9699.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.8fa4c17a.js",
"runtime-main.js.map": "/static/js/runtime-main.8fa4c17a.js.map",
"static/css/2.a94a8b68.chunk.css": "/static/css/2.a94a8b68.chunk.css",
"static/js/2.637d7a90.chunk.js": "/static/js/2.637d7a90.chunk.js",
"static/js/2.637d7a90.chunk.js.map": "/static/js/2.637d7a90.chunk.js.map",
"index.html": "/index.html",
- "precache-manifest.d1d7c8d8559118114220728682d484c9.js": "/precache-manifest.d1d7c8d8559118114220728682d484c9.js",
+ "precache-manifest.c85cf283695a25241e587d74bf1b1e14.js": "/precache-manifest.c85cf283695a25241e587d74bf1b1e14.js",
"service-worker.js": "/service-worker.js",
"static/css/2.a94a8b68.chunk.css.map": "/static/css/2.a94a8b68.chunk.css.map",
"static/css/main.f6c8bfb4.chunk.css.map": "/static/css/main.f6c8bfb4.chunk.css.map",
@@ -21,6 +21,6 @@
"static/css/2.a94a8b68.chunk.css",
"static/js/2.637d7a90.chunk.js",
"static/css/main.f6c8bfb4.chunk.css",
- "static/js/main.5a370586.chunk.js"
+ "static/js/main.d2ee9699.chunk.js"
]
}
\ No newline at end of file
diff --git a/src/main/resources/panel/index.html b/src/main/resources/panel/index.html
index 9375a5aff0..519878f5ca 100644
--- a/src/main/resources/panel/index.html
+++ b/src/main/resources/panel/index.html
@@ -1 +1 @@
-
Ergo node interface You need to enable JavaScript to run this app.
\ No newline at end of file
+Ergo node interface You need to enable JavaScript to run this app.
\ No newline at end of file
diff --git a/src/main/resources/panel/precache-manifest.d1d7c8d8559118114220728682d484c9.js b/src/main/resources/panel/precache-manifest.c85cf283695a25241e587d74bf1b1e14.js
similarity index 90%
rename from src/main/resources/panel/precache-manifest.d1d7c8d8559118114220728682d484c9.js
rename to src/main/resources/panel/precache-manifest.c85cf283695a25241e587d74bf1b1e14.js
index 88bb5b675a..a2fcea6549 100644
--- a/src/main/resources/panel/precache-manifest.d1d7c8d8559118114220728682d484c9.js
+++ b/src/main/resources/panel/precache-manifest.c85cf283695a25241e587d74bf1b1e14.js
@@ -1,6 +1,6 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
- "revision": "fa20f17e2c568ac2ce5ac36c4e17de30",
+ "revision": "886d9d7a31acaa442ac06d534acf79c5",
"url": "/index.html"
},
{
@@ -8,7 +8,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/css/2.a94a8b68.chunk.css"
},
{
- "revision": "cd542706258c59b4adcb",
+ "revision": "dddfb9512708699add4a",
"url": "/static/css/main.f6c8bfb4.chunk.css"
},
{
@@ -16,8 +16,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/js/2.637d7a90.chunk.js"
},
{
- "revision": "cd542706258c59b4adcb",
- "url": "/static/js/main.5a370586.chunk.js"
+ "revision": "dddfb9512708699add4a",
+ "url": "/static/js/main.d2ee9699.chunk.js"
},
{
"revision": "c51552afbc3cf987b481",
diff --git a/src/main/resources/panel/service-worker.js b/src/main/resources/panel/service-worker.js
index da2dce3410..7c73e66e64 100644
--- a/src/main/resources/panel/service-worker.js
+++ b/src/main/resources/panel/service-worker.js
@@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
- "/precache-manifest.d1d7c8d8559118114220728682d484c9.js"
+ "/precache-manifest.c85cf283695a25241e587d74bf1b1e14.js"
);
self.addEventListener('message', (event) => {
diff --git a/src/main/resources/panel/static/js/main.5a370586.chunk.js b/src/main/resources/panel/static/js/main.d2ee9699.chunk.js
similarity index 76%
rename from src/main/resources/panel/static/js/main.5a370586.chunk.js
rename to src/main/resources/panel/static/js/main.d2ee9699.chunk.js
index ee78ef38e4..f5cb119960 100644
--- a/src/main/resources/panel/static/js/main.5a370586.chunk.js
+++ b/src/main/resources/panel/static/js/main.d2ee9699.chunk.js
@@ -1,2 +1,2 @@
-(this["webpackJsonpergo-node-interface"]=this["webpackJsonpergo-node-interface"]||[]).push([[0],{108:function(e,t,a){e.exports=a.p+"static/media/logotype_white.4dcfd639.svg"},111:function(e,t,a){e.exports=a(164)},122:function(e,t,a){},139:function(e,t,a){},159:function(e,t,a){},160:function(e,t,a){},162:function(e,t,a){},164:function(e,t,a){"use strict";a.r(t);var n=a(0),r=a.n(n),l=a(15),c=a.n(l),s=a(49),o=a(18),i=a(42),u=a(39),m=a(7),d=a(8),p=a(10),b=a(9),f=a(11),h=a(19),E=a(22),y=a(52),w=a(97),v={swaggerInterface:"/swagger",website:"https://ergoplatform.org",explorer:"https://explorer.ergoplatform.com",nanoErgInErg:1e9},g={dashboard:{href:"/",icon:r.a.createElement(h.a,{icon:E.b}),title:"Dashboard"},wallet:{href:"/wallet",icon:r.a.createElement(h.a,{icon:E.e}),title:"Wallet"}},O={swaggerInterface:{href:v.swaggerInterface,icon:r.a.createElement(h.a,{icon:E.a}),title:"Swagger"},explorer:{href:v.explorer,icon:r.a.createElement(h.a,{icon:w.a}),title:"Explorer"},website:{href:v.website,icon:r.a.createElement(h.a,{icon:E.g}),title:"Website"}},j=Object(u.f)((function(e){var t=e.location.pathname;return r.a.createElement("div",null,r.a.createElement("p",{className:"h5 pl-3 pt-4"},"Menu"),r.a.createElement("hr",{className:"mb-0"}),r.a.createElement("div",{className:"list-group list-group-flush"},Object.values(g).map((function(e,a){var n=e.href,l=e.icon,c=e.title;return r.a.createElement(i.b,{key:c,className:Object(y.a)("list-group-item list-group-item-action",{"list-group-item-dark":n===t,active:n===t,"border-top-0":0===a}),to:n},l," ",c)}))),r.a.createElement("p",{className:"h5 pl-3 pt-4"},"External links"),r.a.createElement("hr",{className:"mb-0"}),r.a.createElement("div",{className:"list-group list-group-flush"},Object.values(O).map((function(e,t){var a=e.href,n=e.icon,l=e.title;return r.a.createElement("a",{key:l,className:Object(y.a)("list-group-item list-group-item-action",{"border-top-0":0===t}),href:a,rel:"noopener noreferrer",target:"_blank"},n," ",l)}))))})),N=(a(122),a(76)),S=Object(N.a)((function(e){return e.app}),(function(e){return e.apiKey})),k=function(e){return e.wallet},W=Object(N.a)(k,(function(e){return e.isWalletUnlocked})),P=Object(N.a)(k,(function(e){return e.isWalletInitialized})),I=a(36),C=a(28),_=Object(C.c)({name:"walletSlice",initialState:{isWalletUnlocked:null,isWalletInitialized:null},reducers:{setIsWalletUnlocked:function(e,t){var a=t.payload;e.isWalletUnlocked=a},setIsWalletInitialized:function(e,t){var a=t.payload;e.isWalletInitialized=a}}});function A(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}var K=Object(C.b)("checkWalletStatus"),x=function(e){for(var t=1;t,\n title: 'Dashboard',\n },\n wallet: {\n href: '/wallet',\n icon: ,\n title: 'Wallet',\n },\n}\n\nconst externalRouteList = {\n swaggerInterface: {\n href: constants.swaggerInterface,\n icon: ,\n title: 'Swagger',\n },\n explorer: {\n href: constants.explorer,\n icon: ,\n title: 'Explorer',\n },\n website: {\n href: constants.website,\n icon: ,\n title: 'Website',\n },\n}\n\nconst MenuList = ({ location: { pathname } }) => {\n return (\n \n
Menu
\n
\n
\n {Object.values(localRouteList).map(({ href, icon, title }, index) => (\n \n {icon} {title}\n \n ))}\n
\n
External links
\n
\n
\n {Object.values(externalRouteList).map(\n ({ href, icon, title }, index) => (\n
\n {icon} {title}\n \n ),\n )}\n
\n
\n )\n}\n\nexport default withRouter(MenuList)\n","import { createSelector } from 'redux-starter-kit'\n\nexport const appSelector = state => state.app\n\nexport const apiKeySelector = createSelector(appSelector, app => app.apiKey)\n","import { createSelector } from 'redux-starter-kit'\n\nexport const walletSelector = state => state.wallet\n\nexport const isWalletUnlockedSelector = createSelector(\n walletSelector,\n wallet => wallet.isWalletUnlocked,\n)\n\nexport const isWalletInitializedSelector = createSelector(\n walletSelector,\n wallet => wallet.isWalletInitialized,\n)\n","import { createSlice } from 'redux-starter-kit'\n\nconst initialState = {\n isWalletUnlocked: null,\n isWalletInitialized: null,\n}\n\nexport default createSlice({\n name: 'walletSlice',\n initialState,\n reducers: {\n setIsWalletUnlocked: (state, { payload }) => {\n state.isWalletUnlocked = payload\n },\n setIsWalletInitialized: (state, { payload }) => {\n state.isWalletInitialized = payload\n },\n },\n})\n","import { createAction } from 'redux-starter-kit'\nimport walletSlice from '../slices/walletSlice'\n\nconst checkWalletStatus = createAction('checkWalletStatus')\n\nexport default {\n ...walletSlice.actions,\n checkWalletStatus,\n}\n","import { createSlice } from 'redux-starter-kit'\n\nconst initialState = {\n apiKey: '',\n}\n\nexport default createSlice({\n name: 'appSlice',\n initialState,\n reducers: {\n setApiKey: (state, action) => {\n state.apiKey = action.payload\n },\n },\n})\n","import appSlice from '../slices/appSlice'\n\nexport default {\n ...appSlice.actions,\n}\n","const appConfig = () => {\n return {\n nodeApiLink: '/',\n }\n}\n\nexport default {\n ...appConfig(),\n}\n","import axios from 'axios'\nimport environment from '../utils/environment'\n\nfunction NetworkError({ status, message, data, statusText }) {\n this.name = 'NetworkError'\n this.message = message || statusText\n this.status = status\n this.data = data\n}\n\nNetworkError.prototype = Object.create(Error.prototype)\n\nconst nodeApi = axios.create({\n baseURL: environment.nodeApiLink,\n timeout: 1000 * 10,\n crossDomain: true,\n headers: {\n 'Content-Type': 'application/json',\n },\n})\n\nnodeApi.interceptors.response.use(\n response => Promise.resolve(response),\n error => Promise.reject(new NetworkError(error.response || error)),\n)\n\nexport default nodeApi\n","import { toast } from 'react-toastify'\nimport './index.scss'\n\nconst toastStates = {\n success: (text, options) =>\n toast.success(text, {\n position: 'top-right',\n autoClose: 5000,\n hideProgressBar: false,\n closeOnClick: true,\n pauseOnHover: true,\n draggable: true,\n className: 'n-toast n-toast--success',\n bodyClassName: 'n-toast__body',\n progressClassName: 'n-toast__progress--success',\n ...options,\n }),\n error: (text, options) =>\n toast.error(text, {\n position: 'top-right',\n autoClose: 5000,\n hideProgressBar: false,\n closeOnClick: true,\n pauseOnHover: true,\n draggable: true,\n className: 'n-toast n-toast--error',\n bodyClassName: 'n-toast__body',\n progressClassName: 'n-toast__progress--error',\n ...options,\n }),\n info: toast.info,\n}\n\nexport default (state, text, options) =>\n toastStates[state]\n ? toastStates[state](text, options)\n : new Error(`Bad toast state`)\n","import React from 'react'\nimport { Modal } from 'react-bootstrap'\nimport { Formik, Form, Field } from 'formik'\n\nconst renderButton = (apiKey, handleShow) => {\n if (apiKey === '') {\n return (\n \n Set API key\n \n )\n }\n\n return (\n \n Update API key\n \n )\n}\n\nconst ApiKeyModalView = ({\n showModal,\n handleHide,\n submitForm,\n apiKey,\n handleShow,\n}) => {\n return (\n \n {renderButton(apiKey, handleShow)}\n
handleHide()} centered>\n \n {() => (\n \n )}\n \n \n
\n )\n}\n\nexport default ApiKeyModalView\n","import ApiKeyModalContainer from './ApiKeyModalContainer'\n\nexport default ApiKeyModalContainer\n","import React, { memo, useState } from 'react'\nimport { connect } from 'react-redux'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport appActions from '../../../store/actions/appActions'\nimport nodeApi from '../../../api/api'\nimport customToast from '../../../utils/toast'\nimport ApiKeyModalView from './ApiKeyModalView'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchSetApiKey: apiKey => dispatch(appActions.setApiKey(apiKey)),\n})\n\nconst ApiKeyModalContainer = props => {\n const { dispatchSetApiKey, apiKey } = props\n\n const [showModal, setShowModal] = useState(false)\n\n const handleShow = () => {\n setShowModal(true)\n }\n\n const handleHide = () => {\n setShowModal(false)\n }\n\n const submitForm = values => {\n // Check API key for random get method\n nodeApi\n .get('/wallet/status', {\n headers: {\n api_key: values.apiKey,\n },\n })\n .then(() => {\n dispatchSetApiKey(values.apiKey.trim())\n customToast('success', 'API key is set successfully')\n handleHide()\n })\n .catch(() => {\n customToast('error', 'Bad API key')\n })\n }\n\n return (\n \n )\n}\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(ApiKeyModalContainer))\n","import React, { Component, memo } from 'react'\nimport Modal from 'react-bootstrap/Modal'\nimport { Formik, Field, Form } from 'formik'\nimport { connect } from 'react-redux'\nimport { isWalletUnlockedSelector } from '../../../store/selectors/wallet'\nimport walletActions from '../../../store/actions/walletActions'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport customToast from '../../../utils/toast'\nimport nodeApi from '../../../api/api'\n\nconst mapStateToProps = state => ({\n isWalletUnlocked: isWalletUnlockedSelector(state),\n apiKey: apiKeySelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchSetIsWalletUnlocked: isWalletUnlock =>\n dispatch(walletActions.setIsWalletUnlocked(isWalletUnlock)),\n})\n\nclass WalletStatusForm extends Component {\n state = {\n showModal: false,\n }\n\n handleShow = () => {\n this.setState({ showModal: true })\n }\n\n handleHide = () => {\n this.setState({ showModal: false })\n }\n\n walletUnlock = pass =>\n nodeApi.post(\n '/wallet/unlock',\n { pass },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n\n walletLock = () =>\n nodeApi.get('/wallet/lock', {\n headers: {\n api_key: this.props.apiKey,\n },\n })\n\n submitWalletUnlockForm = (\n { pass },\n { setSubmitting, resetForm, setStatus },\n ) => {\n setStatus({ status: 'submitting' })\n this.walletUnlock(pass)\n .then(() => {\n resetForm({ pass: '' })\n customToast('success', 'Your wallet is unlocked successfully')\n this.props.dispatchSetIsWalletUnlocked(true)\n this.handleHide()\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n submitWalletLockForm = () => {\n // eslint-disable-next-line\n if (confirm('Are you sure want to lock wallet?')) {\n this.walletLock()\n .then(() => {\n customToast('success', 'Your wallet is locked successfully')\n this.props.dispatchSetIsWalletUnlocked(false)\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n })\n }\n }\n\n renderButton = () => {\n if (!this.props.isWalletUnlocked) {\n return (\n \n Unlock wallet\n \n )\n }\n\n return (\n \n Lock wallet\n \n )\n }\n\n render() {\n return (\n \n {this.renderButton()}\n
this.handleHide()}\n centered\n aria-labelledby=\"example-custom-modal-styling-title\"\n >\n \n {({ isSubmitting }) => (\n \n )}\n \n \n
\n )\n }\n}\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(WalletStatusForm))\n","import React from 'react'\nimport copy from 'clipboard-copy'\nimport { Overlay, Tooltip } from 'react-bootstrap'\nimport { faCopy } from '@fortawesome/free-solid-svg-icons'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\n\nclass CopyToClipboard extends React.PureComponent {\n constructor(props) {\n super(props)\n\n this.myRef = React.createRef()\n this.state = { showTooltip: false }\n }\n\n render() {\n return (\n <>\n \n {this.props.children}\n \n \n \n \n Copied! \n \n >\n )\n }\n\n startTimer = () => {\n const timerId = setTimeout(\n () => this.setState({ showTooltip: false }),\n 1500,\n )\n this.setState({ timerId })\n }\n\n onCopy = e => {\n e.preventDefault()\n copy(this.props.children)\n this.setState({ showTooltip: true })\n this.startTimer()\n }\n\n handleOnTooltipClose = () => {\n this.setState({ showTooltip: false })\n }\n\n componentWillUnmount() {\n clearTimeout(this.state.timerId)\n }\n}\n\nexport default CopyToClipboard\n","import React, { Component, memo } from 'react'\nimport { Formik, Field, Form } from 'formik'\nimport nodeApi from '../../../api/api'\nimport CopyToClipboard from '../../common/CopyToClipboard'\nimport customToast from '../../../utils/toast'\n\nconst initialFormValues = {\n walletPassword: '',\n mnemonicPass: '',\n}\n\nclass WalletInitializeForm extends Component {\n state = { isShowMnemonic: false }\n\n walletInit = async ({ walletPassword, mnemonicPass }) => {\n const { data } = await nodeApi.post(\n '/wallet/init',\n { pass: walletPassword, mnemonicPass },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n\n return data\n }\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.walletInit(values)\n .then(result => {\n resetForm(initialFormValues)\n setStatus({\n state: 'success',\n msg: (\n <>\n Your wallet successfully initialized. Please, save your mnemonic -{' '}\n {result.mnemonic} \n >\n ),\n })\n this.setState({ isShowMnemonic: true })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
Initialize wallet \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n )\n }\n}\n\nexport default memo(WalletInitializeForm)\n","import React, { Component, memo } from 'react'\nimport { Formik, Field, Form } from 'formik'\nimport nodeApi from '../../../api/api'\nimport customToast from '../../../utils/toast'\n\nconst initialFormValues = {\n walletPassword: '',\n mnemonicPass: '',\n mnemonic: '',\n}\n\nclass WalletInitializeForm extends Component {\n walletRestore = async ({\n walletPassword,\n mnemonicPass = '',\n mnemonic = '',\n }) => {\n if (!mnemonic || !String(mnemonic).trim()) {\n throw Error('Need to set mnemonic')\n }\n\n return nodeApi.post(\n '/wallet/restore',\n { pass: walletPassword, mnemonicPass, mnemonic },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n }\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.walletRestore(values)\n .then(() => {\n resetForm(initialFormValues)\n customToast('success', 'Your wallet successfully re-stored')\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
Re-store wallet \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n )\n }\n}\n\nexport default memo(WalletInitializeForm)\n","import React, { Component, memo } from 'react'\nimport Modal from 'react-bootstrap/Modal'\nimport { connect } from 'react-redux'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport appActions from '../../../store/actions/appActions'\nimport WalletInitializeForm from '../../elements/WalletInitializeForm'\nimport RestoreWalletForm from '../../elements/RestoreWalletForm'\nimport walletActions from '../../../store/actions/walletActions'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n dispatchSetApiKey: apiKey => dispatch(appActions.setApiKey(apiKey)),\n})\n\nclass WalletInitModal extends Component {\n state = {\n showModal: false,\n }\n\n handleShow = () => {\n this.setState({ showModal: true })\n }\n\n handleHide = () => {\n this.props.dispatchCheckWalletStatus()\n this.setState({ showModal: false })\n }\n\n renderButton = () => {\n return (\n \n Initialize wallet\n \n )\n }\n\n render() {\n const { apiKey } = this.props\n\n return (\n \n {this.renderButton()}\n
this.handleHide()}\n centered\n size=\"lg\"\n >\n \n \n Wallet initialization\n \n \n \n \n \n
\n \n \n
\n \n \n \n Close\n \n \n \n
\n )\n }\n}\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(WalletInitModal))\n","import React, { memo } from 'react'\nimport { Navbar } from 'react-bootstrap'\nimport { Link } from 'react-router-dom'\nimport ApiKeyModal from './ApiKeyModal'\nimport WalletStatusModal from './WalletStatusModal'\nimport WalletInitModal from './WalletInitModal'\nimport logo from '../../assets/images/logotype_white.svg'\n\nconst renderWalletForms = isWalletInitialized => {\n if (isWalletInitialized === null) {\n return <>>\n }\n\n if (isWalletInitialized) {\n return (\n \n \n
\n )\n }\n\n return (\n \n \n
\n )\n}\n\nconst HeaderView = ({ isApiKeySetted, isWalletInitialized }) => {\n return (\n \n \n \n \n \n \n \n {isApiKeySetted && renderWalletForms(isWalletInitialized)}\n \n )\n}\n\nexport default memo(HeaderView)\n","import HeaderContainer from './HeaderContainer'\n\nexport default HeaderContainer\n","import React, { memo, useEffect } from 'react'\nimport { connect } from 'react-redux'\nimport { apiKeySelector } from '../../store/selectors/app'\nimport { isWalletInitializedSelector } from '../../store/selectors/wallet'\nimport walletActions from '../../store/actions/walletActions'\nimport HeaderView from './HeaderView'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n})\n\nconst HeaderContainer = props => {\n const { apiKey, dispatchCheckWalletStatus, isWalletInitialized } = props\n\n useEffect(() => {\n if (apiKey !== '') {\n dispatchCheckWalletStatus()\n }\n }, [apiKey, dispatchCheckWalletStatus])\n\n const isApiKeySetted = apiKey !== ''\n\n return (\n \n )\n}\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(HeaderContainer))\n","import React, { Component } from 'react'\nimport { withRouter } from 'react-router-dom'\nimport MenuList from '../common/MenuList'\nimport './index.scss'\nimport Header from '../Header'\n\nclass Layout extends Component {\n render() {\n return (\n \n
\n
\n \n
\n
\n {this.props.children}
\n \n
\n )\n }\n}\n\nexport default withRouter(Layout)\n","import React from 'react'\nimport clsx from 'clsx'\nimport './index.scss'\n\nconst InfoCard = ({ color, children, className }) => {\n return (\n \n {children}\n
\n )\n}\n\nexport default InfoCard\n","import React, { Component } from 'react'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { faSync, faCheck } from '@fortawesome/free-solid-svg-icons'\nimport InfoCard from '../InfoCard'\nimport './index.scss'\n\nexport default class SynchCard extends Component {\n renderActiveSynchronization = () => (\n <>\n Current node state
\n \n Active\n synchronization\n
\n >\n )\n\n renderCompleteSynchronization = () => (\n <>\n Current node state
\n \n Node is synced\n
\n >\n )\n\n renderSynchronizationState = state =>\n ({\n active: this.renderActiveSynchronization,\n complete: this.renderCompleteSynchronization,\n }[state])\n\n getSynchronizationState = ({ fullHeight, headersHeight }) => {\n if (\n fullHeight !== null &&\n headersHeight !== null &&\n fullHeight === headersHeight\n ) {\n return 'complete'\n }\n\n return 'active'\n }\n\n shouldComponentUpdate(nextProps) {\n if (\n this.getSynchronizationState(nextProps) !==\n this.getSynchronizationState(this.props.nodeInfo)\n ) {\n return true\n }\n\n return false\n }\n\n render() {\n const currentSynchState = this.getSynchronizationState(this.props.nodeInfo)\n return (\n \n {this.renderSynchronizationState(currentSynchState)()}\n \n )\n }\n}\n","import React, { Fragment } from 'react'\nimport {\n faExclamationTriangle,\n faSync,\n} from '@fortawesome/free-solid-svg-icons'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { format } from 'date-fns'\nimport InfoCard from './InfoCard'\nimport SynchCard from './SynchCard'\n\nconst getWalletStatus = (isWalletInitialized, isWalletUnlocked) => {\n if (!isWalletInitialized) {\n return 'Not initialized'\n }\n\n if (!isWalletUnlocked) {\n return 'Initialized'\n }\n\n return 'Unlocked'\n}\n\nconst DashboardView = ({\n error,\n nodeInfo,\n isWalletInitialized,\n isWalletUnlocked,\n apiKey,\n}) => {\n if (error !== null) {\n return (\n \n \n
\n \n \n {error}\n \n \n \n )\n }\n\n if (nodeInfo === null) {\n return (\n \n \n \n
\n \n )\n }\n\n const {\n peersCount,\n bestHeaderId,\n launchTime,\n fullHeight,\n appVersion,\n isMining,\n } = nodeInfo\n\n return (\n \n \n
\n
\n
\n Node version
\n {appVersion}
\n \n
\n
\n \n
\n
\n
\n Node started at
\n \n {format(new Date(launchTime), 'MM-dd-yyyy HH:mm:ss')}\n
\n \n
\n {fullHeight === null ? null : (\n
\n
\n Current height
\n {fullHeight}
\n \n
\n )}\n {bestHeaderId === null ? null : (\n
\n
\n Best block id
\n {bestHeaderId}
\n \n
\n )}\n
\n
\n Mining enabled
\n {isMining ? 'true' : 'false'}
\n \n
\n
\n
\n Peers connected
\n {peersCount}
\n \n
\n {apiKey !== '' && (\n
\n
\n Wallet status
\n \n {getWalletStatus(isWalletInitialized, isWalletUnlocked)}\n
\n \n
\n )}\n
\n
\n \n )\n}\n\nexport default DashboardView\n","import { useEffect, useRef } from 'react'\n\nfunction usePrevious(value) {\n // The ref object is a generic container whose current property is mutable ...\n // ... and can hold any value, similar to an instance property on a class\n const ref = useRef()\n\n // Store current value in ref\n useEffect(() => {\n ref.current = value\n }, [value]) // Only re-run if value changes\n\n // Return previous value (happens before update in useEffect above)\n return ref.current\n}\n\nexport default usePrevious\n","import DashboardContainer from './DashboardContainer'\n\nexport default DashboardContainer\n","import React, { useState, useEffect, useCallback, memo } from 'react'\nimport { connect } from 'react-redux'\nimport nodeApi from '../../../api/api'\nimport DashboardView from './DashboardView'\nimport {\n isWalletInitializedSelector,\n isWalletUnlockedSelector,\n} from '../../../store/selectors/wallet'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport usePrevious from '../../../hooks/usePrevious'\nimport walletActions from '../../../store/actions/walletActions'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n isWalletUnlocked: isWalletUnlockedSelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n})\n\nconst DashboardContainer = props => {\n const {\n isWalletInitialized,\n isWalletUnlocked,\n apiKey,\n dispatchCheckWalletStatus,\n } = props\n\n const [nodeInfo, setNodeInfo] = useState(null)\n const [error, setError] = useState(null)\n const [timerId, setTimerId] = useState(null)\n\n const getNodeCurrentState = () => nodeApi.get('/info')\n\n const setNodeCurrentState = useCallback(async () => {\n try {\n const { data } = await getNodeCurrentState()\n\n setNodeInfo(data)\n setError(null)\n } catch {\n setError('Node connection is lost.')\n }\n }, [])\n\n const setTimer = useCallback(() => {\n const newTimerId = setInterval(setNodeCurrentState, 2000)\n\n setTimerId(newTimerId)\n }, [setNodeCurrentState])\n\n const prevError = usePrevious(error)\n useEffect(() => {\n if (prevError && prevError !== error) {\n dispatchCheckWalletStatus()\n }\n }, [dispatchCheckWalletStatus, error, prevError])\n\n useEffect(() => {\n setNodeCurrentState()\n setTimer()\n // eslint-disable-next-line\n }, [])\n\n useEffect(\n () => () => {\n clearInterval(timerId)\n },\n [timerId],\n )\n\n return (\n \n )\n}\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(DashboardContainer))\n","import React, { PureComponent } from 'react'\nimport { Formik, Field, Form } from 'formik'\nimport nodeApi from '../../../../../api/api'\nimport customToast from '../../../../../utils/toast'\nimport CopyToClipboard from '../../../../common/CopyToClipboard'\nimport constants from '../../../../../utils/constants'\n\nconst initialFormValues = {\n recipientAddress: '',\n amount: '',\n}\n\nclass PaymentSendForm extends PureComponent {\n state = {\n isShowTransactionId: false,\n }\n\n paymentSend = ({ recipientAddress, amount }) =>\n nodeApi.post(\n '/wallet/payment/send',\n [\n {\n address: recipientAddress,\n value: Number(\n (parseFloat(amount) * constants.nanoErgInErg).toFixed(1),\n ),\n },\n ],\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.paymentSend(values)\n .then(({ data }) => {\n resetForm(initialFormValues)\n setStatus({\n state: 'success',\n msg: (\n <>\n \n Your payment successfully sent. Your transaction ID -{' '}\n {data} \n
\n \n \n Click Here To Go To The Explorer\n \n
\n >\n ),\n })\n this.setState({ isShowTransactionId: true })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
\n
Payment send \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n
\n )\n }\n}\n\nexport default PaymentSendForm\n","import React, { PureComponent } from 'react'\nimport { Formik, Form } from 'formik'\nimport NumberFormat from 'react-number-format'\nimport nodeApi from '../../../../../api/api'\nimport customToast from '../../../../../utils/toast'\n\nconst initialFormValues = {\n walletPassword: '',\n}\n\nclass GetBalanceForm extends PureComponent {\n state = {\n isShowBalance: false,\n }\n\n getBalance = () =>\n nodeApi.get('/wallet/balances', {\n headers: {\n api_key: this.props.apiKey,\n },\n })\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.getBalance(values)\n .then(({ data: { balance } }) => {\n resetForm(initialFormValues)\n setStatus({\n state: 'success',\n msg: (\n <>\n Your wallet balance -{' '}\n \n >\n ),\n })\n this.setState({ isShowBalance: true })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
\n
Get confirmed wallet balance \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n
\n )\n }\n}\n\nexport default GetBalanceForm\n","import React, { PureComponent } from 'react'\nimport nodeApi from '../../../../../api/api'\nimport customToast from '../../../../../utils/toast'\nimport CopyToClipboard from '../../../../common/CopyToClipboard'\n\nclass GetWalletAddressesForm extends PureComponent {\n state = {\n isShowWalletAddresses: false,\n walletAddresses: [],\n }\n\n getWalletAddresses = () =>\n nodeApi.get('/wallet/addresses', {\n headers: {\n api_key: this.props.apiKey,\n },\n })\n\n handleSubmit = event => {\n event.preventDefault()\n\n this.getWalletAddresses()\n .then(({ data: walletAddresses }) => {\n this.setState({ isShowWalletAddresses: true, walletAddresses })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n })\n }\n\n render() {\n return (\n \n
\n
Get all wallet addresses \n
\n
\n
\n )\n }\n}\n\nexport default GetWalletAddressesForm\n","import React, { Component, memo } from 'react'\nimport { connect } from 'react-redux'\nimport PaymentSendForm from './components/PaymentSendForm'\nimport GetBalanceForm from './components/GetBalanceForm'\nimport GetWalletAddressesForm from './components/GetWalletAddressesForm'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport {\n isWalletInitializedSelector,\n isWalletUnlockedSelector,\n} from '../../../store/selectors/wallet'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n isWalletUnlocked: isWalletUnlockedSelector(state),\n})\n\nclass Wallet extends Component {\n renderState = state =>\n ({\n unlocked: apiKey => this.renderWalletUnlockedState(apiKey),\n locked: () => this.renderWalletLockedState(),\n initialized: apiKey => this.renderInitializedState(apiKey),\n }[state])\n\n renderWalletUnlockedState = apiKey => (\n \n )\n\n renderWalletLockedState = () => (\n \n
\n The wallet UI is locked. You need to unlock the wallet to access its UI.\n
\n
\n )\n\n renderInitializedState = () => (\n \n
You need to initialize your wallet to access wallet UI.
\n
\n )\n\n render() {\n const { apiKey, isWalletUnlocked, isWalletInitialized } = this.props\n\n if (apiKey === '') {\n return (\n \n
For continue need to set API key.
\n
\n )\n }\n\n if (!isWalletInitialized) {\n return this.renderState('initialized')(apiKey)\n }\n\n if (isWalletUnlocked) {\n return this.renderState('unlocked')(apiKey)\n }\n\n return this.renderState('locked')()\n }\n}\n\nexport default connect(mapStateToProps)(memo(Wallet))\n","import React from 'react'\nimport { BrowserRouter, Switch, Route } from 'react-router-dom'\nimport Layout from '../components/layout'\nimport Dashboard from '../components/pages/Dashboard'\nimport Wallet from '../components/pages/Wallet'\n\nconst Router = () => (\n \n \n \n \n \n \n \n \n)\n\nexport default Router\n","import { combineReducers } from 'redux'\nimport appSlice from '../slices/appSlice'\nimport walletSlice from '../slices/walletSlice'\n\nexport default combineReducers({\n app: appSlice.reducer,\n wallet: walletSlice.reducer,\n})\n","import walletActions from '../actions/walletActions'\nimport nodeApi from '../../api/api'\nimport { apiKeySelector } from '../selectors/app'\n\nexport default store => next => action => {\n const { dispatch, getState } = store\n const apiKey = apiKeySelector(getState())\n\n switch (action.type) {\n case walletActions.checkWalletStatus.type:\n nodeApi\n .get('/wallet/status', {\n headers: {\n api_key: apiKey,\n },\n })\n .then(({ data: { isUnlocked, isInitialized } }) => {\n dispatch(walletActions.setIsWalletUnlocked(isUnlocked))\n dispatch(walletActions.setIsWalletInitialized(isInitialized))\n })\n .catch(() => {})\n\n break\n\n default:\n break\n }\n next(action)\n}\n","import React from 'react'\nimport { toast } from 'react-toastify'\nimport { Provider } from 'react-redux'\nimport Router from './router/router'\nimport createStore from './store'\n\nimport 'bootstrap/dist/css/bootstrap.min.css'\nimport './assets/styles/index.scss'\nimport 'react-toastify/dist/ReactToastify.min.css'\n\ntoast.configure()\nconst store = createStore()\n\nconst App = () => {\n return (\n \n \n \n )\n}\n\nexport default App\n","import { configureStore, getDefaultMiddleware } from 'redux-starter-kit'\nimport rootReducer from './reducers/rootReducer'\nimport walletMiddleware from './middlewares/walletMiddleware'\n\nexport default () => {\n const store = configureStore({\n reducer: rootReducer,\n middleware: [...getDefaultMiddleware(), walletMiddleware],\n })\n\n return store\n}\n","import React from 'react'\nimport ReactDOM from 'react-dom'\nimport App from './App'\n\nReactDOM.render( , document.getElementById('root'))\n"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"sources":["assets/images/logotype_white.svg","utils/constants.js","components/common/MenuList/index.js","store/selectors/app.js","store/selectors/wallet.js","store/slices/walletSlice.js","store/actions/walletActions.js","store/slices/appSlice.js","store/actions/appActions.js","utils/environment.js","api/api.js","utils/toast/index.js","components/Header/ApiKeyModal/ApiKeyModalView.js","components/Header/ApiKeyModal/index.js","components/Header/ApiKeyModal/ApiKeyModalContainer.js","components/Header/WalletStatusModal/index.js","components/common/CopyToClipboard/index.js","components/elements/WalletInitializeForm/index.js","components/elements/RestoreWalletForm/index.js","components/Header/WalletInitModal/index.js","components/Header/HeaderView.js","components/Header/index.js","components/Header/HeaderContainer.js","components/layout/index.js","components/pages/Dashboard/InfoCard/index.js","components/pages/Dashboard/SynchCard/index.js","components/pages/Dashboard/DashboardView.js","hooks/usePrevious.js","components/pages/Dashboard/index.js","components/pages/Dashboard/DashboardContainer.js","components/pages/Wallet/components/PaymentSendForm/index.js","components/pages/Wallet/components/GetBalanceForm/index.js","components/pages/Wallet/components/GetWalletAddressesForm/index.js","components/pages/Wallet/index.js","router/router.js","store/reducers/rootReducer.js","store/middlewares/walletMiddleware.js","App.js","store/index.js","index.js"],"names":["module","exports","swaggerInterface","website","explorer","nanoErgInErg","localRouteList","dashboard","href","icon","faChartLine","title","wallet","faExchangeAlt","externalRouteList","constants","faBook","faWpexplorer","faGlobe","withRouter","pathname","location","className","Object","values","map","index","key","clsx","active","to","rel","target","apiKeySelector","createSelector","state","app","apiKey","walletSelector","isWalletUnlockedSelector","isWalletUnlocked","isWalletInitializedSelector","isWalletInitialized","createSlice","name","initialState","reducers","setIsWalletUnlocked","payload","setIsWalletInitialized","checkWalletStatus","createAction","walletSlice","actions","setApiKey","action","appSlice","nodeApiLink","NetworkError","status","message","data","statusText","this","prototype","create","Error","nodeApi","axios","baseURL","environment","timeout","crossDomain","headers","interceptors","response","use","Promise","resolve","error","reject","toastStates","success","text","options","toast","position","autoClose","hideProgressBar","closeOnClick","pauseOnHover","draggable","bodyClassName","progressClassName","info","ApiKeyModalView","showModal","handleHide","submitForm","handleShow","onClick","renderButton","Modal","show","onHide","centered","initialValues","onSubmit","Header","closeButton","Title","Body","type","placeholder","Footer","ApiKeyModalContainer","connect","dispatch","dispatchSetApiKey","appActions","memo","props","useState","setShowModal","get","api_key","then","trim","customToast","catch","WalletStatusForm","setState","walletUnlock","pass","post","walletLock","submitWalletUnlockForm","setSubmitting","resetForm","setStatus","dispatchSetIsWalletUnlocked","err","errMessage","detail","submitWalletLockForm","confirm","aria-labelledby","isSubmitting","id","htmlFor","disabled","Component","isWalletUnlock","walletActions","CopyToClipboard","startTimer","timerId","setTimeout","showTooltip","onCopy","e","preventDefault","copy","children","handleOnTooltipClose","myRef","React","createRef","ref","faCopy","Overlay","current","placement","Tooltip","clearTimeout","PureComponent","initialFormValues","walletPassword","mnemonicPass","WalletInitializeForm","isShowMnemonic","walletInit","a","handleSubmit","result","msg","mnemonic","role","aria-hidden","walletRestore","String","required","WalletInitModal","dispatchCheckWalletStatus","size","RestoreWalletForm","isApiKeySetted","Navbar","expand","Brand","src","logo","alt","ApiKeyModal","WalletStatusModal","renderWalletForms","HeaderContainer","useEffect","Layout","InfoCard","color","SynchCard","renderActiveSynchronization","faSync","spin","renderCompleteSynchronization","faCheck","renderSynchronizationState","complete","getSynchronizationState","fullHeight","headersHeight","nextProps","nodeInfo","currentSynchState","DashboardView","faExclamationTriangle","peersCount","bestHeaderId","launchTime","appVersion","isMining","format","Date","getWalletStatus","usePrevious","value","useRef","DashboardContainer","setNodeInfo","setError","setTimerId","setNodeCurrentState","useCallback","setTimer","newTimerId","setInterval","prevError","clearInterval","recipientAddress","amount","PaymentSendForm","isShowTransactionId","paymentSend","address","Number","parseFloat","toFixed","GetBalanceForm","isShowBalance","getBalance","balance","displayType","thousandSeparator","suffix","GetWalletAddressesForm","isShowWalletAddresses","walletAddresses","getWalletAddresses","event","addr","Wallet","renderState","unlocked","renderWalletUnlockedState","locked","renderWalletLockedState","initialized","renderInitializedState","Router","basename","exact","path","component","Dashboard","combineReducers","reducer","store","next","getState","isUnlocked","isInitialized","configure","configureStore","rootReducer","middleware","getDefaultMiddleware","walletMiddleware","App","ReactDOM","render","document","getElementById"],"mappings":"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","file":"static/js/main.d2ee9699.chunk.js","sourcesContent":["module.exports = __webpack_public_path__ + \"static/media/logotype_white.4dcfd639.svg\";","export default {\n swaggerInterface: '/swagger',\n website: 'https://ergoplatform.org',\n explorer: 'https://explorer.ergoplatform.com',\n nanoErgInErg: 1000000000,\n}\n","import React from 'react'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faChartLine,\n faExchangeAlt,\n faGlobe,\n faBook,\n} from '@fortawesome/free-solid-svg-icons'\nimport clsx from 'clsx'\nimport { faWpexplorer } from '@fortawesome/free-brands-svg-icons'\nimport { withRouter, Link } from 'react-router-dom'\nimport constants from '../../../utils/constants'\n\nconst localRouteList = {\n dashboard: {\n href: '/',\n icon: ,\n title: 'Dashboard',\n },\n wallet: {\n href: '/wallet',\n icon: ,\n title: 'Wallet',\n },\n}\n\nconst externalRouteList = {\n swaggerInterface: {\n href: constants.swaggerInterface,\n icon: ,\n title: 'Swagger',\n },\n explorer: {\n href: constants.explorer,\n icon: ,\n title: 'Explorer',\n },\n website: {\n href: constants.website,\n icon: ,\n title: 'Website',\n },\n}\n\nconst MenuList = ({ location: { pathname } }) => {\n return (\n \n
Menu
\n
\n
\n {Object.values(localRouteList).map(({ href, icon, title }, index) => (\n \n {icon} {title}\n \n ))}\n
\n
External links
\n
\n
\n {Object.values(externalRouteList).map(\n ({ href, icon, title }, index) => (\n
\n {icon} {title}\n \n ),\n )}\n
\n
\n )\n}\n\nexport default withRouter(MenuList)\n","import { createSelector } from 'redux-starter-kit'\n\nexport const appSelector = state => state.app\n\nexport const apiKeySelector = createSelector(appSelector, app => app.apiKey)\n","import { createSelector } from 'redux-starter-kit'\n\nexport const walletSelector = state => state.wallet\n\nexport const isWalletUnlockedSelector = createSelector(\n walletSelector,\n wallet => wallet.isWalletUnlocked,\n)\n\nexport const isWalletInitializedSelector = createSelector(\n walletSelector,\n wallet => wallet.isWalletInitialized,\n)\n","import { createSlice } from 'redux-starter-kit'\n\nconst initialState = {\n isWalletUnlocked: null,\n isWalletInitialized: null,\n}\n\nexport default createSlice({\n name: 'walletSlice',\n initialState,\n reducers: {\n setIsWalletUnlocked: (state, { payload }) => {\n state.isWalletUnlocked = payload\n },\n setIsWalletInitialized: (state, { payload }) => {\n state.isWalletInitialized = payload\n },\n },\n})\n","import { createAction } from 'redux-starter-kit'\nimport walletSlice from '../slices/walletSlice'\n\nconst checkWalletStatus = createAction('checkWalletStatus')\n\nexport default {\n ...walletSlice.actions,\n checkWalletStatus,\n}\n","import { createSlice } from 'redux-starter-kit'\n\nconst initialState = {\n apiKey: '',\n}\n\nexport default createSlice({\n name: 'appSlice',\n initialState,\n reducers: {\n setApiKey: (state, action) => {\n state.apiKey = action.payload\n },\n },\n})\n","import appSlice from '../slices/appSlice'\n\nexport default {\n ...appSlice.actions,\n}\n","const appConfig = () => {\n return {\n nodeApiLink: '/',\n }\n}\n\nexport default {\n ...appConfig(),\n}\n","import axios from 'axios'\nimport environment from '../utils/environment'\n\nfunction NetworkError({ status, message, data, statusText }) {\n this.name = 'NetworkError'\n this.message = message || statusText\n this.status = status\n this.data = data\n}\n\nNetworkError.prototype = Object.create(Error.prototype)\n\nconst nodeApi = axios.create({\n baseURL: environment.nodeApiLink,\n timeout: 1000 * 10,\n crossDomain: true,\n headers: {\n 'Content-Type': 'application/json',\n },\n})\n\nnodeApi.interceptors.response.use(\n response => Promise.resolve(response),\n error => Promise.reject(new NetworkError(error.response || error)),\n)\n\nexport default nodeApi\n","import { toast } from 'react-toastify'\nimport './index.scss'\n\nconst toastStates = {\n success: (text, options) =>\n toast.success(text, {\n position: 'top-right',\n autoClose: 5000,\n hideProgressBar: false,\n closeOnClick: true,\n pauseOnHover: true,\n draggable: true,\n className: 'n-toast n-toast--success',\n bodyClassName: 'n-toast__body',\n progressClassName: 'n-toast__progress--success',\n ...options,\n }),\n error: (text, options) =>\n toast.error(text, {\n position: 'top-right',\n autoClose: 5000,\n hideProgressBar: false,\n closeOnClick: true,\n pauseOnHover: true,\n draggable: true,\n className: 'n-toast n-toast--error',\n bodyClassName: 'n-toast__body',\n progressClassName: 'n-toast__progress--error',\n ...options,\n }),\n info: toast.info,\n}\n\nexport default (state, text, options) =>\n toastStates[state]\n ? toastStates[state](text, options)\n : new Error(`Bad toast state`)\n","import React from 'react'\nimport { Modal } from 'react-bootstrap'\nimport { Formik, Form, Field } from 'formik'\n\nconst renderButton = (apiKey, handleShow) => {\n if (apiKey === '') {\n return (\n \n Set API key\n \n )\n }\n\n return (\n \n Update API key\n \n )\n}\n\nconst ApiKeyModalView = ({\n showModal,\n handleHide,\n submitForm,\n apiKey,\n handleShow,\n}) => {\n return (\n \n {renderButton(apiKey, handleShow)}\n
handleHide()} centered>\n \n {() => (\n \n )}\n \n \n
\n )\n}\n\nexport default ApiKeyModalView\n","import ApiKeyModalContainer from './ApiKeyModalContainer'\n\nexport default ApiKeyModalContainer\n","import React, { memo, useState } from 'react'\nimport { connect } from 'react-redux'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport appActions from '../../../store/actions/appActions'\nimport nodeApi from '../../../api/api'\nimport customToast from '../../../utils/toast'\nimport ApiKeyModalView from './ApiKeyModalView'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchSetApiKey: apiKey => dispatch(appActions.setApiKey(apiKey)),\n})\n\nconst ApiKeyModalContainer = props => {\n const { dispatchSetApiKey, apiKey } = props\n\n const [showModal, setShowModal] = useState(false)\n\n const handleShow = () => {\n setShowModal(true)\n }\n\n const handleHide = () => {\n setShowModal(false)\n }\n\n const submitForm = values => {\n // Check API key for random get method\n nodeApi\n .get('/wallet/status', {\n headers: {\n api_key: values.apiKey,\n },\n })\n .then(() => {\n dispatchSetApiKey(values.apiKey.trim())\n customToast('success', 'API key is set successfully')\n handleHide()\n })\n .catch(() => {\n customToast('error', 'Bad API key')\n })\n }\n\n return (\n \n )\n}\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(ApiKeyModalContainer))\n","import React, { Component, memo } from 'react'\nimport Modal from 'react-bootstrap/Modal'\nimport { Formik, Field, Form } from 'formik'\nimport { connect } from 'react-redux'\nimport { isWalletUnlockedSelector } from '../../../store/selectors/wallet'\nimport walletActions from '../../../store/actions/walletActions'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport customToast from '../../../utils/toast'\nimport nodeApi from '../../../api/api'\n\nconst mapStateToProps = state => ({\n isWalletUnlocked: isWalletUnlockedSelector(state),\n apiKey: apiKeySelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchSetIsWalletUnlocked: isWalletUnlock =>\n dispatch(walletActions.setIsWalletUnlocked(isWalletUnlock)),\n})\n\nclass WalletStatusForm extends Component {\n state = {\n showModal: false,\n }\n\n handleShow = () => {\n this.setState({ showModal: true })\n }\n\n handleHide = () => {\n this.setState({ showModal: false })\n }\n\n walletUnlock = pass =>\n nodeApi.post(\n '/wallet/unlock',\n { pass },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n\n walletLock = () =>\n nodeApi.get('/wallet/lock', {\n headers: {\n api_key: this.props.apiKey,\n },\n })\n\n submitWalletUnlockForm = (\n { pass },\n { setSubmitting, resetForm, setStatus },\n ) => {\n setStatus({ status: 'submitting' })\n this.walletUnlock(pass)\n .then(() => {\n resetForm({ pass: '' })\n customToast('success', 'Your wallet is unlocked successfully')\n this.props.dispatchSetIsWalletUnlocked(true)\n this.handleHide()\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n submitWalletLockForm = () => {\n // eslint-disable-next-line\n if (confirm('Are you sure want to lock wallet?')) {\n this.walletLock()\n .then(() => {\n customToast('success', 'Your wallet is locked successfully')\n this.props.dispatchSetIsWalletUnlocked(false)\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n })\n }\n }\n\n renderButton = () => {\n if (!this.props.isWalletUnlocked) {\n return (\n \n Unlock wallet\n \n )\n }\n\n return (\n \n Lock wallet\n \n )\n }\n\n render() {\n return (\n \n {this.renderButton()}\n
this.handleHide()}\n centered\n aria-labelledby=\"example-custom-modal-styling-title\"\n >\n \n {({ isSubmitting }) => (\n \n )}\n \n \n
\n )\n }\n}\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(WalletStatusForm))\n","import React from 'react'\nimport copy from 'clipboard-copy'\nimport { Overlay, Tooltip } from 'react-bootstrap'\nimport { faCopy } from '@fortawesome/free-solid-svg-icons'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\n\nclass CopyToClipboard extends React.PureComponent {\n constructor(props) {\n super(props)\n\n this.myRef = React.createRef()\n this.state = { showTooltip: false }\n }\n\n render() {\n return (\n <>\n \n {this.props.children}\n \n \n \n \n Copied! \n \n >\n )\n }\n\n startTimer = () => {\n const timerId = setTimeout(\n () => this.setState({ showTooltip: false }),\n 1500,\n )\n this.setState({ timerId })\n }\n\n onCopy = e => {\n e.preventDefault()\n copy(this.props.children)\n this.setState({ showTooltip: true })\n this.startTimer()\n }\n\n handleOnTooltipClose = () => {\n this.setState({ showTooltip: false })\n }\n\n componentWillUnmount() {\n clearTimeout(this.state.timerId)\n }\n}\n\nexport default CopyToClipboard\n","import React, { Component, memo } from 'react'\nimport { Formik, Field, Form } from 'formik'\nimport nodeApi from '../../../api/api'\nimport CopyToClipboard from '../../common/CopyToClipboard'\nimport customToast from '../../../utils/toast'\n\nconst initialFormValues = {\n walletPassword: '',\n mnemonicPass: '',\n}\n\nclass WalletInitializeForm extends Component {\n state = { isShowMnemonic: false }\n\n walletInit = async ({ walletPassword, mnemonicPass }) => {\n const { data } = await nodeApi.post(\n '/wallet/init',\n { pass: walletPassword, mnemonicPass },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n\n return data\n }\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.walletInit(values)\n .then(result => {\n resetForm(initialFormValues)\n setStatus({\n state: 'success',\n msg: (\n <>\n Your wallet successfully initialized. Please, save your mnemonic -{' '}\n {result.mnemonic} \n >\n ),\n })\n this.setState({ isShowMnemonic: true })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
Initialize wallet \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n )\n }\n}\n\nexport default memo(WalletInitializeForm)\n","import React, { Component, memo } from 'react'\nimport { Formik, Field, Form } from 'formik'\nimport nodeApi from '../../../api/api'\nimport customToast from '../../../utils/toast'\n\nconst initialFormValues = {\n walletPassword: '',\n mnemonicPass: '',\n mnemonic: '',\n}\n\nclass WalletInitializeForm extends Component {\n walletRestore = async ({\n walletPassword,\n mnemonicPass = '',\n mnemonic = '',\n }) => {\n if (!mnemonic || !String(mnemonic).trim()) {\n throw Error('Need to set mnemonic')\n }\n\n return nodeApi.post(\n '/wallet/restore',\n { pass: walletPassword, mnemonicPass, mnemonic },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n }\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.walletRestore(values)\n .then(() => {\n resetForm(initialFormValues)\n customToast('success', 'Your wallet successfully re-stored')\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
Re-store wallet \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n )\n }\n}\n\nexport default memo(WalletInitializeForm)\n","import React, { Component, memo } from 'react'\nimport Modal from 'react-bootstrap/Modal'\nimport { connect } from 'react-redux'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport appActions from '../../../store/actions/appActions'\nimport WalletInitializeForm from '../../elements/WalletInitializeForm'\nimport RestoreWalletForm from '../../elements/RestoreWalletForm'\nimport walletActions from '../../../store/actions/walletActions'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n dispatchSetApiKey: apiKey => dispatch(appActions.setApiKey(apiKey)),\n})\n\nclass WalletInitModal extends Component {\n state = {\n showModal: false,\n }\n\n handleShow = () => {\n this.setState({ showModal: true })\n }\n\n handleHide = () => {\n this.props.dispatchCheckWalletStatus()\n this.setState({ showModal: false })\n }\n\n renderButton = () => {\n return (\n \n Initialize wallet\n \n )\n }\n\n render() {\n const { apiKey } = this.props\n\n return (\n \n {this.renderButton()}\n
this.handleHide()}\n centered\n size=\"lg\"\n >\n \n \n Wallet initialization\n \n \n \n \n \n
\n \n \n
\n \n \n \n Close\n \n \n \n
\n )\n }\n}\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(WalletInitModal))\n","import React, { memo } from 'react'\nimport { Navbar } from 'react-bootstrap'\nimport { Link } from 'react-router-dom'\nimport ApiKeyModal from './ApiKeyModal'\nimport WalletStatusModal from './WalletStatusModal'\nimport WalletInitModal from './WalletInitModal'\nimport logo from '../../assets/images/logotype_white.svg'\n\nconst renderWalletForms = isWalletInitialized => {\n if (isWalletInitialized === null) {\n return <>>\n }\n\n if (isWalletInitialized) {\n return (\n \n \n
\n )\n }\n\n return (\n \n \n
\n )\n}\n\nconst HeaderView = ({ isApiKeySetted, isWalletInitialized }) => {\n return (\n \n \n \n \n \n \n \n {isApiKeySetted && renderWalletForms(isWalletInitialized)}\n \n )\n}\n\nexport default memo(HeaderView)\n","import HeaderContainer from './HeaderContainer'\n\nexport default HeaderContainer\n","import React, { memo, useEffect } from 'react'\nimport { connect } from 'react-redux'\nimport { apiKeySelector } from '../../store/selectors/app'\nimport { isWalletInitializedSelector } from '../../store/selectors/wallet'\nimport walletActions from '../../store/actions/walletActions'\nimport HeaderView from './HeaderView'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n})\n\nconst HeaderContainer = props => {\n const { apiKey, dispatchCheckWalletStatus, isWalletInitialized } = props\n\n useEffect(() => {\n if (apiKey !== '') {\n dispatchCheckWalletStatus()\n }\n }, [apiKey, dispatchCheckWalletStatus])\n\n const isApiKeySetted = apiKey !== ''\n\n return (\n \n )\n}\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(HeaderContainer))\n","import React, { Component } from 'react'\nimport { withRouter } from 'react-router-dom'\nimport MenuList from '../common/MenuList'\nimport './index.scss'\nimport Header from '../Header'\n\nclass Layout extends Component {\n render() {\n return (\n \n
\n
\n \n
\n
\n {this.props.children}
\n \n
\n )\n }\n}\n\nexport default withRouter(Layout)\n","import React from 'react'\nimport clsx from 'clsx'\nimport './index.scss'\n\nconst InfoCard = ({ color, children, className }) => {\n return (\n \n {children}\n
\n )\n}\n\nexport default InfoCard\n","import React, { Component } from 'react'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { faSync, faCheck } from '@fortawesome/free-solid-svg-icons'\nimport InfoCard from '../InfoCard'\nimport './index.scss'\n\nexport default class SynchCard extends Component {\n renderActiveSynchronization = () => (\n <>\n Current node state
\n \n Active\n synchronization\n
\n >\n )\n\n renderCompleteSynchronization = () => (\n <>\n Current node state
\n \n Node is synced\n
\n >\n )\n\n renderSynchronizationState = state =>\n ({\n active: this.renderActiveSynchronization,\n complete: this.renderCompleteSynchronization,\n }[state])\n\n getSynchronizationState = ({ fullHeight, headersHeight }) => {\n if (\n fullHeight !== null &&\n headersHeight !== null &&\n fullHeight === headersHeight\n ) {\n return 'complete'\n }\n\n return 'active'\n }\n\n shouldComponentUpdate(nextProps) {\n if (\n this.getSynchronizationState(nextProps) !==\n this.getSynchronizationState(this.props.nodeInfo)\n ) {\n return true\n }\n\n return false\n }\n\n render() {\n const currentSynchState = this.getSynchronizationState(this.props.nodeInfo)\n return (\n \n {this.renderSynchronizationState(currentSynchState)()}\n \n )\n }\n}\n","import React, { Fragment } from 'react'\nimport {\n faExclamationTriangle,\n faSync,\n} from '@fortawesome/free-solid-svg-icons'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { format } from 'date-fns'\nimport InfoCard from './InfoCard'\nimport SynchCard from './SynchCard'\n\nconst getWalletStatus = (isWalletInitialized, isWalletUnlocked) => {\n if (!isWalletInitialized) {\n return 'Not initialized'\n }\n\n if (!isWalletUnlocked) {\n return 'Initialized'\n }\n\n return 'Unlocked'\n}\n\nconst DashboardView = ({\n error,\n nodeInfo,\n isWalletInitialized,\n isWalletUnlocked,\n apiKey,\n}) => {\n if (error !== null) {\n return (\n \n \n
\n \n \n {error}\n \n \n \n )\n }\n\n if (nodeInfo === null) {\n return (\n \n \n \n
\n \n )\n }\n\n const {\n peersCount,\n bestHeaderId,\n launchTime,\n fullHeight,\n appVersion,\n isMining,\n } = nodeInfo\n\n return (\n \n \n
\n
\n
\n Node version
\n {appVersion}
\n \n
\n
\n \n
\n
\n
\n Node started at
\n \n {format(new Date(launchTime), 'MM-dd-yyyy HH:mm:ss')}\n
\n \n
\n {fullHeight === null ? null : (\n
\n
\n Current height
\n {fullHeight}
\n \n
\n )}\n {bestHeaderId === null ? null : (\n
\n
\n Best block id
\n {bestHeaderId}
\n \n
\n )}\n
\n
\n Mining enabled
\n {isMining ? 'true' : 'false'}
\n \n
\n
\n
\n Peers connected
\n {peersCount}
\n \n
\n {apiKey !== '' && (\n
\n
\n Wallet status
\n \n {getWalletStatus(isWalletInitialized, isWalletUnlocked)}\n
\n \n
\n )}\n
\n
\n \n )\n}\n\nexport default DashboardView\n","import { useEffect, useRef } from 'react'\n\nfunction usePrevious(value) {\n // The ref object is a generic container whose current property is mutable ...\n // ... and can hold any value, similar to an instance property on a class\n const ref = useRef()\n\n // Store current value in ref\n useEffect(() => {\n ref.current = value\n }, [value]) // Only re-run if value changes\n\n // Return previous value (happens before update in useEffect above)\n return ref.current\n}\n\nexport default usePrevious\n","import DashboardContainer from './DashboardContainer'\n\nexport default DashboardContainer\n","import React, { useState, useEffect, useCallback, memo } from 'react'\nimport { connect } from 'react-redux'\nimport nodeApi from '../../../api/api'\nimport DashboardView from './DashboardView'\nimport {\n isWalletInitializedSelector,\n isWalletUnlockedSelector,\n} from '../../../store/selectors/wallet'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport usePrevious from '../../../hooks/usePrevious'\nimport walletActions from '../../../store/actions/walletActions'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n isWalletUnlocked: isWalletUnlockedSelector(state),\n})\n\nconst mapDispatchToProps = dispatch => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n})\n\nconst DashboardContainer = props => {\n const {\n isWalletInitialized,\n isWalletUnlocked,\n apiKey,\n dispatchCheckWalletStatus,\n } = props\n\n const [nodeInfo, setNodeInfo] = useState(null)\n const [error, setError] = useState(null)\n const [timerId, setTimerId] = useState(null)\n\n const getNodeCurrentState = () => nodeApi.get('/info')\n\n const setNodeCurrentState = useCallback(async () => {\n try {\n const { data } = await getNodeCurrentState()\n\n setNodeInfo(data)\n setError(null)\n } catch {\n setError('Node connection is lost.')\n }\n }, [])\n\n const setTimer = useCallback(() => {\n const newTimerId = setInterval(setNodeCurrentState, 2000)\n\n setTimerId(newTimerId)\n }, [setNodeCurrentState])\n\n const prevError = usePrevious(error)\n useEffect(() => {\n if (prevError && prevError !== error) {\n dispatchCheckWalletStatus()\n }\n }, [dispatchCheckWalletStatus, error, prevError])\n\n useEffect(() => {\n setNodeCurrentState()\n setTimer()\n // eslint-disable-next-line\n }, [])\n\n useEffect(\n () => () => {\n clearInterval(timerId)\n },\n [timerId],\n )\n\n return (\n \n )\n}\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(memo(DashboardContainer))\n","import React, { PureComponent } from 'react'\nimport { Formik, Field, Form } from 'formik'\nimport nodeApi from '../../../../../api/api'\nimport customToast from '../../../../../utils/toast'\nimport CopyToClipboard from '../../../../common/CopyToClipboard'\nimport constants from '../../../../../utils/constants'\n\nconst initialFormValues = {\n recipientAddress: '',\n amount: '',\n}\n\nclass PaymentSendForm extends PureComponent {\n state = {\n isShowTransactionId: false,\n }\n\n paymentSend = ({ recipientAddress, amount }) =>\n nodeApi.post(\n '/wallet/payment/send',\n [\n {\n address: recipientAddress,\n value: Number(\n (parseFloat(amount) * constants.nanoErgInErg).toFixed(1),\n ),\n },\n ],\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n )\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.paymentSend(values)\n .then(({ data }) => {\n resetForm(initialFormValues)\n setStatus({\n state: 'success',\n msg: (\n <>\n \n Your payment has been sent successfully. The transaction ID is -{' '}\n {data} \n
\n \n \n Click Here To Go To The Explorer\n \n
\n >\n ),\n })\n this.setState({ isShowTransactionId: true })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
\n
Payment send \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n
\n )\n }\n}\n\nexport default PaymentSendForm\n","import React, { PureComponent } from 'react'\nimport { Formik, Form } from 'formik'\nimport NumberFormat from 'react-number-format'\nimport nodeApi from '../../../../../api/api'\nimport customToast from '../../../../../utils/toast'\n\nconst initialFormValues = {\n walletPassword: '',\n}\n\nclass GetBalanceForm extends PureComponent {\n state = {\n isShowBalance: false,\n }\n\n getBalance = () =>\n nodeApi.get('/wallet/balances', {\n headers: {\n api_key: this.props.apiKey,\n },\n })\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }) => {\n setStatus({ status: 'submitting' })\n this.getBalance(values)\n .then(({ data: { balance } }) => {\n resetForm(initialFormValues)\n setStatus({\n state: 'success',\n msg: (\n <>\n Your wallet balance -{' '}\n \n >\n ),\n })\n this.setState({ isShowBalance: true })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n setSubmitting(false)\n })\n }\n\n render() {\n return (\n \n
\n
Get confirmed wallet balance \n
\n {({ status, isSubmitting }) => (\n \n )}\n \n
\n
\n )\n }\n}\n\nexport default GetBalanceForm\n","import React, { PureComponent } from 'react'\nimport nodeApi from '../../../../../api/api'\nimport customToast from '../../../../../utils/toast'\nimport CopyToClipboard from '../../../../common/CopyToClipboard'\n\nclass GetWalletAddressesForm extends PureComponent {\n state = {\n isShowWalletAddresses: false,\n walletAddresses: [],\n }\n\n getWalletAddresses = () =>\n nodeApi.get('/wallet/addresses', {\n headers: {\n api_key: this.props.apiKey,\n },\n })\n\n handleSubmit = event => {\n event.preventDefault()\n\n this.getWalletAddresses()\n .then(({ data: walletAddresses }) => {\n this.setState({ isShowWalletAddresses: true, walletAddresses })\n })\n .catch(err => {\n const errMessage = err.data ? err.data.detail : err.message\n customToast('error', errMessage)\n })\n }\n\n render() {\n return (\n \n
\n
Get all wallet addresses \n
\n
\n
\n )\n }\n}\n\nexport default GetWalletAddressesForm\n","import React, { Component, memo } from 'react'\nimport { connect } from 'react-redux'\nimport PaymentSendForm from './components/PaymentSendForm'\nimport GetBalanceForm from './components/GetBalanceForm'\nimport GetWalletAddressesForm from './components/GetWalletAddressesForm'\nimport { apiKeySelector } from '../../../store/selectors/app'\nimport {\n isWalletInitializedSelector,\n isWalletUnlockedSelector,\n} from '../../../store/selectors/wallet'\n\nconst mapStateToProps = state => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n isWalletUnlocked: isWalletUnlockedSelector(state),\n})\n\nclass Wallet extends Component {\n renderState = state =>\n ({\n unlocked: apiKey => this.renderWalletUnlockedState(apiKey),\n locked: () => this.renderWalletLockedState(),\n initialized: apiKey => this.renderInitializedState(apiKey),\n }[state])\n\n renderWalletUnlockedState = apiKey => (\n \n )\n\n renderWalletLockedState = () => (\n \n
\n The wallet UI is locked. You need to unlock the wallet to access its UI.\n
\n
\n )\n\n renderInitializedState = () => (\n \n
You need to initialize your wallet to access wallet UI.
\n
\n )\n\n render() {\n const { apiKey, isWalletUnlocked, isWalletInitialized } = this.props\n\n if (apiKey === '') {\n return (\n \n
To continue, please set your API key.
\n
\n )\n }\n\n if (!isWalletInitialized) {\n return this.renderState('initialized')(apiKey)\n }\n\n if (isWalletUnlocked) {\n return this.renderState('unlocked')(apiKey)\n }\n\n return this.renderState('locked')()\n }\n}\n\nexport default connect(mapStateToProps)(memo(Wallet))\n","import React from 'react'\nimport { BrowserRouter, Switch, Route } from 'react-router-dom'\nimport Layout from '../components/layout'\nimport Dashboard from '../components/pages/Dashboard'\nimport Wallet from '../components/pages/Wallet'\n\nconst Router = () => (\n \n \n \n \n \n \n \n \n)\n\nexport default Router\n","import { combineReducers } from 'redux'\nimport appSlice from '../slices/appSlice'\nimport walletSlice from '../slices/walletSlice'\n\nexport default combineReducers({\n app: appSlice.reducer,\n wallet: walletSlice.reducer,\n})\n","import walletActions from '../actions/walletActions'\nimport nodeApi from '../../api/api'\nimport { apiKeySelector } from '../selectors/app'\n\nexport default store => next => action => {\n const { dispatch, getState } = store\n const apiKey = apiKeySelector(getState())\n\n switch (action.type) {\n case walletActions.checkWalletStatus.type:\n nodeApi\n .get('/wallet/status', {\n headers: {\n api_key: apiKey,\n },\n })\n .then(({ data: { isUnlocked, isInitialized } }) => {\n dispatch(walletActions.setIsWalletUnlocked(isUnlocked))\n dispatch(walletActions.setIsWalletInitialized(isInitialized))\n })\n .catch(() => {})\n\n break\n\n default:\n break\n }\n next(action)\n}\n","import React from 'react'\nimport { toast } from 'react-toastify'\nimport { Provider } from 'react-redux'\nimport Router from './router/router'\nimport createStore from './store'\n\nimport 'bootstrap/dist/css/bootstrap.min.css'\nimport './assets/styles/index.scss'\nimport 'react-toastify/dist/ReactToastify.min.css'\n\ntoast.configure()\nconst store = createStore()\n\nconst App = () => {\n return (\n \n \n \n )\n}\n\nexport default App\n","import { configureStore, getDefaultMiddleware } from 'redux-starter-kit'\nimport rootReducer from './reducers/rootReducer'\nimport walletMiddleware from './middlewares/walletMiddleware'\n\nexport default () => {\n const store = configureStore({\n reducer: rootReducer,\n middleware: [...getDefaultMiddleware(), walletMiddleware],\n })\n\n return store\n}\n","import React from 'react'\nimport ReactDOM from 'react-dom'\nimport App from './App'\n\nReactDOM.render( , document.getElementById('root'))\n"],"sourceRoot":""}
\ No newline at end of file
diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf
index 58fdf6953a..11cd130282 100644
--- a/src/main/resources/reference.conf
+++ b/src/main/resources/reference.conf
@@ -343,7 +343,7 @@ scorex {
deliveryTimeout = 10s
# Max number of delivery checks. Stop expecting modifier (and penalize peer) if it was not delivered on time
- maxDeliveryChecks = 40
+ maxDeliveryChecks = 2
############
# Timeouts #
diff --git a/src/main/scala/org/ergoplatform/BootstrapController.scala b/src/main/scala/org/ergoplatform/BootstrapController.scala
deleted file mode 100644
index 2031f47f92..0000000000
--- a/src/main/scala/org/ergoplatform/BootstrapController.scala
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.ergoplatform
-
-import akka.actor.ActorSystem
-import akka.http.scaladsl.Http
-import akka.http.scaladsl.model.{HttpMethods, HttpRequest}
-import akka.http.scaladsl.unmarshalling.Unmarshal
-import akka.stream.ActorMaterializer
-import akka.util.ByteString
-import io.circe.generic.auto._
-import io.circe.parser._
-import org.ergoplatform.BootstrapController.GenesisSettings
-import org.ergoplatform.settings.BootstrapSettings
-import scorex.util.ScorexLogging
-
-import scala.concurrent.duration._
-import scala.concurrent.{Await, ExecutionContext, Future}
-import scala.util.{Failure, Success, Try}
-
-final class BootstrapController(settings: BootstrapSettings)(implicit val as: ActorSystem)
- extends ScorexLogging {
-
- implicit val ec: ExecutionContext = as.getDispatcher
- implicit val mat: ActorMaterializer = ActorMaterializer()
-
- def waitForBootSettings(): (Seq[String], String) = {
- def tryFetchSettings: GenesisSettings = {
- Try(Await.result(getSettings(settings.resourceUri), atMost = 5.second)) match {
- case Success(Some(genesisSettings)) => genesisSettings
- case Success(_) =>
- log.info(s"Wrong response format, retrying in ${settings.pollDelay.toSeconds}s")
- Thread.sleep(settings.pollDelay.toMillis)
- tryFetchSettings
- case Failure(e) =>
- log.info(s"Failed to fetch genesis settings: ${e.getMessage}. Retrying in ${settings.pollDelay.toSeconds}s")
- Thread.sleep(settings.pollDelay.toMillis)
- tryFetchSettings
- }
- }
- tryFetchSettings.unapply
- }
-
- private def getSettings(uri: String): Future[Option[GenesisSettings]] =
- Http().singleRequest(HttpRequest(method = HttpMethods.GET, uri = uri))
- .flatMap { resp =>
- Unmarshal(resp.entity)
- .to[ByteString]
- .map(bs => decode[GenesisSettings](bs.utf8String).toOption)
- }
-
-}
-
-object BootstrapController {
-
- final case class GenesisSettings(noPremineProof: Seq[String],
- genesisStateDigestHex: String) {
- def unapply: (Seq[String], String) = noPremineProof -> genesisStateDigestHex
- }
-
-}
diff --git a/src/main/scala/org/ergoplatform/ErgoApp.scala b/src/main/scala/org/ergoplatform/ErgoApp.scala
index 159f1185db..a6a9b4378e 100644
--- a/src/main/scala/org/ergoplatform/ErgoApp.scala
+++ b/src/main/scala/org/ergoplatform/ErgoApp.scala
@@ -44,22 +44,6 @@ class ErgoApp(args: Args) extends ScorexLogging {
implicit private val actorSystem: ActorSystem = ActorSystem(settings.network.agentName)
implicit private val executionContext: ExecutionContext = actorSystem.dispatcher
- ergoSettings = ergoSettings.bootstrapSettingsOpt match {
- case Some(bs) if isEmptyState =>
- log.info("Entering coordinated network bootstrap procedure ..")
- val (npmProof, genesisDigest) =
- new BootstrapController(bs).waitForBootSettings()
- log.info("Boot settings received. Starting the node ..")
- ergoSettings.copy(
- chainSettings = ergoSettings.chainSettings.copy(
- noPremineProof = npmProof,
- genesisStateDigestHex = genesisDigest
- )
- )
- case _ =>
- ergoSettings
- }
-
private val features: Seq[PeerFeature] = Seq(ModeFeature(ergoSettings.nodeSettings))
private val timeProvider = new NetworkTimeProvider(settings.ntp)
diff --git a/src/main/scala/org/ergoplatform/http/api/EmissionApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/EmissionApiRoute.scala
index 7e2a849054..afece4c4d0 100644
--- a/src/main/scala/org/ergoplatform/http/api/EmissionApiRoute.scala
+++ b/src/main/scala/org/ergoplatform/http/api/EmissionApiRoute.scala
@@ -8,8 +8,8 @@ import org.ergoplatform.settings.ErgoSettings
import scorex.core.api.http.ApiResponse
import scorex.core.settings.RESTApiSettings
-final case class EmissionApiRoute(ergoSettings: ErgoSettings)
- (implicit val context: ActorRefFactory) extends ErgoBaseApiRoute {
+case class EmissionApiRoute(ergoSettings: ErgoSettings)
+ (implicit val context: ActorRefFactory) extends ErgoBaseApiRoute {
import EmissionApiRoute._
diff --git a/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala
index 734116b394..ed84177cc5 100644
--- a/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala
+++ b/src/main/scala/org/ergoplatform/http/api/ScanApiRoute.scala
@@ -38,7 +38,8 @@ case class ScanApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings)
deregisterR ~
listScansR ~
unspentR ~
- stopTrackingR
+ stopTrackingR ~
+ addBoxR
}
def registerR: Route = (path("register") & post & entity(as[ScanRequest])) { request =>
@@ -55,7 +56,6 @@ case class ScanApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings)
}
}
- //todo: paging?
def listScansR: Route = (path("listAll") & get) {
withWallet(_.readScans().map(_.apps))
}
@@ -74,4 +74,11 @@ case class ScanApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings)
}
}
+ def addBoxR: Route = (path("addBox") & post & entity(as[BoxWithScanIds])) { scanIdsBox =>
+ withWalletOp(_.addBox(scanIdsBox.box, scanIdsBox.scanIds).map(_.status)) {
+ case Failure(e) => BadRequest(s"Bad request ($scanIdsBox): ${Option(e.getMessage).getOrElse(e.toString)}")
+ case Success(_) => ApiResponse(scanIdsBox.box.id)
+ }
+ }
+
}
diff --git a/src/main/scala/org/ergoplatform/http/api/ScanEntities.scala b/src/main/scala/org/ergoplatform/http/api/ScanEntities.scala
index 382556b57d..312fcbd5fb 100644
--- a/src/main/scala/org/ergoplatform/http/api/ScanEntities.scala
+++ b/src/main/scala/org/ergoplatform/http/api/ScanEntities.scala
@@ -2,7 +2,7 @@ package org.ergoplatform.http.api
import io.circe.{Decoder, HCursor}
import org.ergoplatform.ErgoBox.BoxId
-import org.ergoplatform.JsonCodecs
+import org.ergoplatform.{ErgoBox, JsonCodecs}
import org.ergoplatform.wallet.Constants.ScanId
/**
@@ -32,17 +32,16 @@ object ScanEntities {
}
- case class ScanIdBox(scanId: ScanId, boxBytes: Array[Byte])
- object ScanIdBox extends JsonCodecs {
+ case class BoxWithScanIds(box: ErgoBox, scanIds: Set[ScanId])
- implicit val scanIdBoxDecoder: Decoder[ScanIdBox] = { c: HCursor =>
+ object BoxWithScanIds extends JsonCodecs {
+ implicit val scanIdsBoxDecoder: Decoder[BoxWithScanIds] = { c: HCursor =>
for {
- scanId <- ScanId @@ c.downField("scanId").as[Short]
- boxBytes <- c.downField("boxBytes").as[Array[Byte]]
- } yield ScanIdBox(scanId, boxBytes)
+ box <- c.downField("box").as[ErgoBox]
+ scanIds <- ScanId @@ c.downField("scanIds").as[Set[Short]]
+ } yield BoxWithScanIds(box, scanIds)
}
-
}
}
diff --git a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala
index 648392c1ca..766c860eff 100644
--- a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala
+++ b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala
@@ -12,6 +12,7 @@ import org.ergoplatform.nodeView.state.UtxoStateReader
import org.ergoplatform.nodeView.wallet._
import org.ergoplatform.nodeView.wallet.requests._
import org.ergoplatform.settings.ErgoSettings
+import org.ergoplatform.wallet.Constants
import scorex.core.NodeViewHolder.ReceivableMessages.LocallyGeneratedTransaction
import scorex.core.api.http.ApiError.{BadRequest, NotExists}
import scorex.core.api.http.ApiResponse
@@ -55,7 +56,8 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e
deriveNextKeyR ~
updateChangeAddressR ~
signTransactionR ~
- checkSeedR
+ checkSeedR ~
+ rescanWalletR
}
}
@@ -82,7 +84,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e
private val checkRequest: Directive1[(String, Option[String])] = entity(as[Json]).flatMap { p =>
p.hcursor.downField("mnemonic").as[String]
.flatMap(mnemo => p.hcursor.downField("mnemonicPass").as[Option[String]]
- .map(mnemoPassOpt => (mnemo, mnemoPassOpt)))
+ .map(mnemoPassOpt => (mnemo, mnemoPassOpt)))
.fold(_ => reject, s => provide(s))
}
@@ -136,19 +138,19 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e
private def sendTransaction(requests: Seq[TransactionGenerationRequest],
inputsRaw: Seq[String],
dataInputsRaw: Seq[String]): Route = {
- generateTransactionAndProcess(requests, inputsRaw, dataInputsRaw, {tx =>
+ generateTransactionAndProcess(requests, inputsRaw, dataInputsRaw, { tx =>
nodeViewActorRef ! LocallyGeneratedTransaction[ErgoTransaction](tx)
ApiResponse(tx.id)
})
}
def sendTransactionR: Route =
- (path("transaction" / "send") & post & entity(as[RequestsHolder])){ holder =>
+ (path("transaction" / "send") & post & entity(as[RequestsHolder])) { holder =>
sendTransaction(holder.withFee, holder.inputsRaw, holder.dataInputsRaw)
}
def generateTransactionR: Route =
- (path("transaction" / "generate") & post & entity(as[RequestsHolder])){ holder =>
+ (path("transaction" / "generate") & post & entity(as[RequestsHolder])) { holder =>
generateTransaction(holder.withFee, holder.inputsRaw, holder.dataInputsRaw)
}
@@ -249,6 +251,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e
_.transactions
.map {
_.filter(tx =>
+ tx.wtx.scanIds.exists(scanId => scanId <= Constants.PaymentsScanId) &&
tx.wtx.inclusionHeight >= minHeight && tx.wtx.inclusionHeight <= maxHeight &&
tx.numConfirmations >= minConfNum && tx.numConfirmations <= maxConfNum
)
@@ -293,8 +296,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e
def checkSeedR: Route = (path("check") & post & checkRequest) {
case (mnemo, mnemoPassOpt) =>
- withWalletOp(_.checkSeed(mnemo, mnemoPassOpt))
- { case matched =>
+ withWalletOp(_.checkSeed(mnemo, mnemoPassOpt)) { matched =>
ApiResponse(
Json.obj(
"matched" -> matched.asJson
@@ -340,4 +342,13 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e
}
}
+ def rescanWalletR: Route = (path("rescan") & get) {
+ withWalletOp(_.rescanWallet()) {
+ _.fold(
+ e => BadRequest(e.getMessage),
+ _ => ApiResponse.toRoute(ApiResponse.OK)
+ )
+ }
+ }
+
}
diff --git a/src/main/scala/org/ergoplatform/local/CleanupWorker.scala b/src/main/scala/org/ergoplatform/local/CleanupWorker.scala
index 912631bbb3..2fc02886e5 100644
--- a/src/main/scala/org/ergoplatform/local/CleanupWorker.scala
+++ b/src/main/scala/org/ergoplatform/local/CleanupWorker.scala
@@ -102,7 +102,7 @@ class CleanupWorker(nodeViewHolderRef: ActorRef,
val (validatedIds, invalidatedIds) = validationLoop(txsToValidate, Seq.empty, Seq.empty, 0L)
epochNr += 1
- if (epochNr % CleanupWorker.IndexRevisionInterval == 0) {
+ if (epochNr % CleanupWorker.RevisionInterval == 0) {
// drop old index in order to check potentially outdated transactions again.
validatedIndex = TreeSet(validatedIds: _*)
} else {
@@ -116,9 +116,22 @@ class CleanupWorker(nodeViewHolderRef: ActorRef,
object CleanupWorker {
- case class RunCleanup(validator: TransactionValidation[ErgoTransaction],
- mempool: ErgoMemPoolReader)
+ /**
+ * Constant which shows on how many cleanup operations (called when a new block arrives) a transaction
+ * re-check happens.
+ *
+ * If transactions set is large and stable, then about (1/RevisionInterval)-th of the pool is checked
+ *
+ */
+ val RevisionInterval: Int = 4
- val IndexRevisionInterval: Int = 16
+ /**
+ *
+ * A command to run (partial) memory pool cleanup
+ *
+ * @param validator - a state implementation which provides transaction validation
+ * @param mempool - mempool reader instance
+ */
+ case class RunCleanup(validator: TransactionValidation[ErgoTransaction], mempool: ErgoMemPoolReader)
}
diff --git a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala
index 1f77aa7fd6..ba1ea29f4b 100644
--- a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala
+++ b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala
@@ -8,7 +8,6 @@ import org.ergoplatform.modifiers.ErgoFullBlock
import org.ergoplatform.modifiers.history.Header
import org.ergoplatform.modifiers.mempool.ErgoTransaction
import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader
-import org.ergoplatform.nodeView.state.UtxoState
import org.ergoplatform.settings.ErgoSettings
import scorex.core.NodeViewHolder.ReceivableMessages.GetNodeViewChanges
import scorex.core.network.Broadcast
@@ -103,31 +102,20 @@ class MempoolAuditor(nodeViewHolderRef: ActorRef,
private def rebroadcastTransactions(): Unit = {
log.debug("Rebroadcasting transactions")
- stateReaderOpt.foreach { st =>
- poolReaderOpt.foreach { pr =>
- pr.take(settings.nodeSettings.rebroadcastCount).foreach { tx =>
- st match {
- case utxo: UtxoState =>
- //todo: currently we're rebroadcasting transactions which are spending on-chain outputs only
- //todo: this is to be changed when most of the nodes on the network will support transactions spending
- //todo: offchain outputs in the mempool (versions 3.2.1 and further)
- if (tx.inputs.forall(i => utxo.boxById(i.boxId).isDefined)) {
- log.info(s"Rebroadcasting $tx")
- val msg = Message(
- new InvSpec(settings.scorexSettings.network.maxInvObjects),
- Right(InvData(Transaction.ModifierTypeId, Seq(tx.id))),
- None
- )
- networkControllerRef ! SendToNetwork(msg, Broadcast)
- } else {
- log.info(s"Not all the inputs of $tx is in UTXO set")
- }
- }
- }
+ poolReaderOpt.foreach { pr =>
+ pr.take(settings.nodeSettings.rebroadcastCount).foreach { tx =>
+ log.info(s"Rebroadcasting $tx")
+ val msg = Message(
+ new InvSpec(settings.scorexSettings.network.maxInvObjects),
+ Right(InvData(Transaction.ModifierTypeId, Seq(tx.id))),
+ None
+ )
+ networkControllerRef ! SendToNetwork(msg, Broadcast)
}
+
}
- }
+ }
}
object MempoolAuditor {
diff --git a/src/main/scala/org/ergoplatform/modifiers/ErgoPersistentModifier.scala b/src/main/scala/org/ergoplatform/modifiers/ErgoPersistentModifier.scala
index 2933232af0..62d16d5440 100644
--- a/src/main/scala/org/ergoplatform/modifiers/ErgoPersistentModifier.scala
+++ b/src/main/scala/org/ergoplatform/modifiers/ErgoPersistentModifier.scala
@@ -7,6 +7,7 @@ import scorex.core.PersistentNodeViewModifier
trait ErgoPersistentModifier extends PersistentNodeViewModifier with ErgoNodeViewModifier
object ErgoPersistentModifier {
+
implicit val jsonEncoder: Encoder[ErgoPersistentModifier] = {
case h: Header => Header.jsonEncoder(h)
case bt: BlockTransactions => BlockTransactions.jsonEncoder(bt)
@@ -14,4 +15,5 @@ object ErgoPersistentModifier {
case ext: Extension => Extension.jsonEncoder(ext)
case other => throw new Exception(s"Unknown persistent modifier type: $other")
}
+
}
diff --git a/src/main/scala/org/ergoplatform/modifiers/history/HeaderChain.scala b/src/main/scala/org/ergoplatform/modifiers/history/HeaderChain.scala
index af8488673f..365863e11c 100644
--- a/src/main/scala/org/ergoplatform/modifiers/history/HeaderChain.scala
+++ b/src/main/scala/org/ergoplatform/modifiers/history/HeaderChain.scala
@@ -1,6 +1,7 @@
package org.ergoplatform.modifiers.history
case class HeaderChain(headers: IndexedSeq[Header]) {
+
headers.indices.foreach { i =>
if (i > 0) require(headers(i).parentId == headers(i - 1).id,
s"Incorrect chain: ${headers(i - 1)},${headers(i)}")
@@ -38,6 +39,7 @@ case class HeaderChain(headers: IndexedSeq[Header]) {
}
object HeaderChain {
+
lazy val empty = HeaderChain(IndexedSeq.empty[Header])
def apply(seq: Seq[Header]): HeaderChain = HeaderChain(seq.toIndexedSeq)
diff --git a/src/main/scala/org/ergoplatform/modifiers/history/PoPoWProof.scala b/src/main/scala/org/ergoplatform/modifiers/history/PoPoWProof.scala
index eb7e7106fe..2f719031b1 100644
--- a/src/main/scala/org/ergoplatform/modifiers/history/PoPoWProof.scala
+++ b/src/main/scala/org/ergoplatform/modifiers/history/PoPoWProof.scala
@@ -5,9 +5,6 @@ import org.ergoplatform.modifiers.ErgoPersistentModifier
import org.ergoplatform.settings.Algos
import scorex.core.ModifierTypeId
import scorex.core.serialization.ScorexSerializer
-import scorex.core.validation.ModifierValidator
-import scorex.core.utils.ScorexEncoding
-import scorex.util.serialization.{Reader, Writer}
import scorex.util.{ModifierId, bytesToId}
case class PoPoWProof(m: Byte,
@@ -37,4 +34,4 @@ case class PoPoWProof(m: Byte,
object PoPoWProof {
val modifierTypeId: ModifierTypeId = ModifierTypeId @@ (105: Byte)
-}
\ No newline at end of file
+}
diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala
index e98bb6b3d8..8b488bc7dc 100644
--- a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala
+++ b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala
@@ -13,6 +13,7 @@ import org.ergoplatform.settings.{Algos, ErgoValidationSettings}
import org.ergoplatform.utils.BoxUtils
import org.ergoplatform.wallet.interpreter.ErgoInterpreter
import org.ergoplatform.wallet.protocol.context.TransactionContext
+import scorex.core.EphemerealNodeViewModifier
import scorex.core.serialization.ScorexSerializer
import scorex.core.transaction.Transaction
import scorex.core.utils.ScorexEncoding
@@ -53,7 +54,7 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],
override val sizeOpt: Option[Int] = None)
extends ErgoLikeTransaction(inputs, dataInputs, outputCandidates)
with Transaction
- with MempoolModifier
+ with EphemerealNodeViewModifier
with ErgoNodeViewModifier
with ScorexLogging {
diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/MempoolModifier.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/MempoolModifier.scala
deleted file mode 100644
index 406bf26c9e..0000000000
--- a/src/main/scala/org/ergoplatform/modifiers/mempool/MempoolModifier.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.ergoplatform.modifiers.mempool
-
-import scorex.core.EphemerealNodeViewModifier
-
-trait MempoolModifier extends EphemerealNodeViewModifier
diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala
index 67a9c3cb18..5e93413098 100644
--- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala
+++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala
@@ -10,10 +10,12 @@ import org.ergoplatform.settings.Constants
import scorex.core.NodeViewHolder._
import scorex.core.{ModifierTypeId, PersistentNodeViewModifier}
import scorex.core.network.NetworkController.ReceivableMessages.SendToNetwork
-import scorex.core.network.NodeViewSynchronizer.ReceivableMessages.{CheckDelivery, SemanticallySuccessfulModifier}
-import scorex.core.network.message.{InvData, Message}
+import scorex.core.network.NetworkControllerSharedMessages.ReceivableMessages.DataFromPeer
+import scorex.core.network.NodeViewSynchronizer.ReceivableMessages.SemanticallySuccessfulModifier
+import scorex.core.network.message.{InvData, InvSpec, Message}
import scorex.core.network.{ModifiersStatus, NodeViewSynchronizer, SendToRandom}
import scorex.core.settings.NetworkSettings
+import scorex.core.transaction.Transaction
import scorex.core.utils.NetworkTimeProvider
import scorex.util.ModifierId
@@ -30,8 +32,8 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
ErgoHistory, ErgoMemPool](networkControllerRef, viewHolderRef, syncInfoSpec, networkSettings, timeProvider,
Constants.modifierSerializers) {
- override protected val deliveryTracker = new ErgoDeliveryTracker(context.system, deliveryTimeout, maxDeliveryChecks,
- self, timeProvider)
+ override protected val deliveryTracker =
+ new ErgoDeliveryTracker(context.system, deliveryTimeout, maxDeliveryChecks, self, timeProvider)
/**
* Approximate number of modifiers to be downloaded simultaneously
@@ -45,26 +47,6 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
context.system.scheduler.schedule(toDownloadCheckInterval, toDownloadCheckInterval)(self ! CheckModifiersToDownload)
}
- //todo: pull back to Scorex with fixing this method
- override protected def checkDelivery: Receive = {
- case CheckDelivery(peerOpt, modifierTypeId, modifierId) =>
- if (deliveryTracker.status(modifierId) == ModifiersStatus.Requested) {
- peerOpt match {
- case Some(peer) =>
- log.info(s"Peer ${peer.toString} has not delivered asked modifier ${encoder.encodeId(modifierId)} on time")
- penalizeNonDeliveringPeer(peer)
- deliveryTracker.setUnknown(modifierId)
- requestDownload(modifierTypeId, Seq(modifierId))
- case None =>
- // Random peer did not delivered modifier we need, ask another peer
- // We need this modifier - no limit for number of attempts
- log.info(s"Modifier ${encoder.encodeId(modifierId)} (type $modifierTypeId) was not delivered on time")
- deliveryTracker.setUnknown(modifierId)
- requestDownload(modifierTypeId, Seq(modifierId))
- }
- }
- }
-
/**
* Requests BlockSections with `Unknown` status that are defined by block headers but not downloaded yet.
* Trying to keep size of requested queue equals to `desiredSizeOfExpectingQueue`.
@@ -77,18 +59,57 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
val toDownload =
h.nextModifiersToDownload(desiredSizeOfExpectingQueue - deliveryTracker.requestedSize, downloadRequired)
- log.info(s"${toDownload.length} modifiers to be downloaded")
+ log.info(s"${toDownload.length} persistent modifiers to be downloaded")
toDownload.groupBy(_._1).foreach(ids => requestDownload(ids._1, ids._2.map(_._2)))
}
}
+ // todo: this method is just a copy of the ancestor from Scorex, however, smarter logic is needed, not just
+ // asking from a random peer
override protected def requestDownload(modifierTypeId: ModifierTypeId, modifierIds: Seq[ModifierId]): Unit = {
deliveryTracker.setRequested(modifierIds, modifierTypeId, None)
val msg = Message(requestModifierSpec, Right(InvData(modifierTypeId, modifierIds)), None)
networkControllerRef ! SendToNetwork(msg, SendToRandom)
}
+ /**
+ * Object ids coming from other node.
+ * Filter out modifier ids that are already in process (requested, received or applied),
+ * request unknown ids from peer and set this ids to requested state.
+ */
+ override protected def processInv: Receive = {
+ case DataFromPeer(spec, invData: InvData@unchecked, peer)
+ if spec.messageCode == InvSpec.MessageCode =>
+
+ (mempoolReaderOpt, historyReaderOpt) match {
+ case (Some(mempool), Some(history)) =>
+
+ val modifierTypeId = invData.typeId
+
+ val newModifierIds = modifierTypeId match {
+ case Transaction.ModifierTypeId =>
+ // We download transactions only if the chain is synced
+ if (history.isHeadersChainSynced && history.fullBlockHeight == history.headersHeight) {
+ invData.ids.filter(mid => deliveryTracker.status(mid, mempool) == ModifiersStatus.Unknown)
+ } else {
+ Seq.empty
+ }
+ case _ =>
+ invData.ids.filter(mid => deliveryTracker.status(mid, history) == ModifiersStatus.Unknown)
+ }
+
+ if (newModifierIds.nonEmpty) {
+ val msg = Message(requestModifierSpec, Right(InvData(modifierTypeId, newModifierIds)), None)
+ peer.handlerRef ! msg
+ deliveryTracker.setRequested(newModifierIds, modifierTypeId, Some(peer))
+ }
+
+ case _ =>
+ log.warn(s"Got data from peer while readers are not ready ${(mempoolReaderOpt, historyReaderOpt)}")
+ }
+ }
+
/**
* If our requested list is more than half empty, enforce to request more:
* - headers, if our headers chain is not synced yet (by sending sync message)
@@ -131,6 +152,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
}
object ErgoNodeViewSynchronizer {
+
def props(networkControllerRef: ActorRef,
viewHolderRef: ActorRef,
syncInfoSpec: ErgoSyncInfoMessageSpec.type,
@@ -148,16 +170,6 @@ object ErgoNodeViewSynchronizer {
(implicit context: ActorRefFactory, ex: ExecutionContext): ActorRef =
context.actorOf(props(networkControllerRef, viewHolderRef, syncInfoSpec, networkSettings, timeProvider))
- def apply(networkControllerRef: ActorRef,
- viewHolderRef: ActorRef,
- syncInfoSpec: ErgoSyncInfoMessageSpec.type,
- networkSettings: NetworkSettings,
- timeProvider: NetworkTimeProvider,
- name: String)
- (implicit context: ActorRefFactory, ex: ExecutionContext): ActorRef =
- context.actorOf(props(networkControllerRef, viewHolderRef, syncInfoSpec, networkSettings, timeProvider), name)
-
-
case object CheckModifiersToDownload
}
diff --git a/src/main/scala/org/ergoplatform/network/ToDownloadStatus.scala b/src/main/scala/org/ergoplatform/network/ToDownloadStatus.scala
deleted file mode 100644
index a81168f26c..0000000000
--- a/src/main/scala/org/ergoplatform/network/ToDownloadStatus.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.ergoplatform.network
-
-import scorex.core.ModifierTypeId
-
-case class ToDownloadStatus(tp: ModifierTypeId, firstViewed: Long, lastTry: Long)
diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ExtensionValidator.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ExtensionValidator.scala
index 759ec67f55..d47781a1ab 100644
--- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ExtensionValidator.scala
+++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ExtensionValidator.scala
@@ -2,10 +2,9 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors
import org.ergoplatform.modifiers.history.PoPowAlgos._
import org.ergoplatform.modifiers.history.{Extension, ExtensionCandidate, Header}
-import org.ergoplatform.settings.ErgoValidationSettings
import org.ergoplatform.settings.ValidationRules._
import scorex.core.utils.ScorexEncoding
-import scorex.core.validation.{ModifierValidator, ValidationState}
+import scorex.core.validation.ValidationState
import scorex.util.bytesToId
/**
diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ModifierProcessorEnvironment.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ModifierProcessorEnvironment.scala
deleted file mode 100644
index 3fe6591e42..0000000000
--- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/ModifierProcessorEnvironment.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package org.ergoplatform.nodeView.history.storage.modifierprocessors
-
-case class ModifierProcessorEnvironment(requiredDifficulty: BigInt)
diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala
index b39b3246ee..d2337557be 100644
--- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala
+++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala
@@ -63,25 +63,31 @@ class ErgoMemPool private[mempool](pool: OrderedTxPool)(implicit settings: ErgoS
def process(tx: ErgoTransaction, state: ErgoState[_]): (ErgoMemPool, ProcessingOutcome) = {
val fee = extractFee(tx)
val minFee = settings.nodeSettings.minimalFeeAmount
+ val canAccept = pool.canAccept(tx)
+
if (fee >= minFee) {
- state match {
- case utxo: UtxoState if pool.canAccept(tx) =>
- // Allow proceeded transaction to spend outputs of pooled transactions.
- utxo.withTransactions(getAll).validate(tx).fold(
- new ErgoMemPool(pool.invalidate(tx)) -> ProcessingOutcome.Invalidated(_),
- _ => new ErgoMemPool(pool.put(tx)) -> ProcessingOutcome.Accepted
- )
-
- case validator: TransactionValidation[ErgoTransaction@unchecked] if pool.canAccept(tx) =>
- // transaction validation currently works only for UtxoState, so this branch currently
- // will not be triggered probably
- validator.validate(tx).fold(
- new ErgoMemPool(pool.invalidate(tx)) -> ProcessingOutcome.Invalidated(_),
- _ => new ErgoMemPool(pool.put(tx)) -> ProcessingOutcome.Accepted
- )
- case _ =>
- this -> ProcessingOutcome.Declined(
- new Exception("Transaction validation not supported"))
+ if (canAccept) {
+ state match {
+ case utxo: UtxoState =>
+ // Allow proceeded transaction to spend outputs of pooled transactions.
+ utxo.withTransactions(getAll).validate(tx).fold(
+ new ErgoMemPool(pool.invalidate(tx)) -> ProcessingOutcome.Invalidated(_),
+ _ => new ErgoMemPool(pool.put(tx)) -> ProcessingOutcome.Accepted
+ )
+ case validator: TransactionValidation[ErgoTransaction@unchecked] =>
+ // transaction validation currently works only for UtxoState, so this branch currently
+ // will not be triggered probably
+ validator.validate(tx).fold(
+ new ErgoMemPool(pool.invalidate(tx)) -> ProcessingOutcome.Invalidated(_),
+ _ => new ErgoMemPool(pool.put(tx)) -> ProcessingOutcome.Accepted
+ )
+ case _ =>
+ this -> ProcessingOutcome.Declined(
+ new Exception("Transaction validation not supported"))
+ }
+ } else {
+ this -> ProcessingOutcome.Declined(
+ new Exception(s"Pool can not accept transaction ${tx.id}, it is invalidated earlier or pool is full"))
}
} else {
this -> ProcessingOutcome.Declined(
diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala
index 4ad7bfd319..1ebb6cbfc2 100644
--- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala
+++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala
@@ -8,6 +8,7 @@ import org.ergoplatform.ErgoBox._
import org.ergoplatform._
import org.ergoplatform.modifiers.ErgoFullBlock
import org.ergoplatform.modifiers.mempool.{ErgoBoxSerializer, ErgoTransaction, UnsignedErgoTransaction}
+import org.ergoplatform.nodeView.history.ErgoHistory.Height
import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader}
import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader
import org.ergoplatform.nodeView.state.{ErgoStateContext, ErgoStateReader, UtxoStateReader}
@@ -15,7 +16,7 @@ import org.ergoplatform.nodeView.wallet.persistence._
import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, ExternalSecret, PaymentRequest, TransactionGenerationRequest}
import org.ergoplatform.nodeView.wallet.scanning.{Scan, ScanRequest}
import org.ergoplatform.settings._
-import org.ergoplatform.utils.BoxUtils
+import org.ergoplatform.utils.{BoxUtils, FileUtils}
import org.ergoplatform.wallet.Constants.{PaymentsScanId, ScanId}
import org.ergoplatform.wallet.TokensMap
import org.ergoplatform.wallet.boxes.{BoxSelector, ChainStatus, TrackedBox}
@@ -29,7 +30,7 @@ import scorex.core.utils.ScorexEncoding
import scorex.crypto.hash.Digest32
import scorex.util.encode.Base16
import scorex.util.{ModifierId, ScorexLogging, idToBytes}
-import sigmastate.Values.{ByteArrayConstant, IntConstant}
+import sigmastate.Values.ByteArrayConstant
import sigmastate.eval.Extensions._
import sigmastate.eval._
@@ -56,7 +57,6 @@ class ErgoWalletActor(settings: ErgoSettings,
private var offChainRegistry: OffChainRegistry = OffChainRegistry.init(registry)
private var walletVars = WalletVars.apply(storage, settings)
-
//todo: temporary 3.2.x collection and readers
private var stateReaderOpt: Option[ErgoStateReader] = None
private var mempoolReaderOpt: Option[ErgoMemPoolReader] = None
@@ -120,6 +120,17 @@ class ErgoWalletActor(settings: ErgoSettings,
offChainRegistry = offReg
}
+ // expected height of a next block when the wallet is receiving a new block with the height blockHeight
+ private def expectedHeight(blockHeight: Height): Height = {
+ if (!settings.nodeSettings.isFullBlocksPruned) {
+ // Node has all the full blocks and applies them sequentially
+ walletHeight() + 1
+ } else {
+ // Node has pruned blockchain
+ if (walletHeight() == 0) blockHeight else walletHeight() + 1
+ }
+ }
+
private def scanLogic: Receive = {
//scan mempool transaction
case ScanOffChain(tx) =>
@@ -128,8 +139,7 @@ class ErgoWalletActor(settings: ErgoSettings,
offChainRegistry = offChainRegistry.updateOnTransaction(newWalletBoxes, inputs)
case ScanInThePast(blockHeight) =>
- val expectedHeight = walletHeight() + 1
- if (expectedHeight == blockHeight) {
+ if (expectedHeight(blockHeight) == blockHeight) {
historyReader.bestFullBlockAt(blockHeight) match {
case Some(block) =>
scanBlock(block)
@@ -143,12 +153,12 @@ class ErgoWalletActor(settings: ErgoSettings,
//scan block transactions
case ScanOnChain(block) =>
- val expectedHeight = walletHeight() + 1
- if (expectedHeight == block.height) {
+ val expHeight = expectedHeight(block.height)
+ if (expHeight == block.height) {
scanBlock(block)
- } else if (expectedHeight < block.height) {
- log.warn(s"Wallet: skipped blocks found starting from $expectedHeight, going back to scan them")
- self ! ScanInThePast(expectedHeight)
+ } else if (expHeight < block.height) {
+ log.warn(s"Wallet: skipped blocks found starting from $expHeight, going back to scan them")
+ self ! ScanInThePast(expHeight)
} else {
log.warn(s"Wallet: block in the past reported at ${block.height}, blockId: ${block.id}")
}
@@ -322,6 +332,22 @@ class ErgoWalletActor(settings: ErgoSettings,
walletVars = walletVars.resetProver()
secretStorageOpt.foreach(_.lock())
+ case RescanWallet =>
+ // We do wallet rescan by closing the wallet's database, deleting it from the disk,
+ // then reopening it and sending a rescan signal.
+ val rescanResult = Try {
+ val registryFolder = WalletRegistry.registryFolder(settings)
+ log.info(s"Rescanning the wallet, the registry is in $registryFolder")
+ registry.close()
+ FileUtils.deleteRecursive(registryFolder)
+ registry = WalletRegistry.apply(settings)
+ self ! ScanInThePast(walletHeight()) // walletHeight() corresponds to empty wallet state now
+ }
+ rescanResult.recover { case t =>
+ log.error("Error during rescan attempt: ", t)
+ }
+ sender() ! rescanResult
+
case GetWalletStatus =>
val status = WalletStatus(secretIsSet, walletVars.proverOpt.isDefined, changeAddress, walletHeight())
sender() ! status
@@ -379,6 +405,10 @@ class ErgoWalletActor(settings: ErgoSettings,
res.foreach(app => walletVars = walletVars.addScan(app))
sender() ! AddScanResponse(res)
+ case AddBox(box: ErgoBox, scanIds: Set[ScanId]) =>
+ registry.updateScans(scanIds, box)
+ sender() ! AddBoxResponse(Success(()))
+
case StopTracking(scanId: ScanId, boxId: BoxId) =>
sender() ! StopTrackingResponse(registry.removeScan(boxId, scanId))
}
@@ -441,6 +471,12 @@ class ErgoWalletActor(settings: ErgoSettings,
*/
private val noFilter: FilterFn = (_: TrackedBox) => true
+ /**
+ * Convert requests (to make payments or to issue an asset) to transaction outputs
+ * There can be only one asset issuance request in the input sequence.
+ * @param requests - an input sequence of requests
+ * @return sequence of transaction outputs or failure if inputs are incorrect
+ */
private def requestsToBoxCandidates(requests: Seq[TransactionGenerationRequest]): Try[Seq[ErgoBoxCandidate]] =
Traverse[List].sequence {
requests.toList
@@ -467,7 +503,7 @@ class ErgoWalletActor(settings: ErgoSettings,
val nonMandatoryRegisters = scala.Predef.Map(
R4 -> ByteArrayConstant(name.getBytes("UTF-8")),
R5 -> ByteArrayConstant(description.getBytes("UTF-8")),
- R6 -> IntConstant(decimals)
+ R6 -> ByteArrayConstant(String.valueOf(decimals).getBytes("UTF-8"))
) ++ registers.getOrElse(Map())
(addressOpt orElse walletVars.publicKeyAddresses.headOption)
.fold[Try[ErgoAddress]](Failure(new Exception("No address available for box locking")))(Success(_))
@@ -608,6 +644,10 @@ class ErgoWalletActor(settings: ErgoSettings,
private def processUnlock(secretStorage: JsonSecretStorage): Unit = Try {
val rootSecretSeq = secretStorage.secret.toSeq
+ if (rootSecretSeq.isEmpty) {
+ log.warn("Master key is not available after unlock")
+ }
+
// first, we're trying to find in the database paths written by clients prior 3.3.0 and convert them
// into a new format (pubkeys with paths stored instead of paths)
val oldPaths = storage.readPaths()
@@ -619,11 +659,13 @@ class ErgoWalletActor(settings: ErgoSettings,
oldPubKeys.foreach(storage.addKey)
storage.removePaths()
}
- val pubKeys = storage.readAllKeys().toIndexedSeq
+ var pubKeys = storage.readAllKeys().toIndexedSeq
//If no public keys in the database yet, add master's public key into it
if (pubKeys.isEmpty) {
- rootSecretSeq.foreach(s => storage.addKey(s.publicKey))
+ val masterPubKey = rootSecretSeq.map(s => s.publicKey)
+ masterPubKey.foreach(pk => storage.addKey(pk))
+ pubKeys = masterPubKey.toIndexedSeq
}
val secrets = pubKeys.flatMap { pk =>
@@ -833,6 +875,11 @@ object ErgoWalletActor {
*/
case object LockWallet
+ /**
+ * Rescan wallet
+ */
+ case object RescanWallet
+
/**
* Get wallet status
*/
@@ -882,6 +929,23 @@ object ErgoWalletActor {
*/
case class StopTrackingResponse(status: Try[Unit])
+
+ /**
+ * Add association between a scan and a box (and add the box to the database if it is not there)
+ *
+ * @param box
+ * @param scanIds
+ *
+ */
+ case class AddBox(box: ErgoBox, scanIds: Set[ScanId])
+
+ /**
+ * Wrapper for a result of AddBox processing
+ *
+ * @param status
+ */
+ case class AddBoxResponse(status: Try[Unit])
+
def signTransaction(proverOpt: Option[ErgoProvingInterpreter],
secrets: Seq[ExternalSecret],
tx: UnsignedErgoTransaction,
diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala
index 4db4d395e4..61a64b6d7e 100644
--- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala
+++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala
@@ -46,6 +46,8 @@ trait ErgoWalletReader extends VaultReader {
def lockWallet(): Unit = walletActor ! LockWallet
+ def rescanWallet(): Future[Try[Unit]] = (walletActor ? RescanWallet).mapTo[Try[Unit]]
+
def getWalletStatus: Future[WalletStatus] =
(walletActor ? GetWalletStatus).mapTo[WalletStatus]
@@ -108,4 +110,7 @@ trait ErgoWalletReader extends VaultReader {
def stopTracking(scanId: ScanId, boxId: BoxId): Future[StopTrackingResponse] =
(walletActor ? StopTracking(scanId, boxId)).mapTo[StopTrackingResponse]
+ def addBox(box: ErgoBox, scanIds: Set[ScanId]): Future[AddBoxResponse] =
+ (walletActor ? AddBox(box, scanIds)).mapTo[AddBoxResponse]
+
}
diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala
index 353b2a9d2e..52146d5718 100644
--- a/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala
+++ b/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala
@@ -1,6 +1,7 @@
package org.ergoplatform.nodeView.wallet.persistence
import java.io.File
+
import org.ergoplatform.ErgoBox.BoxId
import org.ergoplatform.db.HybridLDBKVStore
import org.ergoplatform.modifiers.history.PreGenesisHeader
@@ -12,8 +13,10 @@ import org.ergoplatform.wallet.boxes.{TrackedBox, TrackedBoxSerializer}
import scorex.core.VersionTag
import scorex.crypto.authds.ADKey
import scorex.util.{ModifierId, ScorexLogging, idToBytes}
-import Constants.{ScanId, PaymentsScanId}
+import Constants.{PaymentsScanId, ScanId}
+import org.ergoplatform.ErgoBox
import scorex.db.LDBVersionedStore
+
import scala.util.{Failure, Success, Try}
import org.ergoplatform.nodeView.wallet.IdUtils.encodedTokenId
@@ -31,6 +34,13 @@ class WalletRegistry(store: HybridLDBKVStore)(ws: WalletSettings) extends Scorex
private val keepHistory = ws.keepSpentBoxes
+ /**
+ * Close wallet registry storage
+ */
+ def close(): Unit = {
+ store.close()
+ }
+
/**
* Read wallet-related box with metadata
*
@@ -268,6 +278,27 @@ class WalletRegistry(store: HybridLDBKVStore)(ws: WalletSettings) extends Scorex
}
}
+ /**
+ * Updates scans of a box stored in the wallet database,
+ * puts the box into the database if it is not there
+ *
+ * @param scanIds
+ * @param box
+ * @return
+ */
+ def updateScans(scanIds: Set[ScanId], box: ErgoBox): Try[Unit] = Try {
+ val bag0 = KeyValuePairsBag(toInsert = Seq.empty, toRemove = Seq.empty)
+ val (updTb, bag1) = getBox(box.id) match {
+ case Some(tb) =>
+ (tb.copy(scans = scanIds), removeBox(bag0, tb))
+ case None =>
+ (TrackedBox(box, box.creationHeight, scanIds), bag0)
+ }
+ val bag2 = putBox(bag1, updTb)
+ store.nonVersionedRemove(bag2.toRemove)
+ store.nonVersionedPut(bag2.toInsert)
+ }
+
/**
* Remove association between an application and a box.
* Please note that in case of rollback association remains removed!
@@ -310,8 +341,10 @@ object WalletRegistry {
val PreGenesisStateVersion: Array[Byte] = idToBytes(PreGenesisHeader.id)
+ def registryFolder(settings: ErgoSettings): File = new File(s"${settings.directory}/wallet/registry")
+
def apply(settings: ErgoSettings): WalletRegistry = {
- val dir = new File(s"${settings.directory}/wallet/registry")
+ val dir = registryFolder(settings)
dir.mkdirs()
val store = new HybridLDBKVStore(dir, settings.nodeSettings.keepVersions)
diff --git a/src/main/scala/org/ergoplatform/settings/BootstrapSettings.scala b/src/main/scala/org/ergoplatform/settings/BootstrapSettings.scala
deleted file mode 100644
index 15e9bc65dc..0000000000
--- a/src/main/scala/org/ergoplatform/settings/BootstrapSettings.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.ergoplatform.settings
-
-import scala.concurrent.duration.FiniteDuration
-
-case class BootstrapSettings(resourceUri: String, pollDelay: FiniteDuration)
diff --git a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala
index f1184ee107..774c641aee 100644
--- a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala
+++ b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala
@@ -22,7 +22,6 @@ case class ErgoSettings(directory: String,
scorexSettings: ScorexSettings,
walletSettings: WalletSettings,
cacheSettings: CacheSettings,
- bootstrapSettingsOpt: Option[BootstrapSettings] = None,
votingTargets: VotingTargets = VotingTargets.empty) {
val addressEncoder = ErgoAddressEncoder(chainSettings.addressPrefix)
@@ -58,7 +57,6 @@ object ErgoSettings extends ScorexLogging
val networkType = NetworkType.fromString(networkTypeName)
.getOrElse(throw new Error(s"Unknown `networkType = $networkTypeName`"))
val nodeSettings = config.as[NodeConfigurationSettings](s"$configPath.node")
- val bootstrappingSettingsOpt = config.as[Option[BootstrapSettings]](s"$configPath.bootstrap")
val chainSettings = config.as[ChainSettings](s"$configPath.chain")
val walletSettings = config.as[WalletSettings](s"$configPath.wallet")
val cacheSettings = config.as[CacheSettings](s"$configPath.cache")
@@ -78,7 +76,6 @@ object ErgoSettings extends ScorexLogging
scorexSettings,
walletSettings,
cacheSettings,
- bootstrappingSettingsOpt,
votingTargets
),
desiredNetworkTypeOpt
diff --git a/src/main/scala/org/ergoplatform/settings/StateTypeReaders.scala b/src/main/scala/org/ergoplatform/settings/StateTypeReaders.scala
index f1b9a2dead..c28cac0d0e 100644
--- a/src/main/scala/org/ergoplatform/settings/StateTypeReaders.scala
+++ b/src/main/scala/org/ergoplatform/settings/StateTypeReaders.scala
@@ -1,17 +1,10 @@
package org.ergoplatform.settings
import com.typesafe.config.ConfigException
-import net.ceedubs.ficus.readers.ValueReader
import org.ergoplatform.nodeView.state._
trait StateTypeReaders {
- implicit val stateTypeReader: ValueReader[StateType] = { (cfg, path) =>
- val typeKey = s"$path.stateType"
- val typeName = cfg.getString(typeKey)
- stateTypeFromString(typeName, typeKey)
- }
-
def stateTypeFromString(typeName: String, path: String): StateType = {
StateType.values.find(_.stateTypeName == typeName)
.getOrElse(throw new ConfigException.BadValue(path, typeName))
diff --git a/src/main/scala/org/ergoplatform/utils/FileUtils.scala b/src/main/scala/org/ergoplatform/utils/FileUtils.scala
index 9bd88a95dc..32fa3a5041 100644
--- a/src/main/scala/org/ergoplatform/utils/FileUtils.scala
+++ b/src/main/scala/org/ergoplatform/utils/FileUtils.scala
@@ -1,20 +1,21 @@
package org.ergoplatform.utils
import java.io.File
+import java.nio.file.Files
+import scala.collection.JavaConverters._
+import scala.util.Try
/**
- * Utilities to work with OS file system
- */
+ * Utilities to work with OS file system
+ */
object FileUtils {
+
/**
- * Perform recursive deletion of directory content.
- */
- def deleteRecursive(dir: File): Unit = {
- for (file <- dir.listFiles) {
- if (!file.getName.startsWith(".")) {
- if (file.isDirectory) deleteRecursive(file)
- file.delete()
- }
+ * Perform recursive deletion of directory content.
+ */
+ def deleteRecursive(root: File): Unit = {
+ if (root.exists()) {
+ Files.walk(root.toPath).iterator().asScala.toSeq.reverse.foreach(path => Try(Files.delete(path)))
}
}
diff --git a/src/test/scala/org/ergoplatform/http/routes/WalletApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/WalletApiRouteSpec.scala
index 2fae93ce12..a33ee02024 100644
--- a/src/test/scala/org/ergoplatform/http/routes/WalletApiRouteSpec.scala
+++ b/src/test/scala/org/ergoplatform/http/routes/WalletApiRouteSpec.scala
@@ -15,6 +15,7 @@ import org.ergoplatform.utils.Stubs
import org.ergoplatform.utils.generators.ErgoTransactionGenerators
import org.ergoplatform.{ErgoAddress, Pay2SAddress}
import org.scalatest.{FlatSpec, Matchers}
+import org.ergoplatform.wallet.{Constants => WalletConstants}
import scala.util.{Random, Try}
import scala.concurrent.duration._
@@ -144,6 +145,13 @@ class WalletApiRouteSpec extends FlatSpec
}
}
+ it should "rescan wallet" in {
+ Get(prefix + "/rescan") ~> route ~> check {
+ status shouldBe StatusCodes.OK
+ }
+ }
+
+
it should "derive new key according to a provided path" in {
Post(prefix + "/deriveKey", Json.obj("derivationPath" -> "m/1/2".asJson)) ~> route ~> check {
status shouldBe StatusCodes.OK
@@ -202,8 +210,12 @@ class WalletApiRouteSpec extends FlatSpec
Get(prefix + "/transactions") ~> route ~> check {
status shouldBe StatusCodes.OK
val response = responseAs[List[Json]]
- response.size shouldBe 2
- responseAs[Seq[AugWalletTransaction]] shouldEqual WalletActorStub.walletTxs
+ val walletTxs = WalletActorStub.walletTxs.filter { awtx =>
+ awtx.wtx.scanIds.exists(_ <= WalletConstants.PaymentsScanId)
+ }
+
+ response.size shouldBe walletTxs.size
+ responseAs[Seq[AugWalletTransaction]] shouldEqual walletTxs
}
}
diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistrySpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistrySpec.scala
index c65926692c..e11ebad30c 100644
--- a/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistrySpec.scala
+++ b/src/test/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistrySpec.scala
@@ -21,7 +21,7 @@ class WalletRegistrySpec
with WalletGenerators
with FileUtils {
- implicit override val generatorDrivenConfig = PropertyCheckConfiguration(minSuccessful = 5, sizeRange = 10)
+ implicit override val generatorDrivenConfig = PropertyCheckConfiguration(minSuccessful = 4, sizeRange = 10)
private val emptyBag = KeyValuePairsBag.empty
private val walletBoxStatus = Set(PaymentsScanId)
@@ -204,6 +204,27 @@ class WalletRegistrySpec
}
}
+ it should "update scans correctly" in {
+ val appId1: ScanId = ScanId @@ 21.toShort
+ val appId2: ScanId = ScanId @@ 22.toShort
+
+ forAll(trackedBoxGen) { tb0 =>
+ withHybridStore(10) { store =>
+ val tb1 = tb0.copy(scans = Set(appId1, appId2), spendingHeightOpt = None, spendingTxIdOpt = None)
+
+ val reg = new WalletRegistry(store)(ws)
+ WalletRegistry.putBox(emptyBag, tb1).transact(store)
+ reg.getBox(tb1.box.id).get.scans shouldBe Set(appId1, appId2)
+ reg.unspentBoxes(appId1).length shouldBe 1
+ reg.unspentBoxes(appId2).length shouldBe 1
+ reg.updateScans(Set(appId1), tb1.box)
+ reg.getBox(tb1.box.id).get.scans shouldBe Set(appId1)
+ reg.unspentBoxes(appId1).length shouldBe 1
+ reg.unspentBoxes(appId2).length shouldBe 0
+ }
+ }
+ }
+
it should "remove application from a box correctly" in {
val appId: ScanId = ScanId @@ 20.toShort
@@ -220,7 +241,7 @@ class WalletRegistrySpec
}
- it should "remove application and then rollback - one app" in {
+ it should "remove box-scan correspondence and then rollback - one app" in {
val scanId: ScanId = ScanId @@ 20.toShort
forAll(trackedBoxGen) { tb0 =>
@@ -239,7 +260,7 @@ class WalletRegistrySpec
}
}
- it should "remove application and then rollback - multiple apps" in {
+ it should "remove box-scan correspondence and then rollback - multiple apps" in {
val scanId: ScanId = ScanId @@ 20.toShort
forAll(trackedBoxGen) { tb0 =>
diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala
index 01685d81d2..056c215611 100644
--- a/src/test/scala/org/ergoplatform/utils/Stubs.scala
+++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala
@@ -16,7 +16,7 @@ import org.ergoplatform.nodeView.wallet._
import org.ergoplatform.nodeView.wallet.persistence.WalletDigest
import org.ergoplatform.sanity.ErgoSanity.HT
import org.ergoplatform.settings.Constants.HashLength
-import org.ergoplatform.wallet.Constants.{ScanId, PaymentsScanId}
+import org.ergoplatform.wallet.Constants.{PaymentsScanId, ScanId}
import org.ergoplatform.settings._
import org.ergoplatform.utils.generators.{ChainGenerator, ErgoGenerators, ErgoTransactionGenerators}
import org.ergoplatform.wallet.boxes.{ChainStatus, TrackedBox}
@@ -24,6 +24,7 @@ import org.ergoplatform.wallet.interpreter.ErgoProvingInterpreter
import org.ergoplatform.wallet.secrets.DerivationPath
import org.ergoplatform.P2PKAddress
import org.ergoplatform.nodeView.wallet.scanning.Scan
+import org.scalacheck.Gen
import scorex.core.app.Version
import scorex.core.network.NetworkController.ReceivableMessages.GetConnectedPeers
import scorex.core.network.peer.PeerManager.ReceivableMessages.{GetAllPeers, GetBlacklistedPeers}
@@ -154,6 +155,8 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with
case LockWallet => ()
+ case RescanWallet => sender ! Success(())
+
case GetWalletStatus => sender() ! WalletStatus(true, true, None, ErgoHistory.GenesisHeight)
case _: CheckSeed => sender() ! true
@@ -246,7 +249,8 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with
spendingTxIdOpt = Some(modifierIdGen.sample.get)
)
)
- val walletTxs: Seq[AugWalletTransaction] = Seq(augWalletTransactionGen.sample.get, augWalletTransactionGen.sample.get)
+ val walletTxs: Seq[AugWalletTransaction] =
+ Gen.listOf(augWalletTransactionGen).sample.get
def props(): Props = Props(new WalletActorStub)