diff --git a/README.md b/README.md index 184463f8d4..12425ba403 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ To run specific Ergo version `` as a service with custom config `/path/ -e MAX_HEAP=3G \ ergoplatform/ergo: -- -c /etc/myergo.conf -Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.21.1`. +Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.22`. This will connect to the Ergo mainnet or testnet following your configuration passed in `myergo.conf` and network flag `--`. Every default config value would be overwritten with corresponding value in `myergo.conf`. `MAX_HEAP` variable can be used to control how much memory can the node consume. diff --git a/benchmarks/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateBenchmark.scala b/benchmarks/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateBenchmark.scala index e1b9465c0b..49909ad177 100644 --- a/benchmarks/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateBenchmark.scala +++ b/benchmarks/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateBenchmark.scala @@ -22,10 +22,10 @@ object UtxoStateBenchmark extends HistoryTestHelpers with NVBenchmark { val transactionsQty = blocks.flatMap(_.transactions).size def bench(mods: Seq[ErgoPersistentModifier]): Long = { - val state = ErgoState.generateGenesisUtxoState(createTempDir, StateConstants(None, realNetworkSetting), parameters)._1 + val state = ErgoState.generateGenesisUtxoState(createTempDir, StateConstants(realNetworkSetting), parameters)._1 Utils.time { mods.foldLeft(state) { case (st, mod) => - st.applyModifier(mod).get + st.applyModifier(mod)(_ => ()).get } }.toLong } diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index d8103ecf73..4aca4fdab6 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.2" info: - version: "4.0.21.1" + version: "4.0.22" title: Ergo Node API description: API docs for Ergo Node. Models are shared between all Ergo products contact: @@ -426,6 +426,7 @@ components: items: anyOf: - $ref: '#/components/schemas/PaymentRequest' + - $ref: '#/components/schemas/BurnTokensRequest' - $ref: '#/components/schemas/AssetIssueRequest' fee: description: Transaction fee @@ -1114,6 +1115,18 @@ components: registers: $ref: '#/components/schemas/Registers' + BurnTokensRequest: + description: Request for burning tokens in wallet + type: object + required: + - assetsToBurn + properties: + assetsToBurn: + description: Assets list to burn in the transaction + type: array + items: + $ref: '#/components/schemas/Asset' + AssetIssueRequest: description: Request for generation of asset issue transaction type: object diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index fccba9af72..78f70a7100 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -51,7 +51,7 @@ ergo { keepVersions = 200 # Acceptable difference between NOW and timestamp of the latest chain update or best block. This helps to discover syncing issues. - acceptableChainUpdateDelay = 15m + acceptableChainUpdateDelay = 30m # Maximum number of unconfirmed transactions node can accept mempoolCapacity = 1000 @@ -345,7 +345,7 @@ scorex { nodeName = "ergo-node" # Network protocol version to be sent in handshakes - appVersion = 4.0.21.1 + appVersion = 4.0.22 # Network agent name. May contain information about client code # stack, starting from core code-base up to the end graphical interface. @@ -522,24 +522,4 @@ api-dispatcher { # processed per actor before the thread jumps to the next actor. # Set to 1 for as fair as possible. throughput = 4 -} - -network-dispatcher { - # Dispatcher is the name of the event-based dispatcher - type = Dispatcher - # What kind of ExecutionService to use - executor = "fork-join-executor" - # Configuration for the fork join pool - fork-join-executor { - # Min number of threads to cap factor-based parallelism number to - parallelism-min = 1 - # Parallelism (threads) ... ceil(available processors * factor) - parallelism-factor = 1.0 - # Max number of threads to cap factor-based parallelism number to - parallelism-max = 1 - } - # Throughput defines the maximum number of messages to be - # processed per actor before the thread jumps to the next actor. - # Set to 1 for as fair as possible. - throughput = 1 } \ No newline at end of file diff --git a/src/main/resources/mainnet.conf b/src/main/resources/mainnet.conf index 2121b6686b..7bbe587794 100644 --- a/src/main/resources/mainnet.conf +++ b/src/main/resources/mainnet.conf @@ -44,7 +44,7 @@ scorex { network { magicBytes = [1, 0, 2, 4] bindAddress = "0.0.0.0:9030" - nodeName = "ergo-mainnet-4.0.21.1" + nodeName = "ergo-mainnet-4.0.22" nodeName = ${?NODENAME} knownPeers = [ "213.239.193.208:9030", diff --git a/src/main/resources/panel/asset-manifest.json b/src/main/resources/panel/asset-manifest.json index 333efb4b09..5758413ff0 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.0e9161bb.chunk.css", - "main.js": "/static/js/main.2343b43e.chunk.js", - "main.js.map": "/static/js/main.2343b43e.chunk.js.map", + "main.js": "/static/js/main.2df85f5c.chunk.js", + "main.js.map": "/static/js/main.2df85f5c.chunk.js.map", "runtime-main.js": "/static/js/runtime-main.219240e0.js", "runtime-main.js.map": "/static/js/runtime-main.219240e0.js.map", "static/css/2.9338f6a1.chunk.css": "/static/css/2.9338f6a1.chunk.css", "static/js/2.6b84a7b0.chunk.js": "/static/js/2.6b84a7b0.chunk.js", "static/js/2.6b84a7b0.chunk.js.map": "/static/js/2.6b84a7b0.chunk.js.map", "index.html": "/index.html", - "precache-manifest.495a82b903bf93fbd7028c4017ca8dee.js": "/precache-manifest.495a82b903bf93fbd7028c4017ca8dee.js", + "precache-manifest.d5050dc805ea3d39fb8ff28d6cef00ed.js": "/precache-manifest.d5050dc805ea3d39fb8ff28d6cef00ed.js", "service-worker.js": "/service-worker.js", "static/css/2.9338f6a1.chunk.css.map": "/static/css/2.9338f6a1.chunk.css.map", "static/css/main.0e9161bb.chunk.css.map": "/static/css/main.0e9161bb.chunk.css.map", @@ -25,6 +25,6 @@ "static/css/2.9338f6a1.chunk.css", "static/js/2.6b84a7b0.chunk.js", "static/css/main.0e9161bb.chunk.css", - "static/js/main.2343b43e.chunk.js" + "static/js/main.2df85f5c.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 99e0d2413a..6942f737c9 100644 --- a/src/main/resources/panel/index.html +++ b/src/main/resources/panel/index.html @@ -1 +1 @@ -Ergo node interface
\ No newline at end of file +Ergo node interface
\ No newline at end of file diff --git a/src/main/resources/panel/precache-manifest.495a82b903bf93fbd7028c4017ca8dee.js b/src/main/resources/panel/precache-manifest.d5050dc805ea3d39fb8ff28d6cef00ed.js similarity index 92% rename from src/main/resources/panel/precache-manifest.495a82b903bf93fbd7028c4017ca8dee.js rename to src/main/resources/panel/precache-manifest.d5050dc805ea3d39fb8ff28d6cef00ed.js index ea3d145fd3..1621125dd3 100644 --- a/src/main/resources/panel/precache-manifest.495a82b903bf93fbd7028c4017ca8dee.js +++ b/src/main/resources/panel/precache-manifest.d5050dc805ea3d39fb8ff28d6cef00ed.js @@ -1,6 +1,6 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([ { - "revision": "7a445dd2913c13bc0923f8785f906190", + "revision": "07a803448c8cd5afd8c3d399f444b959", "url": "/index.html" }, { @@ -8,7 +8,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([ "url": "/static/css/2.9338f6a1.chunk.css" }, { - "revision": "623717ef0d9ac2c40350", + "revision": "b6be50d400b93ddc0e13", "url": "/static/css/main.0e9161bb.chunk.css" }, { @@ -16,8 +16,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([ "url": "/static/js/2.6b84a7b0.chunk.js" }, { - "revision": "623717ef0d9ac2c40350", - "url": "/static/js/main.2343b43e.chunk.js" + "revision": "b6be50d400b93ddc0e13", + "url": "/static/js/main.2df85f5c.chunk.js" }, { "revision": "ce813ebd69754efed759", diff --git a/src/main/resources/panel/service-worker.js b/src/main/resources/panel/service-worker.js index 14b40cb13e..bf8593521c 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.495a82b903bf93fbd7028c4017ca8dee.js" + "/precache-manifest.d5050dc805ea3d39fb8ff28d6cef00ed.js" ); self.addEventListener('message', (event) => { diff --git a/src/main/resources/panel/static/js/main.2343b43e.chunk.js b/src/main/resources/panel/static/js/main.2df85f5c.chunk.js similarity index 74% rename from src/main/resources/panel/static/js/main.2343b43e.chunk.js rename to src/main/resources/panel/static/js/main.2df85f5c.chunk.js index c6a73ad002..3a2ebfd168 100644 --- a/src/main/resources/panel/static/js/main.2343b43e.chunk.js +++ b/src/main/resources/panel/static/js/main.2df85f5c.chunk.js @@ -1,2 +1,2 @@ -(this["webpackJsonpergo-node-interface"]=this["webpackJsonpergo-node-interface"]||[]).push([[0],{103:function(e,t,a){e.exports=a(143)},114:function(e,t,a){},131:function(e,t,a){},135:function(e,t,a){},136:function(e,t,a){},137:function(e,t,a){},138:function(e,t,a){},139:function(e,t,a){},141:function(e,t,a){},143:function(e,t,a){"use strict";a.r(t);var n=a(0),r=a.n(n),l=a(13),c=a.n(l),o=a(49),s=a(9),i=a(39),m=a(7),u=a(11),d=a(10),p=Object(d.c)({name:"nodeSlice",initialState:{network:"mainnet"},reducers:{setNetwork:function(e,t){var a=t.payload;e.network=a}}}),f=Object(d.b)("getNetwork"),b=Object(u.a)(Object(u.a)({},p.actions),{},{getNetwork:f}),h=a(19),E=a(21),v=a(40),y="/swagger",g="https://ergoplatform.org",w=1e9,N=a(14),O=a(15),j=a(18),k=a(17),S=a(90),_=a(79),C=function(e){return e.node},x=(Object(_.a)(C,(function(e){return e.network})),Object(_.a)(C,(function(e){return"mainnet"===e.network?"explorer":e.network}))),W=r.a.createElement(h.a,{icon:S.a}),A=function(e){Object(j.a)(a,e);var t=Object(k.a)(a);function a(){return Object(N.a)(this,a),t.apply(this,arguments)}return Object(O.a)(a,[{key:"render",value:function(){var e=this.props.explorerSubdomain;return r.a.createElement("a",{ref:this.exporerRef,key:"Explorer",className:Object(v.a)("list-group-item list-group-item-action"),href:"https://".concat(e,".ergoplatform.com"),rel:"noopener noreferrer",target:"_blank"},W," ","Explorer")}}]),a}(n.Component),I=Object(s.b)((function(e){return{explorerSubdomain:x(e)}}),null)(A),B={dashboard:{href:"/",icon:r.a.createElement(h.a,{icon:E.b}),title:"Dashboard"},wallet:{href:"/wallet",icon:r.a.createElement(h.a,{icon:E.d}),title:"Wallet"}},P={swaggerInterface:{href:y,icon:r.a.createElement(h.a,{icon:E.a}),title:"Swagger"},website:{href:g,icon:r.a.createElement(h.a,{icon:E.f}),title:"Website"}},F=Object(m.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(B).map((function(e,a){var n=e.href,l=e.icon,c=e.title;return r.a.createElement(i.b,{key:c,className:Object(v.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(P).map((function(e,t){var a=e.href,n=e.icon,l=e.title;return r.a.createElement("a",{key:l,className:Object(v.a)("list-group-item list-group-item-action",{"border-top-0":0===t}),href:a,rel:"noopener noreferrer",target:"_blank"},n," ",l)})),r.a.createElement(I,null)))})),z=(a(114),Object(_.a)((function(e){return e.app}),(function(e){return e.apiKey}))),T=function(e){return e.wallet},L=Object(_.a)(T,(function(e){return e.isWalletUnlocked})),M=Object(_.a)(T,(function(e){return e.isWalletInitialized})),H=Object(_.a)(T,(function(e){return e.walletStatusData})),K=Object(_.a)(T,(function(e){return e.walletBalanceData})),R=Object(_.a)(T,(function(e){return e.walletAddresses})),U=Object(_.a)(T,(function(e){return e.ergPrice})),D=Object(d.c)({name:"walletSlice",initialState:{isWalletUnlocked:null,isWalletInitialized:null,walletStatusData:null,walletBalanceData:null,ergPrice:null,walletAddresses:null},reducers:{setIsWalletUnlocked:function(e,t){var a=t.payload;e.isWalletUnlocked=a},setIsWalletInitialized:function(e,t){var a=t.payload;e.isWalletInitialized=a},setWalletStatusData:function(e,t){var a=t.payload;e.walletStatusData=a},setWalletBalanceData:function(e,t){var a=t.payload;e.walletBalanceData=a},setErgPrice:function(e,t){var a=t.payload;e.ergPrice=a},setWalletAddresses:function(e,t){var a=t.payload;e.walletAddresses=a}}}),G=Object(d.b)("checkWalletStatus"),V=Object(d.b)("getWalletBalance"),Y=Object(d.b)("getErgPrice"),q=Object(d.b)("getWalletAddresses"),Z=Object(u.a)(Object(u.a)({},D.actions),{},{checkWalletStatus:G,getWalletBalance:V,getErgPrice:Y,getWalletAddresses:q}),J=a(5),$=a(152),Q=Object(d.c)({name:"appSlice",initialState:{apiKey:""},reducers:{setApiKey:function(e,t){e.apiKey=t.payload}}}),X=Object(u.a)({},Q.actions),ee=a(91),te=a.n(ee),ae=Object(u.a)({},{nodeApiLink:"/",oracleApiLink:"https://erg-usd-ergo-oracle.emurgo.io"});function ne(e){var t=e.status,a=e.message,n=e.data,r=e.statusText;this.name="NetworkError",this.message=a||r,this.status=t,this.data=n}ne.prototype=Object.create(Error.prototype);var re=te.a.create({baseURL:ae.nodeApiLink,timeout:1e4,crossDomain:!0,headers:{"Content-Type":"application/json"}});re.interceptors.response.use((function(e){return Promise.resolve(e)}),(function(e){return Promise.reject(new ne(e.response||e))}));var le=re,ce=(a(131),{success:function(e,t){return o.a.success(e,Object(u.a)({position:"top-right",autoClose:5e3,hideProgressBar:!1,closeOnClick:!0,pauseOnHover:!0,draggable:!0,className:"n-toast n-toast--success",bodyClassName:"n-toast__body",progressClassName:"n-toast__progress--success"},t))},error:function(e,t){return o.a.error(e,Object(u.a)({position:"top-right",autoClose:5e3,hideProgressBar:!1,closeOnClick:!0,pauseOnHover:!0,draggable:!0,className:"n-toast n-toast--error",bodyClassName:"n-toast__body",progressClassName:"n-toast__progress--error"},t))},info:o.a.info}),oe=function(e,t,a){return ce[e]?ce[e](t,a):new Error("Bad toast state")},se=a(43),ie=a(29),me=a(12),ue=a(153),de=function(e){var t=e.showModal,a=e.handleHide,n=e.submitForm,l=e.apiKey,c=e.handleShow,o=Object(ue.a)();return r.a.createElement("div",null,function(e,t){return""===e?r.a.createElement("button",{type:"button",onClick:t,className:"btn btn-primary"},"Set API key"):r.a.createElement("button",{type:"button",onClick:t,className:"btn btn-outline-primary"},"Update API key")}(l,c),r.a.createElement(ie.a,{show:t,onHide:function(){return a()},centered:!0},r.a.createElement(me.c,{initialValues:Object(se.a)({},"apiKey".concat(o),l),onSubmit:function(e){return n(e,o)}},(function(){return r.a.createElement(me.b,null,r.a.createElement(ie.a.Header,{closeButton:!0},r.a.createElement(ie.a.Title,null,"Authorization")),r.a.createElement(ie.a.Body,null,r.a.createElement("p",{className:"text"},"Set API key to access Node requests"),r.a.createElement("div",{className:"input-group"},r.a.createElement(me.a,{type:"text",name:"apiKey".concat(o),className:"form-control",placeholder:"Enter API key"}))),r.a.createElement(ie.a.Footer,null,r.a.createElement("button",{type:"button",className:"btn btn-outline-secondary",onClick:a},"Close"),r.a.createElement("button",{type:"submit",className:"btn btn-primary"},"Save changes")))}))))},pe=Object(s.b)((function(e){return{apiKey:z(e)}}),(function(e){return{dispatchSetApiKey:function(t){return e(X.setApiKey(t))}}}))(Object(n.memo)((function(e){var t=e.dispatchSetApiKey,a=e.apiKey,l=Object(n.useState)(!1),c=Object(J.a)(l,2),o=c[0],s=c[1],i=function(){s(!1)};return r.a.createElement(de,{showModal:o,apiKey:a,handleHide:i,submitForm:function(e,a){le.get("/wallet/status",{headers:{api_key:e["apiKey".concat(a)]}}).then((function(){t(e["apiKey".concat(a)].trim()),oe("success","API key is set successfully"),i()})).catch((function(){oe("error","Bad API key")}))},handleShow:function(){s(!0)}})}))),fe="INIT",be="STATUS",he=function(e){Object(j.a)(a,e);var t=Object(k.a)(a);function a(){var e;Object(N.a)(this,a);for(var n=arguments.length,l=new Array(n),c=0;c0&&void 0!==arguments[0]?arguments[0]:Math.random().toString(32).slice(2,10),t="id-".concat(e),a=Ze().isBrowser,r=Object(n.useState)(a?Je(t):null),l=Object(J.a)(r,2),c=l[0],o=l[1];return Object(n.useEffect)((function(){var e=document.querySelector("#".concat(t)),a=e||Je(t);e||document.body.appendChild(a),o(a)}),[]),c},Qe={scrollLayer:!1},Xe=new Map,et=function(e){return!!(e.touches&&e.touches.length>1)||(e.preventDefault(),!1)},tt=function(e,t){if("undefined"===typeof document)return[!1,function(e){return e}];var a=e||Object(n.useRef)(document.body),r=Object(n.useState)(!1),l=Object(J.a)(r,2),c=l[0],o=l[1],s=Object(u.a)(Object(u.a)({},Qe),t||{}),i=function(){return!s.scrollLayer&&(!("undefined"===typeof window||!window.navigator)&&/iP(ad|hone|od)/.test(window.navigator.platform))};return Object(n.useEffect)((function(){if(a&&a.current){var e=a.current.style.overflow;if(c){if(Xe.has(a.current))return;return i()?document.addEventListener("touchmove",et,{passive:!1}):a.current.style.overflow="hidden",void Xe.set(a.current,{last:e})}if(Xe.has(a.current)){if(i())document.removeEventListener("touchmove",et);else{var t=Xe.get(a.current);a.current.style.overflow=t.last}Xe.delete(a.current)}}}),[c,a]),[c,o]},at=function(e){var t=Object(n.useState)((function(){return"function"===typeof e?e():e})),a=Object(J.a)(t,2),r=a[0],l=a[1],c=Object(n.useRef)(e);Object(n.useEffect)((function(){c.current=r}),[r]);return[r,function(e){var t="function"===typeof e?e(c.current):e;c.current=t,l(t)},c]},nt=a(66),rt=a(4),lt=a.n(rt),ct=function(e){var t=e.children,a=e.variant,n=e.component,l=e.color,c=e.className,o=e.dangerouslySetInnerHTML,s=Object(nt.a)(e,["children","variant","component","color","className","dangerouslySetInnerHTML"]),i=a,m=n||"p";return o?r.a.createElement(r.a.Fragment,null,r.a.createElement(m,Object.assign({className:lt()({colored:l},i,c),dangerouslySetInnerHTML:o},s)),r.a.createElement("style",null,"\n .colored {\n color: var(--".concat(l,");\n }\n "))):r.a.createElement(m,Object.assign({className:lt()({colored:l},i,c)},s),t,r.a.createElement("style",null,"\n .colored {\n color: var(--".concat(l,");\n }\n ")))},ot=function(e,t){return e.defaultProps=t,e},st=ot((function(e){var t=e.children,a=e.className,l=e.visible,c=e.enterTime,o=e.leaveTime,s=e.clearTime,i=e.name,m=Object(nt.a)(e,["children","className","visible","enterTime","leaveTime","clearTime","name"]),d=Object(n.useState)(""),p=Object(J.a)(d,2),f=p[0],b=p[1],h=Object(n.useState)(l),E=Object(J.a)(h,2),v=E[0],y=E[1];return Object(n.useEffect)((function(){var e=l?"enter":"leave",t=l?c:o;l&&!v&&y(!0),b("".concat(i,"-").concat(e));var a=setTimeout((function(){b("".concat(i,"-").concat(e," ").concat(i,"-").concat(e,"-active")),clearTimeout(a)}),t),n=setTimeout((function(){l||(b(""),y(!1)),clearTimeout(n)}),t+s);return function(){clearTimeout(a),clearTimeout(n)}}),[l,v]),r.a.isValidElement(t)&&v?r.a.cloneElement(t,Object(u.a)(Object(u.a)({},m),{},{className:"".concat(t.props.className," ").concat(a," ").concat(f)})):null}),{visible:!1,enterTime:60,leaveTime:60,clearTime:60,className:"",name:"transition"}),it=a(68),mt=a.n(it),ut=ot(r.a.memo((function(e){var t=e.children,a=e.onClick,l=e.visible,c=e.className,o=at(!1),s=Object(J.a)(o,3),i=s[1],m=s[2],u=Object(n.useCallback)((function(e){e.stopPropagation()}),[]);return r.a.createElement(st,{visible:l,clearTime:300},r.a.createElement("div",{className:mt.a.backdrop,onClick:function(e){m.current||a&&a(e)},onMouseUp:function(){if(m.current)var e=setTimeout((function(){i(!1),clearTimeout(e)}),0)}},r.a.createElement("div",{className:mt.a.layer}),r.a.createElement("div",{onClick:u,className:lt()(mt.a.content,c),onMouseDown:function(){return i(!0)}},t)))})),{onClick:function(){},visible:!1}),dt=r.a.createContext({});function pt(){return(pt=Object.assign||function(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var bt=r.a.createElement("path",{d:"M10.7951 9.30799C10.3844 8.89734 9.71865 8.89734 9.30799 9.30799C8.89734 9.71865 8.89734 10.3844 9.30799 10.7951L14.5129 16L9.30799 21.2049C8.89734 21.6156 8.89734 22.2814 9.30799 22.692C9.71865 23.1027 10.3844 23.1027 10.7951 22.692L16 17.4871L21.2049 22.692C21.6156 23.1027 22.2814 23.1027 22.692 22.692C23.1027 22.2814 23.1027 21.6156 22.692 21.2049L17.4871 16L22.692 10.7951C23.1027 10.3844 23.1027 9.71865 22.692 9.30799C22.2814 8.89734 21.6156 8.89734 21.2049 9.30799L16 14.5129L10.7951 9.30799Z",fill:"#76767A"}),ht=function(e){var t=e.svgRef,a=e.title,n=ft(e,["svgRef","title"]);return r.a.createElement("svg",pt({width:32,height:32,viewBox:"0 0 32 32",fill:"none",ref:t},n),a?r.a.createElement("title",null,a):null,bt)},Et=r.a.forwardRef((function(e,t){return r.a.createElement(ht,pt({svgRef:t},e))}));a.p;a.p;function vt(){return(vt=Object.assign||function(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var gt=r.a.createElement("path",{d:"M13.3332 7.99992C13.3332 8.36811 13.0347 8.66659 12.6665 8.66659H3.33317C2.96498 8.66659 2.6665 8.36811 2.6665 7.99992C2.6665 7.63173 2.96498 7.33325 3.33317 7.33325H12.6665C13.0347 7.33325 13.3332 7.63173 13.3332 7.99992Z",fill:"#0078FF"}),wt=function(e){var t=e.svgRef,a=e.title,n=yt(e,["svgRef","title"]);return r.a.createElement("svg",vt({width:16,height:16,viewBox:"0 0 16 16",fill:"none",ref:t},n),a?r.a.createElement("title",null,a):null,gt)},Nt=r.a.forwardRef((function(e,t){return r.a.createElement(wt,vt({svgRef:t},e))})),Ot=(a.p,function(e,t){return r.a.createElement(e,{className:t,focusable:"false"})}),jt=function(e){var t=e.className;return Ot(Nt,t)},kt=function(e){var t=e.className;return Ot(Et,t)},St=function(e){var t=e.title,a=e.description,c=e.primaryButtonContent,o=e.secondaryButtonContent,s=e.disableBackdropClick,i=e.onClose,m=e.onOpen,u=e.onPrimaryHandler,d=e.onSecondaryHandler,p=e.open,f=$e("modal"),b=tt(null,{scrollLayer:!0}),h=Object(J.a)(b,2)[1],E=at(!1),v=Object(J.a)(E,3),y=v[0],g=v[1],w=v[2],N=Object(n.useCallback)((function(){i&&i(),g(!1),h(!1)}),[i,g,h]);Object(n.useEffect)((function(){void 0!==p&&(p&&m&&m(),!p&&w.current&&i&&i(),g(p),h(p))}),[p]);var O=Object(n.useMemo)((function(){return{close:N}}),[N]);return f?Object(l.createPortal)(r.a.createElement(dt.Provider,{value:O},r.a.createElement(ut,{onClick:function(){s||N()},visible:y,className:"info-modal-backdrop"},r.a.createElement(st,{name:"wrapper",visible:y,clearTime:300},r.a.createElement("div",{className:"info-modal"},r.a.createElement("div",{className:"info-modal__content"},r.a.createElement("h3",{className:"mb-3"},t),r.a.createElement(ct,{xl:"body-text1",sm:"small-text1",className:"mb-40 mb-md-56 pr-40 pr-md-0"},a),r.a.createElement("button",{type:"button",className:"btn btn-primary px-4",onClick:function(){u&&u(),i&&i()}},c),r.a.createElement("button",{type:"button",className:"btn btn-outline-secondary px-4 ml-3",onClick:function(){d&&d(),i&&i()}},o)),r.a.createElement("button",{type:"button",className:"info-modal__button--close",onClick:N},r.a.createElement(kt,null)),r.a.createElement("style",null,"\n .wrapper-enter {\n opacity: 0;\n transform: translate3d(0px, -40px, 0px);\n }\n .wrapper-enter-active {\n opacity: 1;\n transform: translate3d(0px, 0px, 0px);\n }\n .wrapper-leave {\n opacity: 1;\n transform: translate3d(0px, 0px, 0px);\n }\n .wrapper-leave-active {\n opacity: 0;\n transform: translate3d(0px, -50px, 0px);\n }\n "))))),f):null};St.defaultProps={width:"26rem",wrapClassName:"",disableBackdropClick:!1};var _t=St,Ct=function(e){var t=e.apiKey,a=e.walletBalanceData,l=e.getWalletBalance,c=e.explorerSubdomain,o=null===a||void 0===a?void 0:a.balance,s=Object(n.useState)(null),i=Object(J.a)(s,2),m=i[0],u=i[1],d=Object(n.useState)(!1),p=Object(J.a)(d,2),f=p[0],b=p[1],h=Object(n.useState)(!1),E=Object(J.a)(h,2),v=E[0],y=E[1],g=Object(n.useCallback)((function(e){var a=e.recipientAddress,n=e.amount,r=e.fee,l=e.asset,c=e.assetAmount,o={address:a.trim(),value:Number((parseFloat(n)*w).toFixed(1)),assets:v&&"none"!==l&&c>0?[{tokenId:l,amount:Number(c)}]:[]};return le.post("/wallet/transaction/send",{requests:[o],fee:Number((parseFloat(r)*w).toFixed(1))},{headers:{api_key:t}})}),[v,t]),N=Object(n.useCallback)((function(e){""!==e.recipientAddress.trim()&&e.recipientAddress&&g(e).then((function(e){var t=e.data;u(t),b(!0),l()})).catch((function(e){var t=e.data?e.data.detail:e.message;oe("error",t)}))}),[g,l]),O=Object(n.useCallback)((function(e){var t,n={},r=(Number(e.amount)+Number(e.fee))*w;return e.recipientAddress&&""!==(null===(t=e.recipientAddress)||void 0===t?void 0:t.trim())||(n.recipientAddress="The field cannot be empty"),(!e.fee||e.fee<.001)&&(n.fee="Minimum 0.001 ERG"),"none"===e.asset&&(n.asset="You need to choose asset"),a&&e.assetAmount&&"none"!==e.asset&&e.assetAmount>a.assets[e.asset]&&(n.assetAmount="Maximum ".concat(a.assets[e.asset])),v&&!e.assetAmount&&(n.assetAmount="The field can't be empty"),o0&&r.a.createElement("div",{className:"form-check mb-2"},r.a.createElement("input",{className:"form-check-input",type:"checkbox",checked:v,onChange:function(e){y(e.target.checked)},id:"assetCheckbox"}),r.a.createElement("label",{className:"form-check-label",htmlFor:"assetCheckbox"},"Add asset")),v&&r.a.createElement(r.a.Fragment,null,r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"asset"},"Asset"),r.a.createElement(qe.a,{name:"asset",component:"select",className:lt()("form-control")},r.a.createElement("option",{value:"none"},"Choose asset"),Object.keys((null===a||void 0===a?void 0:a.assets)||{}).map((function(e){return r.a.createElement("option",{key:e,value:e},e)})))),i.asset&&"none"!==i.asset&&r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"assetAmount"},"Asset amount"),r.a.createElement(qe.a,{name:"assetAmount",className:lt()("form-control"),render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"assetAmount",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),type:"number",placeholder:"0,000"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}}))),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"fee"},"Fee (in ERG)"),r.a.createElement(qe.a,{name:"fee",value:"0.001",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"fee",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),type:"number",placeholder:"Minimum 0.001 ERG"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("button",{type:"submit",className:"btn btn-primary",disabled:n||Object.keys(u).length>0||l||"none"===i.asset},"Send")),r.a.createElement(_t,{open:f,onClose:function(){b(!1)},title:"Payment sent",description:r.a.createElement(r.a.Fragment,null,r.a.createElement("p",null,"Your payment has been sent successfully. The transaction ID is -"," ",r.a.createElement(ke,null,m)),r.a.createElement("p",null,r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://".concat(c,".ergoplatform.com/en/transactions/").concat(m)},"Click Here To Go To The Explorer"))),primaryButtonContent:r.a.createElement("span",{className:"pl-3 pr-3"},"OK"),secondaryButtonContent:"Send again",onPrimaryHandler:function(){return function(e){e.restart(),b(!1)}(s)}}))}})))},xt=function(e){var t=e.apiKey,a=e.getWalletBalance,l=e.explorerSubdomain,c=Object(n.useState)(null),o=Object(J.a)(c,2),s=o[0],i=o[1],m=Object(n.useState)(!1),u=Object(J.a)(m,2),d=u[0],p=u[1],f=Object(n.useState)(null),b=Object(J.a)(f,2),h=b[0],E=b[1],v=Object(n.useState)(null),y=Object(J.a)(v,2),g=y[0],N=y[1],O=Object(n.useCallback)((function(e){var a=e.name,n=e.amount,r=e.decimals,l=e.description,c=e.fee;E(n),N(a);var o={name:a,amount:n,decimals:r,description:l};return le.post("/wallet/transaction/send",{requests:[o],fee:Number((parseFloat(c)*w).toFixed(1))},{headers:{api_key:t}})}),[t,E,N]),j=Object(n.useCallback)((function(e){return O(e).then((function(e){var t=e.data;i(t),p(!0),a()})).catch((function(e){var t=e.data?e.data.detail:e.message;oe("error",t)}))}),[O,a]);return r.a.createElement("div",{className:"card bg-white p-4"},r.a.createElement("h2",{className:"h5 mb-3"},"Issue Tokens"),r.a.createElement(qe.b,{onSubmit:j,validate:function(e){var t={};return e.name||(t.name="The field cannot be empty"),e.amount||(t.amount="The field cannot be empty"),e.decimals||(t.decimals="The field cannot be empty"),e.description||(t.description="The field cannot be empty"),(!e.fee||Number(e.fee)<.001)&&(t.fee="Minimum 0.001 ERG"),!Number.isInteger(Number(e.amount))&&e.amount&&(t.amount="Should be an integer"),!Number.isInteger(Number(e.decimals))&&e.decimals&&(t.decimals="Should be an integer"),Number(e.fee)<0&&(t.fee="Fee can't be negative"),t},initialValues:{fee:.001},render:function(e){var t=e.handleSubmit,a=e.submitting,n=e.pristine,c=e.form,o=e.errors;return r.a.createElement(r.a.Fragment,null,r.a.createElement("form",{onSubmit:t},r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"name"},"Asset name"),r.a.createElement(qe.a,{name:"name",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"name",type:"text",placeholder:"Enter asset name",className:lt()("form-control",{"is-invalid":a.touched&&a.error})},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"amount"},"Net amount"),r.a.createElement(qe.a,{name:"amount",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"amount",type:"text",placeholder:"Enter net amount",className:lt()("form-control",{"is-invalid":a.touched&&a.error})},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"decimals"},"Decimal places"),r.a.createElement(qe.a,{name:"decimals",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"decimals",type:"text",placeholder:"Enter decimals as integer",className:lt()("form-control",{"is-invalid":a.touched&&a.error})},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"description"},"Brief description"),r.a.createElement(qe.a,{name:"description",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("textarea",Object.assign({id:"description",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),placeholder:"Add asset description"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"fee"},"Fee (in ERG)"),r.a.createElement(qe.a,{name:"fee",value:"0.001",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"fee",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),type:"number",placeholder:"Minimum 0.001 ERG"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("button",{type:"submit",className:"btn btn-primary",disabled:a||Object.keys(o).length>0||n},"Issue")),r.a.createElement(_t,{open:d,onClose:function(){p(!1)},title:"Congratulations!",description:r.a.createElement(r.a.Fragment,null,r.a.createElement("p",null,"You have successfully issued ".concat(h," ").concat(g," tokens! The transaction ID is\n"),r.a.createElement(ke,null,s)),r.a.createElement("p",null,r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://".concat(l,".ergoplatform.com/en/transactions/").concat(s)},"Click Here To View Transaction"))),primaryButtonContent:r.a.createElement("span",{className:"pl-3 pr-3"},"OK"),secondaryButtonContent:"Send again",onPrimaryHandler:function(){return function(e){e.restart(),p(!1)}(c)}}))}}))};a(138);function Wt(){return(Wt=Object.assign||function(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var It=r.a.createElement("g",null,r.a.createElement("path",{d:"M39.622,21.746l-6.749,6.75c-0.562,0.562-1.326,0.879-2.122,0.879s-1.56-0.316-2.121-0.879l-6.75-6.75 c-1.171-1.171-1.171-3.071,0-4.242c1.171-1.172,3.071-1.172,4.242,0l1.832,1.832C27.486,13.697,22.758,9.25,17,9.25 c-6.064,0-11,4.935-11,11c0,6.064,4.936,11,11,11c1.657,0,3,1.343,3,3s-1.343,3-3,3c-9.373,0-17-7.626-17-17s7.627-17,17-17 c8.936,0,16.266,6.933,16.936,15.698l1.442-1.444c1.172-1.172,3.072-1.172,4.242,0C40.792,18.674,40.792,20.574,39.622,21.746z"})),Bt=r.a.createElement("g",null),Pt=r.a.createElement("g",null),Ft=r.a.createElement("g",null),zt=r.a.createElement("g",null),Tt=r.a.createElement("g",null),Lt=r.a.createElement("g",null),Mt=r.a.createElement("g",null),Ht=r.a.createElement("g",null),Kt=r.a.createElement("g",null),Rt=r.a.createElement("g",null),Ut=r.a.createElement("g",null),Dt=r.a.createElement("g",null),Gt=r.a.createElement("g",null),Vt=r.a.createElement("g",null),Yt=r.a.createElement("g",null),qt=function(e){var t=e.svgRef,a=e.title,n=At(e,["svgRef","title"]);return r.a.createElement("svg",Wt({id:"Capa_1",x:"0px",y:"0px",width:"32px",height:"32px",viewBox:"0 0 40.499 40.5",style:{enableBackground:"new 0 0 40.499 40.5"},xmlSpace:"preserve",ref:t},n),a?r.a.createElement("title",null,a):null,It,Bt,Pt,Ft,zt,Tt,Lt,Mt,Ht,Kt,Rt,Ut,Dt,Gt,Vt,Yt)},Zt=r.a.forwardRef((function(e,t){return r.a.createElement(qt,Wt({svgRef:t},e))})),Jt=(a.p,function(e){var t=e.className;return Ot(Zt,t)}),$t=function(e){var t,a,l=e.name,c=e.value,o=Object(n.useState)(!1),s=Object(J.a)(o,2),i=s[0],m=s[1];return Array.isArray(c)?(t=c.length,a=r.a.createElement("div",null,c.map((function(e){return r.a.createElement("div",{key:e.name},r.a.createElement("div",null,e.value||""," ",e.name||""),r.a.createElement("br",null))})))):(t=c,a=c),r.a.createElement("div",{className:"wallet-table__item"},r.a.createElement("div",{className:lt()("wallet-table-item-header",{"wallet-table-item-header--opened":i})},r.a.createElement("div",{className:"wallet-table-item-header__title"},l),r.a.createElement("div",{className:"wallet-table-item-header__right-side"},!i&&r.a.createElement("div",{className:"wallet-table-item-header__opacity-paragraph"},t),r.a.createElement("a",{className:"wallet-table-item-header__link",onClick:function(){return m((function(e){return!e}))}},!i&&"More",i&&r.a.createElement(jt,null)))),i&&r.a.createElement("div",{className:"wallet-table-item-body"},a))},Qt=Object(s.b)((function(e){return{walletBalance:K(e),ergPrice:U(e),walletAddresses:R(e),explorerSubdomain:x(e)}}),(function(e){return{dispatchGetWalletBalance:function(){return e(Z.getWalletBalance())},dispatchGetErgPrice:function(){return e(Z.getErgPrice())},dispatchGetWalletAddresses:function(){return e(Z.getWalletAddresses())}}}))((function(e){var t=e.walletBalance,a=e.dispatchGetWalletBalance,l=e.dispatchGetErgPrice,c=e.dispatchGetWalletAddresses,o=e.walletAddresses,s=e.explorerSubdomain,i=Object(n.useCallback)((function(){a(),l(),c()}),[a,l,c]),m=Object(n.useCallback)((function(e,t){return 0===e.length?0:e.map((function(e){return{value:r.a.createElement("a",{rel:"noopener noreferrer",target:"_blank",href:"https://".concat(t,".ergoplatform.com/en/addresses/").concat(e)},e)}}))}),[]),u=Object(n.useCallback)((function(e){return 0===Object.values(e).length?0:Object.keys(e).map((function(t){return{name:r.a.createElement("span",{className:"text-muted"},t),value:r.a.createElement("span",{className:"font-weight-bold"},e[t])}}))}),[]);Object(n.useEffect)((function(){i()}),[i]);var d=Object(n.useMemo)((function(){return[{name:"Balance",value:t?"".concat(t.balance/w," ERG"):"loading..."},{name:"Assets",value:t?u(t.assets):"Loading..."},{name:"Addresses",value:o?m(o,s):"Loading..."}]}),[t,u,o,m,s]),p=Object(n.useCallback)((function(){i()}),[i]);return r.a.createElement("div",{className:"wallet-table"},r.a.createElement("div",{className:"wallet-table__header"},r.a.createElement("h2",{className:"wallet-table__title"},"Wallet Information"," ",r.a.createElement("button",{type:"button",className:"wallet-table__icon-redo",onClick:p},r.a.createElement(Jt,null)))),r.a.createElement("div",{className:"wallet-table__body"},d.map((function(e){var t=e.value,a=e.name;return r.a.createElement($t,{key:a,name:a,value:t})}))))})),Xt=(a(139),function(e){Object(j.a)(a,e);var t=Object(k.a)(a);function a(){var e;Object(N.a)(this,a);for(var n=arguments.length,l=new Array(n),c=0;c0&&void 0!==arguments[0]?arguments[0]:Math.random().toString(32).slice(2,10),t="id-".concat(e),a=Ze().isBrowser,r=Object(n.useState)(a?Je(t):null),l=Object(J.a)(r,2),c=l[0],o=l[1];return Object(n.useEffect)((function(){var e=document.querySelector("#".concat(t)),a=e||Je(t);e||document.body.appendChild(a),o(a)}),[]),c},Qe={scrollLayer:!1},Xe=new Map,et=function(e){return!!(e.touches&&e.touches.length>1)||(e.preventDefault(),!1)},tt=function(e,t){if("undefined"===typeof document)return[!1,function(e){return e}];var a=e||Object(n.useRef)(document.body),r=Object(n.useState)(!1),l=Object(J.a)(r,2),c=l[0],o=l[1],s=Object(u.a)(Object(u.a)({},Qe),t||{}),i=function(){return!s.scrollLayer&&(!("undefined"===typeof window||!window.navigator)&&/iP(ad|hone|od)/.test(window.navigator.platform))};return Object(n.useEffect)((function(){if(a&&a.current){var e=a.current.style.overflow;if(c){if(Xe.has(a.current))return;return i()?document.addEventListener("touchmove",et,{passive:!1}):a.current.style.overflow="hidden",void Xe.set(a.current,{last:e})}if(Xe.has(a.current)){if(i())document.removeEventListener("touchmove",et);else{var t=Xe.get(a.current);a.current.style.overflow=t.last}Xe.delete(a.current)}}}),[c,a]),[c,o]},at=function(e){var t=Object(n.useState)((function(){return"function"===typeof e?e():e})),a=Object(J.a)(t,2),r=a[0],l=a[1],c=Object(n.useRef)(e);Object(n.useEffect)((function(){c.current=r}),[r]);return[r,function(e){var t="function"===typeof e?e(c.current):e;c.current=t,l(t)},c]},nt=a(66),rt=a(4),lt=a.n(rt),ct=function(e){var t=e.children,a=e.variant,n=e.component,l=e.color,c=e.className,o=e.dangerouslySetInnerHTML,s=Object(nt.a)(e,["children","variant","component","color","className","dangerouslySetInnerHTML"]),i=a,m=n||"p";return o?r.a.createElement(r.a.Fragment,null,r.a.createElement(m,Object.assign({className:lt()({colored:l},i,c),dangerouslySetInnerHTML:o},s)),r.a.createElement("style",null,"\n .colored {\n color: var(--".concat(l,");\n }\n "))):r.a.createElement(m,Object.assign({className:lt()({colored:l},i,c)},s),t,r.a.createElement("style",null,"\n .colored {\n color: var(--".concat(l,");\n }\n ")))},ot=function(e,t){return e.defaultProps=t,e},st=ot((function(e){var t=e.children,a=e.className,l=e.visible,c=e.enterTime,o=e.leaveTime,s=e.clearTime,i=e.name,m=Object(nt.a)(e,["children","className","visible","enterTime","leaveTime","clearTime","name"]),d=Object(n.useState)(""),p=Object(J.a)(d,2),f=p[0],b=p[1],h=Object(n.useState)(l),E=Object(J.a)(h,2),v=E[0],y=E[1];return Object(n.useEffect)((function(){var e=l?"enter":"leave",t=l?c:o;l&&!v&&y(!0),b("".concat(i,"-").concat(e));var a=setTimeout((function(){b("".concat(i,"-").concat(e," ").concat(i,"-").concat(e,"-active")),clearTimeout(a)}),t),n=setTimeout((function(){l||(b(""),y(!1)),clearTimeout(n)}),t+s);return function(){clearTimeout(a),clearTimeout(n)}}),[l,v]),r.a.isValidElement(t)&&v?r.a.cloneElement(t,Object(u.a)(Object(u.a)({},m),{},{className:"".concat(t.props.className," ").concat(a," ").concat(f)})):null}),{visible:!1,enterTime:60,leaveTime:60,clearTime:60,className:"",name:"transition"}),it=a(68),mt=a.n(it),ut=ot(r.a.memo((function(e){var t=e.children,a=e.onClick,l=e.visible,c=e.className,o=at(!1),s=Object(J.a)(o,3),i=s[1],m=s[2],u=Object(n.useCallback)((function(e){e.stopPropagation()}),[]);return r.a.createElement(st,{visible:l,clearTime:300},r.a.createElement("div",{className:mt.a.backdrop,onClick:function(e){m.current||a&&a(e)},onMouseUp:function(){if(m.current)var e=setTimeout((function(){i(!1),clearTimeout(e)}),0)}},r.a.createElement("div",{className:mt.a.layer}),r.a.createElement("div",{onClick:u,className:lt()(mt.a.content,c),onMouseDown:function(){return i(!0)}},t)))})),{onClick:function(){},visible:!1}),dt=r.a.createContext({});function pt(){return(pt=Object.assign||function(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var bt=r.a.createElement("path",{d:"M10.7951 9.30799C10.3844 8.89734 9.71865 8.89734 9.30799 9.30799C8.89734 9.71865 8.89734 10.3844 9.30799 10.7951L14.5129 16L9.30799 21.2049C8.89734 21.6156 8.89734 22.2814 9.30799 22.692C9.71865 23.1027 10.3844 23.1027 10.7951 22.692L16 17.4871L21.2049 22.692C21.6156 23.1027 22.2814 23.1027 22.692 22.692C23.1027 22.2814 23.1027 21.6156 22.692 21.2049L17.4871 16L22.692 10.7951C23.1027 10.3844 23.1027 9.71865 22.692 9.30799C22.2814 8.89734 21.6156 8.89734 21.2049 9.30799L16 14.5129L10.7951 9.30799Z",fill:"#76767A"}),ht=function(e){var t=e.svgRef,a=e.title,n=ft(e,["svgRef","title"]);return r.a.createElement("svg",pt({width:32,height:32,viewBox:"0 0 32 32",fill:"none",ref:t},n),a?r.a.createElement("title",null,a):null,bt)},Et=r.a.forwardRef((function(e,t){return r.a.createElement(ht,pt({svgRef:t},e))}));a.p;a.p;function vt(){return(vt=Object.assign||function(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var gt=r.a.createElement("path",{d:"M13.3332 7.99992C13.3332 8.36811 13.0347 8.66659 12.6665 8.66659H3.33317C2.96498 8.66659 2.6665 8.36811 2.6665 7.99992C2.6665 7.63173 2.96498 7.33325 3.33317 7.33325H12.6665C13.0347 7.33325 13.3332 7.63173 13.3332 7.99992Z",fill:"#0078FF"}),wt=function(e){var t=e.svgRef,a=e.title,n=yt(e,["svgRef","title"]);return r.a.createElement("svg",vt({width:16,height:16,viewBox:"0 0 16 16",fill:"none",ref:t},n),a?r.a.createElement("title",null,a):null,gt)},Nt=r.a.forwardRef((function(e,t){return r.a.createElement(wt,vt({svgRef:t},e))})),Ot=(a.p,function(e,t){return r.a.createElement(e,{className:t,focusable:"false"})}),jt=function(e){var t=e.className;return Ot(Nt,t)},kt=function(e){var t=e.className;return Ot(Et,t)},St=function(e){var t=e.title,a=e.description,c=e.primaryButtonContent,o=e.secondaryButtonContent,s=e.disableBackdropClick,i=e.onClose,m=e.onOpen,u=e.onPrimaryHandler,d=e.onSecondaryHandler,p=e.open,f=$e("modal"),b=tt(null,{scrollLayer:!0}),h=Object(J.a)(b,2)[1],E=at(!1),v=Object(J.a)(E,3),y=v[0],g=v[1],w=v[2],N=Object(n.useCallback)((function(){i&&i(),g(!1),h(!1)}),[i,g,h]);Object(n.useEffect)((function(){void 0!==p&&(p&&m&&m(),!p&&w.current&&i&&i(),g(p),h(p))}),[p]);var O=Object(n.useMemo)((function(){return{close:N}}),[N]);return f?Object(l.createPortal)(r.a.createElement(dt.Provider,{value:O},r.a.createElement(ut,{onClick:function(){s||N()},visible:y,className:"info-modal-backdrop"},r.a.createElement(st,{name:"wrapper",visible:y,clearTime:300},r.a.createElement("div",{className:"info-modal"},r.a.createElement("div",{className:"info-modal__content"},r.a.createElement("h3",{className:"mb-3"},t),r.a.createElement(ct,{xl:"body-text1",sm:"small-text1",className:"mb-40 mb-md-56 pr-40 pr-md-0"},a),r.a.createElement("button",{type:"button",className:"btn btn-primary px-4",onClick:function(){u&&u(),i&&i()}},c),r.a.createElement("button",{type:"button",className:"btn btn-outline-secondary px-4 ml-3",onClick:function(){d&&d(),i&&i()}},o)),r.a.createElement("button",{type:"button",className:"info-modal__button--close",onClick:N},r.a.createElement(kt,null)),r.a.createElement("style",null,"\n .wrapper-enter {\n opacity: 0;\n transform: translate3d(0px, -40px, 0px);\n }\n .wrapper-enter-active {\n opacity: 1;\n transform: translate3d(0px, 0px, 0px);\n }\n .wrapper-leave {\n opacity: 1;\n transform: translate3d(0px, 0px, 0px);\n }\n .wrapper-leave-active {\n opacity: 0;\n transform: translate3d(0px, -50px, 0px);\n }\n "))))),f):null};St.defaultProps={width:"26rem",wrapClassName:"",disableBackdropClick:!1};var _t=St,Ct=function(e){var t=e.apiKey,a=e.walletBalanceData,l=e.getWalletBalance,c=e.explorerSubdomain,o=null===a||void 0===a?void 0:a.balance,s=Object(n.useState)(null),i=Object(J.a)(s,2),m=i[0],u=i[1],d=Object(n.useState)(!1),p=Object(J.a)(d,2),f=p[0],b=p[1],h=Object(n.useState)(!1),E=Object(J.a)(h,2),v=E[0],y=E[1],g=Object(n.useCallback)((function(e){var a=e.recipientAddress,n=e.amount,r=e.fee,l=e.asset,c=e.assetAmount,o={address:a.trim(),value:Number((parseFloat(n)*w).toFixed(1)),assets:v&&"none"!==l&&c>0?[{tokenId:l,amount:Number(c)}]:[]};return le.post("/wallet/transaction/send",{requests:[o],fee:Number((parseFloat(r)*w).toFixed(1))},{headers:{api_key:t}})}),[v,t]),N=Object(n.useCallback)((function(e){""!==e.recipientAddress.trim()&&e.recipientAddress&&g(e).then((function(e){var t=e.data;u(t),b(!0),l()})).catch((function(e){var t=e.data?e.data.detail:e.message;oe("error",t)}))}),[g,l]),O=Object(n.useCallback)((function(e){var t,n={},r=(Number(e.amount)+Number(e.fee))*w;return e.recipientAddress&&""!==(null===(t=e.recipientAddress)||void 0===t?void 0:t.trim())||(n.recipientAddress="The field cannot be empty"),(!e.fee||e.fee<.001)&&(n.fee="Minimum 0.001 ERG"),"none"===e.asset&&(n.asset="You need to choose asset"),a&&e.assetAmount&&"none"!==e.asset&&e.assetAmount>a.assets[e.asset]&&(n.assetAmount="Maximum ".concat(a.assets[e.asset])),v&&!e.assetAmount&&(n.assetAmount="The field can't be empty"),o0&&r.a.createElement("div",{className:"form-check mb-2"},r.a.createElement("input",{className:"form-check-input",type:"checkbox",checked:v,onChange:function(e){y(e.target.checked)},id:"assetCheckbox"}),r.a.createElement("label",{className:"form-check-label",htmlFor:"assetCheckbox"},"Add asset")),v&&r.a.createElement(r.a.Fragment,null,r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"asset"},"Asset"),r.a.createElement(qe.a,{name:"asset",component:"select",className:lt()("form-control")},r.a.createElement("option",{value:"none"},"Choose asset"),Object.keys((null===a||void 0===a?void 0:a.assets)||{}).map((function(e){return r.a.createElement("option",{key:e,value:e},e)})))),i.asset&&"none"!==i.asset&&r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"assetAmount"},"Asset amount"),r.a.createElement(qe.a,{name:"assetAmount",className:lt()("form-control"),render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"assetAmount",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),type:"float",placeholder:"0,000"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}}))),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"fee"},"Fee (in ERG)"),r.a.createElement(qe.a,{name:"fee",value:"0.001",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"fee",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),type:"float",placeholder:"Minimum 0.001 ERG"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("button",{type:"submit",className:"btn btn-primary",disabled:n||Object.keys(u).length>0||l||"none"===i.asset},"Send")),r.a.createElement(_t,{open:f,onClose:function(){b(!1)},title:"Payment sent",description:r.a.createElement(r.a.Fragment,null,r.a.createElement("p",null,"Your payment has been sent successfully. The transaction ID is -"," ",r.a.createElement(ke,null,m)),r.a.createElement("p",null,r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://".concat(c,".ergoplatform.com/en/transactions/").concat(m)},"Click Here To Go To The Explorer"))),primaryButtonContent:r.a.createElement("span",{className:"pl-3 pr-3"},"OK"),secondaryButtonContent:"Send again",onPrimaryHandler:function(){return function(e){e.restart(),b(!1)}(s)}}))}})))},xt=function(e){var t=e.apiKey,a=e.getWalletBalance,l=e.explorerSubdomain,c=Object(n.useState)(null),o=Object(J.a)(c,2),s=o[0],i=o[1],m=Object(n.useState)(!1),u=Object(J.a)(m,2),d=u[0],p=u[1],f=Object(n.useState)(null),b=Object(J.a)(f,2),h=b[0],E=b[1],v=Object(n.useState)(null),y=Object(J.a)(v,2),g=y[0],N=y[1],O=Object(n.useCallback)((function(e){var a=e.name,n=e.amount,r=e.decimals,l=e.description,c=e.fee;E(n),N(a);var o={name:a,amount:n,decimals:r,description:l};return le.post("/wallet/transaction/send",{requests:[o],fee:Number((parseFloat(c)*w).toFixed(1))},{headers:{api_key:t}})}),[t,E,N]),j=Object(n.useCallback)((function(e){return O(e).then((function(e){var t=e.data;i(t),p(!0),a()})).catch((function(e){var t=e.data?e.data.detail:e.message;oe("error",t)}))}),[O,a]);return r.a.createElement("div",{className:"card bg-white p-4"},r.a.createElement("h2",{className:"h5 mb-3"},"Issue Tokens"),r.a.createElement(qe.b,{onSubmit:j,validate:function(e){var t={};return e.name||(t.name="The field cannot be empty"),e.amount||(t.amount="The field cannot be empty"),e.decimals||(t.decimals="The field cannot be empty"),e.description||(t.description="The field cannot be empty"),(!e.fee||Number(e.fee)<.001)&&(t.fee="Minimum 0.001 ERG"),!Number.isInteger(Number(e.amount))&&e.amount&&(t.amount="Should be an integer"),!Number.isInteger(Number(e.decimals))&&e.decimals&&(t.decimals="Should be an integer"),Number(e.fee)<0&&(t.fee="Fee can't be negative"),t},initialValues:{fee:.001},render:function(e){var t=e.handleSubmit,a=e.submitting,n=e.pristine,c=e.form,o=e.errors;return r.a.createElement(r.a.Fragment,null,r.a.createElement("form",{onSubmit:t},r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"name"},"Asset name"),r.a.createElement(qe.a,{name:"name",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"name",type:"text",placeholder:"Enter asset name",className:lt()("form-control",{"is-invalid":a.touched&&a.error})},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"amount"},"Net amount"),r.a.createElement(qe.a,{name:"amount",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"amount",type:"text",placeholder:"Enter net amount",className:lt()("form-control",{"is-invalid":a.touched&&a.error})},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"decimals"},"Decimal places"),r.a.createElement(qe.a,{name:"decimals",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"decimals",type:"text",placeholder:"Enter decimals as integer",className:lt()("form-control",{"is-invalid":a.touched&&a.error})},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"description"},"Brief description"),r.a.createElement(qe.a,{name:"description",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("textarea",Object.assign({id:"description",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),placeholder:"Add asset description"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("div",{className:"mb-3"},r.a.createElement("label",{htmlFor:"fee"},"Fee (in ERG)"),r.a.createElement(qe.a,{name:"fee",value:"0.001",render:function(e){var t=e.input,a=e.meta;return r.a.createElement(r.a.Fragment,null,r.a.createElement("input",Object.assign({id:"fee",className:lt()("form-control",{"is-invalid":a.touched&&a.error}),type:"float",placeholder:"Minimum 0.001 ERG"},t)),r.a.createElement("div",{className:"invalid-feedback"},a.error))}})),r.a.createElement("button",{type:"submit",className:"btn btn-primary",disabled:a||Object.keys(o).length>0||n},"Issue")),r.a.createElement(_t,{open:d,onClose:function(){p(!1)},title:"Congratulations!",description:r.a.createElement(r.a.Fragment,null,r.a.createElement("p",null,"You have successfully issued ".concat(h," ").concat(g," tokens! The transaction ID is\n"),r.a.createElement(ke,null,s)),r.a.createElement("p",null,r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://".concat(l,".ergoplatform.com/en/transactions/").concat(s)},"Click Here To View Transaction"))),primaryButtonContent:r.a.createElement("span",{className:"pl-3 pr-3"},"OK"),secondaryButtonContent:"Send again",onPrimaryHandler:function(){return function(e){e.restart(),p(!1)}(c)}}))}}))};a(138);function Wt(){return(Wt=Object.assign||function(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var It=r.a.createElement("g",null,r.a.createElement("path",{d:"M39.622,21.746l-6.749,6.75c-0.562,0.562-1.326,0.879-2.122,0.879s-1.56-0.316-2.121-0.879l-6.75-6.75 c-1.171-1.171-1.171-3.071,0-4.242c1.171-1.172,3.071-1.172,4.242,0l1.832,1.832C27.486,13.697,22.758,9.25,17,9.25 c-6.064,0-11,4.935-11,11c0,6.064,4.936,11,11,11c1.657,0,3,1.343,3,3s-1.343,3-3,3c-9.373,0-17-7.626-17-17s7.627-17,17-17 c8.936,0,16.266,6.933,16.936,15.698l1.442-1.444c1.172-1.172,3.072-1.172,4.242,0C40.792,18.674,40.792,20.574,39.622,21.746z"})),Bt=r.a.createElement("g",null),Pt=r.a.createElement("g",null),Ft=r.a.createElement("g",null),zt=r.a.createElement("g",null),Tt=r.a.createElement("g",null),Lt=r.a.createElement("g",null),Mt=r.a.createElement("g",null),Ht=r.a.createElement("g",null),Kt=r.a.createElement("g",null),Rt=r.a.createElement("g",null),Ut=r.a.createElement("g",null),Dt=r.a.createElement("g",null),Gt=r.a.createElement("g",null),Vt=r.a.createElement("g",null),Yt=r.a.createElement("g",null),qt=function(e){var t=e.svgRef,a=e.title,n=At(e,["svgRef","title"]);return r.a.createElement("svg",Wt({id:"Capa_1",x:"0px",y:"0px",width:"32px",height:"32px",viewBox:"0 0 40.499 40.5",style:{enableBackground:"new 0 0 40.499 40.5"},xmlSpace:"preserve",ref:t},n),a?r.a.createElement("title",null,a):null,It,Bt,Pt,Ft,zt,Tt,Lt,Mt,Ht,Kt,Rt,Ut,Dt,Gt,Vt,Yt)},Zt=r.a.forwardRef((function(e,t){return r.a.createElement(qt,Wt({svgRef:t},e))})),Jt=(a.p,function(e){var t=e.className;return Ot(Zt,t)}),$t=function(e){var t,a,l=e.name,c=e.value,o=Object(n.useState)(!1),s=Object(J.a)(o,2),i=s[0],m=s[1];return Array.isArray(c)?(t=c.length,a=r.a.createElement("div",null,c.map((function(e){return r.a.createElement("div",{key:e.name},r.a.createElement("div",null,e.value||""," ",e.name||""),r.a.createElement("br",null))})))):(t=c,a=c),r.a.createElement("div",{className:"wallet-table__item"},r.a.createElement("div",{className:lt()("wallet-table-item-header",{"wallet-table-item-header--opened":i})},r.a.createElement("div",{className:"wallet-table-item-header__title"},l),r.a.createElement("div",{className:"wallet-table-item-header__right-side"},!i&&r.a.createElement("div",{className:"wallet-table-item-header__opacity-paragraph"},t),r.a.createElement("a",{className:"wallet-table-item-header__link",onClick:function(){return m((function(e){return!e}))}},!i&&"More",i&&r.a.createElement(jt,null)))),i&&r.a.createElement("div",{className:"wallet-table-item-body"},a))},Qt=Object(s.b)((function(e){return{walletBalance:K(e),ergPrice:U(e),walletAddresses:R(e),explorerSubdomain:x(e)}}),(function(e){return{dispatchGetWalletBalance:function(){return e(Z.getWalletBalance())},dispatchGetErgPrice:function(){return e(Z.getErgPrice())},dispatchGetWalletAddresses:function(){return e(Z.getWalletAddresses())}}}))((function(e){var t=e.walletBalance,a=e.dispatchGetWalletBalance,l=e.dispatchGetErgPrice,c=e.dispatchGetWalletAddresses,o=e.walletAddresses,s=e.explorerSubdomain,i=Object(n.useCallback)((function(){a(),l(),c()}),[a,l,c]),m=Object(n.useCallback)((function(e,t){return 0===e.length?0:e.map((function(e){return{value:r.a.createElement("a",{rel:"noopener noreferrer",target:"_blank",href:"https://".concat(t,".ergoplatform.com/en/addresses/").concat(e)},e)}}))}),[]),u=Object(n.useCallback)((function(e){return 0===Object.values(e).length?0:Object.keys(e).map((function(t){return{name:r.a.createElement("span",{className:"text-muted"},t),value:r.a.createElement("span",{className:"font-weight-bold"},e[t])}}))}),[]);Object(n.useEffect)((function(){i()}),[i]);var d=Object(n.useMemo)((function(){return[{name:"Balance",value:t?"".concat(t.balance/w," ERG"):"loading..."},{name:"Assets",value:t?u(t.assets):"Loading..."},{name:"Addresses",value:o?m(o,s):"Loading..."}]}),[t,u,o,m,s]),p=Object(n.useCallback)((function(){i()}),[i]);return r.a.createElement("div",{className:"wallet-table"},r.a.createElement("div",{className:"wallet-table__header"},r.a.createElement("h2",{className:"wallet-table__title"},"Wallet Information"," ",r.a.createElement("button",{type:"button",className:"wallet-table__icon-redo",onClick:p},r.a.createElement(Jt,null)))),r.a.createElement("div",{className:"wallet-table__body"},d.map((function(e){var t=e.value,a=e.name;return r.a.createElement($t,{key:a,name:a,value:t})}))))})),Xt=(a(139),function(e){Object(j.a)(a,e);var t=Object(k.a)(a);function a(){var e;Object(N.a)(this,a);for(var n=arguments.length,l=new Array(n),c=0;c {\n state.network = payload;\n },\n },\n});\n","import { createAction } from 'redux-starter-kit';\nimport nodeSlice from '../slices/nodeSlice';\n\nconst getNetwork = createAction('getNetwork');\n\nexport default {\n ...nodeSlice.actions,\n getNetwork,\n};\n","export default {\n swaggerInterface: '/swagger',\n website: 'https://ergoplatform.org',\n nanoErgInErg: 1000000000,\n};\n","import { createSelector } from 'redux-starter-kit';\n\nexport const nodeSelector = (state) => state.node;\n\nexport const networkSelector = createSelector(nodeSelector, (node) => node.network);\n\nexport const explorerSelector = createSelector(nodeSelector, (node) =>\n node.network === 'mainnet' ? 'explorer' : node.network,\n);\n","import React, { Component } from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { connect } from 'react-redux';\nimport clsx from 'clsx';\nimport { faWpexplorer } from '@fortawesome/free-brands-svg-icons';\nimport { explorerSelector } from 'store/selectors/node';\n\nconst mapStateToProps = (state) => ({ explorerSubdomain: explorerSelector(state) });\n\nconst icon = ;\nconst title = 'Explorer';\n\nclass Explorer extends Component {\n render() {\n const { explorerSubdomain } = this.props;\n\n return (\n \n {icon} {title}\n \n );\n }\n}\n\nexport default connect(mapStateToProps, null)(Explorer);\n","import React from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faChartLine, faExchangeAlt, faGlobe, faBook } from '@fortawesome/free-solid-svg-icons';\nimport clsx from 'clsx';\nimport { withRouter, Link } from 'react-router-dom';\nimport constants from '../../../utils/constants';\nimport Explorer from './components/Explorer';\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 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(({ 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\nexport const walletStatusDataSelector = createSelector(\n walletSelector,\n (wallet) => wallet.walletStatusData,\n);\n\nexport const walletBalanceDataSelector = createSelector(\n walletSelector,\n (wallet) => wallet.walletBalanceData,\n);\n\nexport const walletAddressesSelector = createSelector(\n walletSelector,\n (wallet) => wallet.walletAddresses,\n);\n\nexport const ergPriceSelector = createSelector(walletSelector, (wallet) => wallet.ergPrice);\n","import { createSlice } from 'redux-starter-kit';\n\nconst initialState = {\n isWalletUnlocked: null,\n isWalletInitialized: null,\n walletStatusData: null,\n walletBalanceData: null,\n ergPrice: null,\n walletAddresses: 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 setWalletStatusData: (state, { payload }) => {\n state.walletStatusData = payload;\n },\n setWalletBalanceData: (state, { payload }) => {\n state.walletBalanceData = payload;\n },\n setErgPrice: (state, { payload }) => {\n state.ergPrice = payload;\n },\n setWalletAddresses: (state, { payload }) => {\n state.walletAddresses = payload;\n },\n },\n});\n","import { createAction } from 'redux-starter-kit';\nimport walletSlice from '../slices/walletSlice';\n\nconst checkWalletStatus = createAction('checkWalletStatus');\nconst getWalletBalance = createAction('getWalletBalance');\nconst getErgPrice = createAction('getErgPrice');\nconst getWalletAddresses = createAction('getWalletAddresses');\n\nexport default {\n ...walletSlice.actions,\n checkWalletStatus,\n getWalletBalance,\n getErgPrice,\n getWalletAddresses,\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 oracleApiLink: 'https://erg-usd-ergo-oracle.emurgo.io',\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] ? toastStates[state](text, options) : new Error(`Bad toast state`);\n","import React from 'react';\nimport { Modal } from 'react-bootstrap';\nimport { Formik, Form, Field } from 'formik';\nimport { v4 as uuidv4 } from 'uuid';\n\nconst renderButton = (apiKey, handleShow) => {\n if (apiKey === '') {\n return (\n \n );\n }\n\n return (\n \n );\n};\n\nconst ApiKeyModalView = ({ showModal, handleHide, submitForm, apiKey, handleShow }) => {\n const uuid = uuidv4();\n return (\n
\n {renderButton(apiKey, handleShow)}\n handleHide()} centered>\n submitForm(values, uuid)}\n >\n {() => (\n
\n \n Authorization\n \n \n

Set API key to access Node requests

\n
\n \n
\n
\n\n \n \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, uuid) => {\n // Check API key for random get method\n nodeApi\n .get('/wallet/status', {\n headers: {\n api_key: values[`apiKey${uuid}`],\n },\n })\n .then(() => {\n dispatchSetApiKey(values[`apiKey${uuid}`].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(mapStateToProps, mapDispatchToProps)(memo(ApiKeyModalContainer));\n","export const MODAL_STATES = {\n INIT: 'INIT',\n STATUS: 'STATUS',\n};\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';\nimport { MODAL_STATES } from '../utils';\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.props.onOpen(MODAL_STATES.STATUS);\n this.setState({ showModal: true });\n };\n\n handleHide = () => {\n this.props.onOpen(null);\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 = ({ pass }, { setSubmitting, resetForm, setStatus }) => {\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 );\n }\n\n return (\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 Unlock wallet form\n \n \n \n
\n \n \n \n * If you have it or leave field empty\n \n
\n
\n\n \n \n Close\n \n \n \n
\n )}\n
\n \n
\n );\n }\n}\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(WalletStatusForm));\n","import React from 'react';\nimport copy from 'clipboard-copy';\nimport { Overlay, Tooltip } from 'react-bootstrap';\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 componentWillUnmount() {\n clearTimeout(this.state.timerId);\n }\n\n startTimer = () => {\n const timerId = setTimeout(() => this.setState({ showTooltip: false }), 1500);\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 render() {\n return (\n <>\n \n {this.props.children}\n \n \n Copied!\n \n \n );\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 {status && status.state === 'error' && (\n
\n {status.msg}\n
\n )}\n {status && status.state === 'success' && this.state.isShowMnemonic && (\n
\n this.setState({ isShowMnemonic: false })}\n >\n ×\n \n {status.msg}\n
\n )}\n
\n \n \n
\n
\n \n \n
\n \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 { v4 as uuidv4 } from 'uuid';\nimport nodeApi from '../../../api/api';\nimport customToast from '../../../utils/toast';\n\nconst initialFormValues = {\n walletPassword: '',\n mnemonicPass: '',\n mnemonic: '',\n};\n\nclass RestoreWalletForm extends Component {\n walletRestore = async (values, uuid) => {\n const { walletPassword, mnemonicPass = '' } = values;\n if (!values[`mnemonic${uuid}`] || !String(values[`mnemonic${uuid}`]).trim()) {\n throw Error('Need to set mnemonic');\n }\n\n return nodeApi.post(\n '/wallet/restore',\n {\n pass: walletPassword || '',\n mnemonicPass: mnemonicPass || '',\n mnemonic: values[`mnemonic${uuid}`],\n },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n );\n };\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }, uuid) => {\n setStatus({ status: 'submitting' });\n this.walletRestore(values, uuid)\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 const uuid = uuidv4();\n\n return (\n
\n

Re-store wallet

\n this.handleSubmit(values, props, uuid)}\n >\n {({ status, isSubmitting }) => (\n
\n {status && status.state === 'error' && (\n
\n {status.msg}\n
\n )}\n {status && status.state === 'success' && (\n
{status.msg}
\n )}\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n
\n )}\n \n
\n );\n }\n}\n\nexport default memo(RestoreWalletForm);\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';\nimport { MODAL_STATES } from '../utils';\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.props.onOpen(MODAL_STATES.INIT);\n this.setState({ showModal: true });\n };\n\n handleHide = () => {\n this.props.onOpen(null);\n this.props.dispatchCheckWalletStatus();\n this.setState({ showModal: false });\n };\n\n renderButton = () => {\n return (\n \n );\n };\n\n render() {\n const { apiKey } = this.props;\n\n return (\n
\n {this.renderButton()}\n this.handleHide()} centered size=\"lg\">\n \n Wallet initialization\n \n \n
\n \n
\n
\n \n
\n
\n \n \n \n
\n
\n );\n }\n}\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(WalletInitModal));\n","import React, { memo, useState } 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';\nimport { MODAL_STATES } from './utils';\n\nconst renderWalletForms = (isWalletInitialized, openedModal, setOpenedModal) => {\n if (isWalletInitialized === null) {\n return <>;\n }\n\n if (isWalletInitialized && openedModal !== MODAL_STATES.INIT) {\n return (\n
\n \n
\n );\n }\n\n return (\n
\n \n
\n );\n};\n\nconst HeaderView = ({ isApiKeySetted, isWalletInitialized }) => {\n const [openedModal, setOpenedModal] = useState(null);\n\n return (\n \n \n \n \"logotype\"\n \n \n
\n \n
\n {isApiKeySetted && renderWalletForms(isWalletInitialized, openedModal, setOpenedModal)}\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\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(HeaderContainer));\n","import React from 'react';\nimport { withRouter } from 'react-router-dom';\nimport MenuList from '../common/MenuList';\nimport './index.scss';\nimport Header from '../Header';\n\nexport const Layout = withRouter((props) => {\n return (\n
\n
\n
\n \n
\n
\n
{props.children}
\n
\n
\n );\n});\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';\n\nexport default class SynchCard extends Component {\n shouldComponentUpdate(nextProps) {\n if (\n this.getSynchronizationState(nextProps) !== this.getSynchronizationState(this.props.nodeInfo)\n ) {\n return true;\n }\n\n return false;\n }\n\n renderActiveSynchronization = () => (\n <>\n

Synchronization state

\n

\n Active synchronization\n

\n \n );\n\n renderCompleteSynchronization = () => (\n <>\n

Synchronization 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 (fullHeight !== null && headersHeight !== null && fullHeight === headersHeight) {\n return 'complete';\n }\n\n return 'active';\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, { Component } from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faSync, faCheck, faTimes } from '@fortawesome/free-solid-svg-icons';\nimport InfoCard from '../InfoCard';\n\nexport default class WalletSyncCard extends Component {\n renderActiveSynchronization = () => (\n <>\n

Synchronization state

\n

\n Active synchronization\n

\n \n );\n\n renderCompleteSynchronization = () => (\n <>\n

Synchronization state

\n

\n Wallet is synced\n

\n \n );\n\n renderErrorSynchronization = () => (\n <>\n

Synchronization state

\n

\n Error - {this.props.walletStatusData.error}\n

\n \n );\n\n renderSynchronizationState = (state) =>\n ({\n active: this.renderActiveSynchronization,\n complete: this.renderCompleteSynchronization,\n error: this.renderErrorSynchronization,\n }[state]);\n\n getSynchronizationState = (walletStatusData, headersHeight) => {\n if (walletStatusData.error?.trim().length !== 0) {\n return 'error';\n }\n\n if (\n walletStatusData.walletHeight !== null &&\n headersHeight !== null &&\n walletStatusData.walletHeight === headersHeight\n ) {\n return 'complete';\n }\n\n return 'active';\n };\n\n render() {\n const { walletStatusData, headersHeight } = this.props;\n const currentSynchState = this.getSynchronizationState(walletStatusData, headersHeight);\n return (\n \n {this.renderSynchronizationState(currentSynchState)()}\n \n );\n }\n}\n","import React from 'react';\nimport './index.scss';\n\nconst LoaderLogo = () => {\n return (\n
\n \n \n \n \n
\n );\n};\n\nexport default LoaderLogo;\n","import React from 'react';\nimport { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { format } from 'date-fns';\nimport constants from 'utils/constants';\nimport InfoCard from './InfoCard';\nimport SynchCard from './SynchCard';\nimport WalletSyncCard from './WalletSyncCard';\nimport LoaderLogo from '../../common/ErgoLoader/index';\n\nconst getWalletStatus = (isWalletInitialized) => {\n if (!isWalletInitialized) {\n return 'Not initialized';\n }\n\n return 'Initialized';\n};\n\nconst DashboardView = ({\n error,\n nodeInfo,\n isWalletInitialized,\n walletStatusData,\n walletBalanceData,\n ergPrice,\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 { peersCount, bestHeaderId, launchTime, fullHeight, appVersion, isMining } = nodeInfo;\n\n return (\n <>\n
\n

Node Information

\n
\n
\n \n

Version

\n

{appVersion}

\n
\n
\n
\n \n
\n
\n \n

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 ? 'Yes' : 'No'}

\n
\n
\n
\n \n

Peers connected

\n

{peersCount}

\n
\n
\n
\n
\n {ergPrice && (\n
\n

ERG Information

\n
\n
\n \n

\n ERG price in $
\n (based on oracle pool data)\n

\n

{ergPrice}

\n
\n
\n
\n
\n )}\n {walletStatusData && (\n
\n

Wallet Information

\n
\n
\n \n

Initialization state

\n

{getWalletStatus(isWalletInitialized)}

\n
\n
\n
\n \n

Lock state

\n

\n {walletStatusData.isUnlocked ? 'Unlocked' : 'Locked'}\n

\n
\n
\n
\n \n
\n {walletBalanceData && (\n
\n \n

Balance

\n

\n {walletBalanceData.balance / constants.nanoErgInErg} ERG{' '}\n {ergPrice &&\n `~ $${Number(\n ergPrice * (walletBalanceData.balance / constants.nanoErgInErg),\n ).toFixed(2)}`}\n

\n
\n
\n )}\n {walletBalanceData && (\n
\n \n

Assets

\n

\n {Object.values(walletBalanceData.assets).length || '0'}\n

\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 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 walletStatusDataSelector,\n walletBalanceDataSelector,\n ergPriceSelector,\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 walletStatusData: walletStatusDataSelector(state),\n walletBalanceData: walletBalanceDataSelector(state),\n ergPrice: ergPriceSelector(state),\n});\n\nconst mapDispatchToProps = (dispatch) => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n dispatchGetWalletBalance: () => dispatch(walletActions.getWalletBalance()),\n dispatchGetErgPrice: () => dispatch(walletActions.getErgPrice()),\n});\n\nconst DashboardContainer = (props) => {\n const {\n isWalletInitialized,\n isWalletUnlocked,\n apiKey,\n dispatchCheckWalletStatus,\n dispatchGetWalletBalance,\n dispatchGetErgPrice,\n walletStatusData,\n walletBalanceData,\n ergPrice,\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(() => {\n setNodeCurrentState();\n dispatchGetErgPrice();\n\n if (apiKey) {\n dispatchCheckWalletStatus();\n dispatchGetWalletBalance();\n }\n }, 2000);\n\n setTimerId(newTimerId);\n }, [\n apiKey,\n dispatchCheckWalletStatus,\n dispatchGetErgPrice,\n dispatchGetWalletBalance,\n setNodeCurrentState,\n ]);\n\n const prevError = usePrevious(error);\n useEffect(() => {\n if (prevError && prevError !== error) {\n dispatchCheckWalletStatus();\n dispatchGetWalletBalance();\n dispatchGetErgPrice();\n }\n }, [dispatchCheckWalletStatus, dispatchGetErgPrice, dispatchGetWalletBalance, error, prevError]);\n\n useEffect(() => {\n setNodeCurrentState();\n dispatchGetErgPrice();\n\n if (apiKey) {\n dispatchCheckWalletStatus();\n dispatchGetWalletBalance();\n }\n\n setTimer();\n // eslint-disable-next-line\n }, [apiKey]);\n\n useEffect(\n () => () => {\n clearInterval(timerId);\n },\n [timerId, apiKey],\n );\n\n return (\n \n );\n};\n\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(DashboardContainer));\n","import DashboardContainer from './DashboardContainer';\nimport './index.scss';\n\nexport default DashboardContainer;\n","import { useEffect, useState } from 'react';\n\nconst isBrowser = (): boolean => {\n return Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);\n};\n\ntype SSRState = {\n isBrowser: boolean;\n isServer: boolean;\n};\n\nconst useSSR = (): SSRState => {\n const [browser, setBrowser] = useState(false);\n useEffect(() => {\n setBrowser(isBrowser());\n }, []);\n\n return {\n isBrowser: browser,\n isServer: !browser,\n };\n};\n\nconst createElement = (id: string): HTMLElement => {\n const el = document.createElement('div');\n el.setAttribute('id', id);\n return el;\n};\n\nconst usePortal = (\n selectId: string = Math.random().toString(32).slice(2, 10),\n): HTMLElement | null => {\n const id = `id-${selectId}`;\n const isUsingBrowser = useSSR().isBrowser;\n const [elSnapshot, setElSnapshot] = useState(\n isUsingBrowser ? createElement(id) : null,\n );\n\n useEffect(() => {\n const hasElement = document.querySelector(`#${id}`);\n const el = hasElement || createElement(id);\n\n if (!hasElement) {\n document.body.appendChild(el);\n }\n setElSnapshot(el);\n }, []);\n\n return elSnapshot;\n};\n\nexport default usePortal;\n","import { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react';\n/* eslint-disable */\nexport type ElementStackItem = {\n last: string;\n};\n\nexport type BodyScrollOptions = {\n scrollLayer: boolean;\n};\n\nconst defaultOptions: BodyScrollOptions = {\n scrollLayer: false,\n};\n\nconst elementStack = new Map();\n\nconst isIos = () => {\n /* istanbul ignore next */\n if (typeof window === 'undefined' || !window.navigator) return false;\n return /iP(ad|hone|od)/.test(window.navigator.platform);\n};\n\nconst touchHandler = (event: TouchEvent): boolean => {\n if (event.touches && event.touches.length > 1) return true;\n event.preventDefault();\n return false;\n};\n\nconst useBodyScroll = (\n elementRef?: RefObject | null,\n options?: BodyScrollOptions\n): [boolean, Dispatch>] => {\n if (typeof document === 'undefined')\n return [false, (t: SetStateAction) => t];\n const elRef = elementRef || useRef(document.body);\n const [hidden, setHidden] = useState(false);\n const safeOptions = {\n ...defaultOptions,\n ...(options || {}),\n };\n\n // don't prevent touch event when layer contain scroll\n const isIosWithCustom = () => {\n if (safeOptions.scrollLayer) return false;\n return isIos();\n };\n\n useEffect(() => {\n if (!elRef || !elRef.current) return;\n const lastOverflow = elRef.current.style.overflow;\n if (hidden) {\n if (elementStack.has(elRef.current)) return;\n if (!isIosWithCustom()) {\n elRef.current.style.overflow = 'hidden';\n } else {\n document.addEventListener('touchmove', touchHandler, {\n passive: false,\n });\n }\n elementStack.set(elRef.current, {\n last: lastOverflow,\n });\n return;\n }\n\n // reset element overflow\n if (!elementStack.has(elRef.current)) return;\n if (!isIosWithCustom()) {\n const store = elementStack.get(elRef.current) as ElementStackItem;\n elRef.current.style.overflow = store.last;\n } else {\n document.removeEventListener('touchmove', touchHandler);\n }\n elementStack.delete(elRef.current);\n }, [hidden, elRef]);\n\n return [hidden, setHidden];\n};\n\nexport default useBodyScroll;\n\n/* eslint-disable */\n","import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react';\n\nexport type CurrentStateType = [S, Dispatch>, MutableRefObject];\n\n// Добавляет ref, по которому текущее значение стейта можно получить\n// сразу вместо того чтобы ждать следующего рендера\nconst useCurrentState = (initialState: S | (() => S)): CurrentStateType => {\n const [state, setState] = useState(() => {\n return typeof initialState === 'function' ? (initialState as () => S)() : initialState;\n });\n const ref = useRef(initialState as S);\n\n useEffect(() => {\n ref.current = state;\n }, [state]);\n\n const setValue = (val: SetStateAction) => {\n const result = typeof val === 'function' ? (val as (prevState: S) => S)(ref.current) : val;\n ref.current = result;\n setState(result);\n };\n\n return [state, setValue, ref];\n};\n\nexport default useCurrentState;\n","import React, { ReactNode } from 'react';\nimport cn from 'classnames';\n\ntype TextVariant =\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'subtitle1'\n | 'subtitle2'\n | 'body-text1'\n | 'body-text2'\n | 'small-text1'\n | 'small-text2'\n | 'small-text3';\n\nexport type TextColor =\n | 'brandOrange'\n | 'purple'\n | 'black'\n | 'spaceGray'\n | 'gray5'\n | 'gray4'\n | 'gray3'\n | 'gray2'\n | 'gray1'\n | 'white'\n | 'orange'\n | 'red'\n | 'green'\n | 'blue'\n | 'blueHover'\n | 'brandOrangeHover'\n | 'brandOrangeActive'\n | 'purpleHover'\n | 'purpleActive';\n\nexport type TextComponent = 'span' | 'p' | 'div' | 'h1' | 'label' | 'h2' | 'h3';\n\ninterface IText {\n children?: ReactNode;\n variant?: TextVariant;\n xl?: TextVariant;\n lg?: TextVariant;\n md?: TextVariant;\n sm?: TextVariant;\n component?: TextComponent;\n color?: TextColor;\n className?: string;\n dangerouslySetInnerHTML?: any;\n htmlFor?: string;\n}\n\nconst Text = ({\n children,\n variant,\n component,\n color,\n className,\n dangerouslySetInnerHTML,\n ...props\n}: IText) => {\n const currentVariant = variant;\n\n const Component = component || 'p';\n\n if (dangerouslySetInnerHTML) {\n return (\n <>\n \n\n \n \n );\n }\n\n return (\n \n {children}\n \n \n );\n};\n\nexport default Text;\n","import React from 'react';\n\n// Прокидывает default параметры в компонент\nconst withDefaults = (component: React.ComponentType

, defaultProps: DP) => {\n type Props = Partial & Omit;\n // eslint-disable-next-line\n component.defaultProps = defaultProps;\n return component as React.ComponentType;\n};\n\nexport default withDefaults;\n","import React, { useEffect, useState } from 'react';\nimport withDefaults from 'utils/withDefaults';\n\ninterface Props {\n visible?: boolean;\n enterTime?: number;\n leaveTime?: number;\n clearTime?: number;\n className?: string;\n name?: string;\n}\n\nconst defaultProps = {\n visible: false,\n enterTime: 60,\n leaveTime: 60,\n clearTime: 60,\n className: '',\n name: 'transition',\n};\n\nexport type CSSTransitionProps = Props & typeof defaultProps;\n\nconst CSSTransition: React.FC> = ({\n children,\n className,\n visible,\n enterTime,\n leaveTime,\n clearTime,\n name,\n ...props\n}) => {\n const [classes, setClasses] = useState('');\n const [renderable, setRenderable] = useState(visible);\n\n useEffect(() => {\n const statusClassName = visible ? 'enter' : 'leave';\n const time = visible ? enterTime : leaveTime;\n if (visible && !renderable) {\n setRenderable(true);\n }\n\n setClasses(`${name}-${statusClassName}`);\n\n // set class to active\n const timer = setTimeout(() => {\n setClasses(`${name}-${statusClassName} ${name}-${statusClassName}-active`);\n clearTimeout(timer);\n }, time);\n\n // remove classess when animation over\n const clearClassesTimer = setTimeout(() => {\n if (!visible) {\n setClasses('');\n setRenderable(false);\n }\n clearTimeout(clearClassesTimer);\n }, time + clearTime);\n\n return () => {\n clearTimeout(timer);\n clearTimeout(clearClassesTimer);\n };\n }, [visible, renderable]);\n if (!React.isValidElement(children) || !renderable) return null;\n\n return React.cloneElement(children, {\n ...props,\n className: `${children.props.className} ${className} ${classes}`,\n });\n};\n\nexport default withDefaults(CSSTransition, defaultProps);\n","import React, { MouseEvent, useCallback, ReactElement } from 'react';\nimport withDefaults from 'utils/withDefaults';\nimport useCurrentState from 'hooks/useCurrentState';\nimport cn from 'classnames';\nimport CssTransition from '../CssTransition/CssTransition';\nimport styles from './Backdrop.module.scss';\n\ninterface Props {\n onClick?: (event: MouseEvent) => void;\n visible?: boolean;\n children?: ReactElement;\n className?: string;\n}\n\nconst defaultProps = {\n onClick: () => {},\n visible: false,\n};\n\nexport type BackdropProps = Props & typeof defaultProps;\n\nconst Backdrop: React.FC> = React.memo(\n ({ children, onClick, visible, className }: BackdropProps) => {\n const [, setIsContentMouseDown, IsContentMouseDownRef] = useCurrentState(false);\n const clickHandler = (event: MouseEvent) => {\n if (IsContentMouseDownRef.current) return;\n if (onClick) {\n onClick(event);\n }\n };\n const childrenClickHandler = useCallback((event: MouseEvent) => {\n event.stopPropagation();\n }, []);\n const mouseUpHandler = () => {\n if (!IsContentMouseDownRef.current) return;\n const timer = setTimeout(() => {\n setIsContentMouseDown(false);\n clearTimeout(timer);\n }, 0);\n };\n\n return (\n \n

\n
\n setIsContentMouseDown(true)}\n >\n {children}\n
\n
\n \n );\n },\n);\n\nexport default withDefaults(Backdrop, defaultProps);\n","import React from 'react';\n\nexport interface ModalConfig {\n close?: () => void;\n}\n\nconst defaultContext = {};\n\nexport const ModalContext = React.createContext(defaultContext);\n\nexport const useModalContext = (): ModalConfig => React.useContext(ModalContext);\n","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M10.7951 9.30799C10.3844 8.89734 9.71865 8.89734 9.30799 9.30799C8.89734 9.71865 8.89734 10.3844 9.30799 10.7951L14.5129 16L9.30799 21.2049C8.89734 21.6156 8.89734 22.2814 9.30799 22.692C9.71865 23.1027 10.3844 23.1027 10.7951 22.692L16 17.4871L21.2049 22.692C21.6156 23.1027 22.2814 23.1027 22.692 22.692C23.1027 22.2814 23.1027 21.6156 22.692 21.2049L17.4871 16L22.692 10.7951C23.1027 10.3844 23.1027 9.71865 22.692 9.30799C22.2814 8.89734 21.6156 8.89734 21.2049 9.30799L16 14.5129L10.7951 9.30799Z\",\n fill: \"#76767A\"\n});\n\nvar SvgClose = function SvgClose(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 32,\n height: 32,\n viewBox: \"0 0 32 32\",\n fill: \"none\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgClose, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/close.feae5a5c.svg\";\nexport { ForwardRef as ReactComponent };","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M10.6668 0.666748H2.66683C1.9335 0.666748 1.3335 1.26675 1.3335 2.00008V11.3334H2.66683V2.00008H10.6668V0.666748ZM10.0002 3.33341L14.0002 7.33341V14.0001C14.0002 14.7334 13.4002 15.3334 12.6668 15.3334H5.32683C4.5935 15.3334 4.00016 14.7334 4.00016 14.0001L4.00683 4.66675C4.00683 3.93341 4.60016 3.33341 5.3335 3.33341H10.0002ZM9.3335 8.00008H13.0002L9.3335 4.33341V8.00008Z\",\n fill: \"#0078FF\"\n});\n\nvar SvgCopyicon = function SvgCopyicon(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 16,\n height: 16,\n viewBox: \"0 0 16 16\",\n fill: \"none\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgCopyicon, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/copy.icon.835ebda7.svg\";\nexport { ForwardRef as ReactComponent };","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M13.3332 7.99992C13.3332 8.36811 13.0347 8.66659 12.6665 8.66659H3.33317C2.96498 8.66659 2.6665 8.36811 2.6665 7.99992C2.6665 7.63173 2.96498 7.33325 3.33317 7.33325H12.6665C13.0347 7.33325 13.3332 7.63173 13.3332 7.99992Z\",\n fill: \"#0078FF\"\n});\n\nvar SvgRemove = function SvgRemove(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 16,\n height: 16,\n viewBox: \"0 0 16 16\",\n fill: \"none\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgRemove, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/remove.94c0849a.svg\";\nexport { ForwardRef as ReactComponent };","import React from 'react';\n\nimport { ReactComponent as copyIcon } from '../../../assets/images/icons/copy.icon.svg';\nimport { ReactComponent as removeIcon } from '../../../assets/images/icons/remove.svg';\n\nexport interface IconProps {\n className?: string;\n}\n\nexport const makeIcon = (Icon: any, className?: string) => {\n return ;\n};\n\nexport const CopyIcon = ({ className }: IconProps) => {\n return makeIcon(copyIcon, className);\n};\n\nexport const RemoveIcon = ({ className }: IconProps) => {\n return makeIcon(removeIcon, className);\n};\n","import { ReactComponent as closeImage } from 'assets/images/icons/close.svg';\nimport { makeIcon, IconProps } from './icons';\n\nexport const CloseIcon = ({ className }: IconProps) => {\n return makeIcon(closeImage, className);\n};\n","import React, { useEffect, useMemo, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport usePortal from 'hooks/usePortal';\nimport useBodyScroll from 'hooks/useBodyScroll';\nimport useCurrentState from 'hooks/useCurrentState';\nimport Text from 'components/common/Text/Text';\nimport Backdrop from '../Backdrop/Backdrop';\nimport { ModalConfig, ModalContext } from './modal-context';\nimport CssTransition from '../CssTransition/CssTransition';\nimport { CloseIcon } from '../icons/CloseIcon';\n\ninterface Props {\n title?: React.ReactNode;\n description?: React.ReactNode;\n primaryButtonContent: React.ReactNode;\n secondaryButtonContent: React.ReactNode;\n disableBackdropClick?: boolean;\n onClose?: () => void;\n onOpen?: () => void;\n onPrimaryHandler?: () => void;\n onSecondaryHandler?: () => void;\n open?: boolean;\n width?: string;\n wrapClassName?: string;\n}\n\nconst defaultProps = {\n width: '26rem',\n wrapClassName: '',\n disableBackdropClick: false,\n};\n\ntype NativeAttrs = Omit, keyof Props>;\nexport type ModalProps = Props & typeof defaultProps & NativeAttrs;\n\nconst InfoModal: React.FC> = ({\n title,\n description,\n primaryButtonContent,\n secondaryButtonContent,\n disableBackdropClick,\n onClose,\n onOpen,\n onPrimaryHandler,\n onSecondaryHandler,\n open,\n}) => {\n const portal = usePortal('modal');\n const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true });\n const [visible, setVisible, visibleRef] = useCurrentState(false);\n\n const closeModal = useCallback(() => {\n if (onClose) {\n onClose();\n }\n setVisible(false);\n setBodyHidden(false);\n }, [onClose, setVisible, setBodyHidden]);\n\n useEffect(() => {\n if (open === undefined) return;\n if (open && onOpen) {\n onOpen();\n }\n if (!open && visibleRef.current && onClose) {\n onClose();\n }\n\n setVisible(open);\n setBodyHidden(open);\n }, [open]);\n\n const closeFromBackdrop = () => {\n if (disableBackdropClick) return;\n closeModal();\n };\n\n const primaryButtonClickHandler = () => {\n if (onPrimaryHandler) {\n onPrimaryHandler();\n }\n if (onClose) {\n onClose();\n }\n };\n\n const secondaryButtonClickHandler = () => {\n if (onSecondaryHandler) {\n onSecondaryHandler();\n }\n if (onClose) {\n onClose();\n }\n };\n\n const modalConfig: ModalConfig = useMemo(\n () => ({\n close: closeModal,\n }),\n [closeModal],\n );\n\n if (!portal) return null;\n\n return createPortal(\n \n \n \n
\n
\n

{title}

\n \n {description}\n \n \n {primaryButtonContent}\n \n \n {secondaryButtonContent}\n \n
\n \n\n \n
\n
\n
\n
,\n portal,\n );\n};\n\ntype ModalComponent

= React.FC

;\ntype ComponentProps = Partial &\n Omit &\n NativeAttrs;\n\nInfoModal.defaultProps = defaultProps;\n\nexport default InfoModal as ModalComponent;\n","import React, { useState, useCallback } from 'react';\nimport { Form, Field } from 'react-final-form';\nimport InfoModal from 'components/common/InfoModal/InfoModal';\nimport cn from 'classnames';\nimport nodeApi from '../../../../../api/api';\nimport customToast from '../../../../../utils/toast';\nimport CopyToClipboard from '../../../../common/CopyToClipboard';\nimport constants from '../../../../../utils/constants';\n\ntype Errors = {\n recipientAddress?: string;\n amount?: string;\n fee?: string;\n asset?: string;\n assetAmount?: string;\n};\n\nconst PaymentSendForm = ({\n apiKey,\n walletBalanceData,\n getWalletBalance,\n explorerSubdomain,\n}: {\n apiKey: string;\n walletBalanceData: any;\n getWalletBalance: any;\n explorerSubdomain: string;\n}) => {\n const currentBalance = walletBalanceData?.balance;\n\n const [transactionId, setTransactionId] = useState(null);\n const [isSentModalOpen, setIsSentModalOpen] = useState(false);\n const [assetCheckbox, setAssetCheckbox] = useState(false);\n\n const paymentSend = useCallback(\n ({ recipientAddress, amount, fee, asset, assetAmount }) => {\n const request = {\n address: recipientAddress.trim(),\n value: Number((parseFloat(amount) * constants.nanoErgInErg).toFixed(1)),\n assets:\n assetCheckbox && asset !== 'none' && assetAmount > 0\n ? [{ tokenId: asset, amount: Number(assetAmount) }]\n : [],\n };\n return nodeApi.post(\n '/wallet/transaction/send',\n {\n requests: [request],\n fee: Number((parseFloat(fee) * constants.nanoErgInErg).toFixed(1)),\n },\n {\n headers: {\n api_key: apiKey,\n },\n },\n );\n },\n [assetCheckbox, apiKey],\n );\n\n const resetForm = (form: any) => {\n form.restart();\n setIsSentModalOpen(false);\n };\n\n const sendForm = useCallback(\n (values) => {\n if (values.recipientAddress.trim() === '' || !values.recipientAddress) {\n return;\n }\n\n paymentSend(values)\n .then(({ data }) => {\n setTransactionId(data);\n setIsSentModalOpen(true);\n getWalletBalance();\n })\n .catch((err) => {\n const errMessage = err.data ? err.data.detail : err.message;\n customToast('error', errMessage);\n });\n },\n [paymentSend, getWalletBalance],\n );\n\n const validateForm = useCallback(\n (values) => {\n const errors: Errors = {};\n\n const totalFeeAndAmount =\n (Number(values.amount) + Number(values.fee)) * constants.nanoErgInErg;\n\n if (!values.recipientAddress || values.recipientAddress?.trim() === '') {\n errors.recipientAddress = 'The field cannot be empty';\n }\n\n if (!values.fee || values.fee < 0.001) {\n errors.fee = 'Minimum 0.001 ERG';\n }\n\n if (values.asset === 'none') {\n errors.asset = 'You need to choose asset';\n }\n\n if (\n walletBalanceData &&\n values.assetAmount &&\n values.asset !== 'none' &&\n values.assetAmount > walletBalanceData.assets[values.asset]\n ) {\n errors.assetAmount = `Maximum ${walletBalanceData.assets[values.asset]}`;\n }\n\n if (assetCheckbox && !values.assetAmount) {\n errors.assetAmount = \"The field can't be empty\";\n }\n\n if (currentBalance < totalFeeAndAmount) {\n errors.amount = `Maximum ${Math.abs(\n currentBalance / constants.nanoErgInErg - Number(values.fee),\n )} ERG`;\n }\n if (values.amount < 0) {\n errors.amount = \"Amount can't be negative\";\n }\n\n if (currentBalance === 0) {\n errors.amount = 'Your balance is empty';\n }\n\n if (values.fee < 0) {\n errors.fee = \"Fee can't be negative\";\n }\n\n return errors;\n },\n [assetCheckbox, walletBalanceData, currentBalance],\n );\n\n return (\n

\n
\n

Payment send

\n {\n return (\n <>\n
\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n
\n \n (\n <>\n \n {currentBalance !== 0 && (\n {\n values.amount = Math.abs(\n (currentBalance - values.fee * constants.nanoErgInErg) /\n constants.nanoErgInErg,\n );\n form.blur('amount');\n }}\n >\n Maximum\n \n )}\n
{meta.error}
\n \n )}\n />\n
\n\n {Object.keys(walletBalanceData?.assets || {}).length > 0 && (\n
\n {\n setAssetCheckbox(e.target.checked);\n }}\n id=\"assetCheckbox\"\n />\n \n
\n )}\n\n {assetCheckbox && (\n <>\n
\n \n \n \n {Object.keys(walletBalanceData?.assets || {}).map((tokenId) => (\n \n ))}\n \n
\n {values.asset && values.asset !== 'none' && (\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n )}\n \n )}\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n 0 ||\n pristine ||\n values.asset === 'none'\n }\n >\n Send\n \n \n\n {\n setIsSentModalOpen(false);\n }}\n title=\"Payment sent\"\n description={\n <>\n

\n Your payment has been sent successfully. The transaction ID is -{' '}\n {transactionId}\n

\n

\n \n Click Here To Go To The Explorer\n \n

\n \n }\n primaryButtonContent={OK}\n secondaryButtonContent=\"Send again\"\n onPrimaryHandler={() => resetForm(form)}\n />\n \n );\n }}\n />\n
\n
\n );\n};\n\nexport default PaymentSendForm;\n","import React, { useCallback, useState } from 'react';\nimport { Field, Form } from 'react-final-form';\n\nimport cn from 'classnames';\nimport InfoModal from 'components/common/InfoModal/InfoModal';\nimport CopyToClipboard from 'components/common/CopyToClipboard';\nimport customToast from 'utils/toast';\nimport nodeApi from 'api/api';\nimport constants from '../../../../../utils/constants';\n\ntype AssetIssueFormData = {\n name?: string;\n amount?: string;\n decimals?: string;\n description?: string;\n fee?: string;\n};\n\nconst AssetIssueForm = ({\n apiKey,\n getWalletBalance,\n explorerSubdomain,\n}: {\n apiKey: string;\n getWalletBalance: any;\n explorerSubdomain: string;\n}) => {\n const [transactionId, setTransactionId] = useState(null);\n const [isSentModalOpen, setIsSentModalOpen] = useState(false);\n const [assetAmount, setAssetAmount] = useState(null);\n const [assetName, setAssetName] = useState(null);\n\n const issueAsset = useCallback(\n ({ name, amount, decimals, description, fee }) => {\n setAssetAmount(amount);\n setAssetName(name);\n const request = {\n name,\n amount,\n decimals,\n description,\n };\n\n return nodeApi.post(\n '/wallet/transaction/send',\n {\n requests: [request],\n fee: Number((parseFloat(fee) * constants.nanoErgInErg).toFixed(1)),\n },\n {\n headers: {\n api_key: apiKey,\n },\n },\n );\n },\n [apiKey, setAssetAmount, setAssetName],\n );\n\n const submitForm = useCallback(\n (formData) => {\n return issueAsset(formData)\n .then(({ data }) => {\n const generatedTransactionId = data;\n setTransactionId(generatedTransactionId);\n setIsSentModalOpen(true);\n getWalletBalance();\n })\n .catch((err) => {\n const errMessage = err.data ? err.data.detail : err.message;\n customToast('error', errMessage);\n });\n },\n [issueAsset, getWalletBalance],\n );\n\n const resetForm = (form: any) => {\n form.restart();\n setIsSentModalOpen(false);\n };\n\n const validateForm = (values: AssetIssueFormData) => {\n const errors: AssetIssueFormData = {};\n\n if (!values.name) {\n errors.name = 'The field cannot be empty';\n }\n\n if (!values.amount) {\n errors.amount = 'The field cannot be empty';\n }\n\n if (!values.decimals) {\n errors.decimals = 'The field cannot be empty';\n }\n\n if (!values.description) {\n errors.description = 'The field cannot be empty';\n }\n\n if (!values.fee || Number(values.fee) < 0.001) {\n errors.fee = 'Minimum 0.001 ERG';\n }\n\n if (!Number.isInteger(Number(values.amount)) && values.amount) {\n errors.amount = 'Should be an integer';\n }\n\n if (!Number.isInteger(Number(values.decimals)) && values.decimals) {\n errors.decimals = 'Should be an integer';\n }\n\n if (Number(values.fee) < 0) {\n errors.fee = \"Fee can't be negative\";\n }\n\n return errors;\n };\n\n return (\n
\n

Issue Tokens

\n {\n return (\n <>\n
\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n 0 || pristine}\n >\n Issue\n \n \n\n {\n setIsSentModalOpen(false);\n }}\n title=\"Congratulations!\"\n description={\n <>\n

\n {`You have successfully issued ${assetAmount} ${assetName} tokens! The transaction ID is\\n`}\n {transactionId}\n

\n

\n \n Click Here To View Transaction\n \n

\n \n }\n primaryButtonContent={OK}\n secondaryButtonContent=\"Send again\"\n onPrimaryHandler={() => resetForm(form)}\n />\n \n );\n }}\n />\n
\n );\n};\n\nexport default AssetIssueForm;\n","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"g\", null, /*#__PURE__*/React.createElement(\"path\", {\n d: \"M39.622,21.746l-6.749,6.75c-0.562,0.562-1.326,0.879-2.122,0.879s-1.56-0.316-2.121-0.879l-6.75-6.75 c-1.171-1.171-1.171-3.071,0-4.242c1.171-1.172,3.071-1.172,4.242,0l1.832,1.832C27.486,13.697,22.758,9.25,17,9.25 c-6.064,0-11,4.935-11,11c0,6.064,4.936,11,11,11c1.657,0,3,1.343,3,3s-1.343,3-3,3c-9.373,0-17-7.626-17-17s7.627-17,17-17 c8.936,0,16.266,6.933,16.936,15.698l1.442-1.444c1.172-1.172,3.072-1.172,4.242,0C40.792,18.674,40.792,20.574,39.622,21.746z\"\n}));\n\nvar _ref3 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref4 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref5 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref6 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref7 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref8 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref9 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref10 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref11 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref12 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref13 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref14 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref15 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref16 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref17 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar SvgRedoArrowSymbol = function SvgRedoArrowSymbol(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n id: \"Capa_1\",\n x: \"0px\",\n y: \"0px\",\n width: \"32px\",\n height: \"32px\",\n viewBox: \"0 0 40.499 40.5\",\n style: {\n enableBackground: \"new 0 0 40.499 40.5\"\n },\n xmlSpace: \"preserve\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgRedoArrowSymbol, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/redo-arrow-symbol.e801de31.svg\";\nexport { ForwardRef as ReactComponent };","import { ReactComponent as redoImage } from 'assets/images/icons/redo-arrow-symbol.svg';\nimport { makeIcon, IconProps } from './icons';\n\nexport const RedoIcon = ({ className }: IconProps) => {\n return makeIcon(redoImage, className);\n};\n","import React, { useState, useEffect, useCallback, useMemo } from 'react';\nimport cn from 'classnames';\nimport './index.scss';\nimport { connect } from 'react-redux';\nimport { RedoIcon } from 'components/common/icons/RedoIcon';\nimport constants from 'utils/constants';\nimport { RemoveIcon } from '../../../../common/icons/icons';\nimport {\n walletBalanceDataSelector,\n ergPriceSelector,\n walletAddressesSelector,\n} from '../../../../../store/selectors/wallet';\nimport { explorerSelector } from '../../../../../store/selectors/node';\nimport walletActions from '../../../../../store/actions/walletActions';\n\nconst WalletInformationTableItem = ({ name, value }: any) => {\n const [isOpen, setIsOpen] = useState(false);\n let resultTitle;\n let resultContent;\n\n if (Array.isArray(value)) {\n resultTitle = value.length;\n\n resultContent = (\n
\n {value.map((item) => (\n
\n
\n {item.value || ''} {item.name || ''}\n
\n
\n
\n ))}\n
\n );\n } else {\n resultTitle = value;\n resultContent = value;\n }\n\n return (\n
\n \n
{name}
\n \n
\n {isOpen &&
{resultContent}
}\n \n );\n};\n\nconst WalletInformationTable = (props: any) => {\n const {\n walletBalance,\n dispatchGetWalletBalance,\n dispatchGetErgPrice,\n dispatchGetWalletAddresses,\n walletAddresses,\n explorerSubdomain,\n } = props;\n\n const getValues = useCallback(() => {\n dispatchGetWalletBalance();\n dispatchGetErgPrice();\n dispatchGetWalletAddresses();\n }, [dispatchGetWalletBalance, dispatchGetErgPrice, dispatchGetWalletAddresses]);\n\n const getAddreses = useCallback((addresses: String[], subdomain: String) => {\n if (addresses.length === 0) {\n return 0;\n }\n\n return addresses.map((item) => ({\n value: (\n \n {item}\n \n ),\n }));\n }, []);\n\n const getAssets = useCallback((assets) => {\n if (Object.values(assets).length === 0) {\n return 0;\n }\n\n return Object.keys(assets).map((key) => ({\n name: {key},\n value: {assets[key]},\n }));\n }, []);\n\n useEffect(() => {\n getValues();\n }, [getValues]);\n\n const data = useMemo(\n () => [\n {\n name: 'Balance',\n value: walletBalance\n ? `${walletBalance.balance / constants.nanoErgInErg} ERG`\n : 'loading...',\n },\n // {\n // name: 'Balance in USD',\n // value: walletBalance\n // ? `$ ${(walletBalance.balance / constants.nanoErgInErg) * ergPrice}`\n // : 'Loading...',\n // },\n {\n name: 'Assets',\n value: walletBalance ? getAssets(walletBalance.assets) : `Loading...`,\n },\n {\n name: 'Addresses',\n value: walletAddresses ? getAddreses(walletAddresses, explorerSubdomain) : `Loading...`,\n },\n ],\n [walletBalance, getAssets, walletAddresses, getAddreses, explorerSubdomain],\n );\n\n const updateValues = useCallback(() => {\n getValues();\n }, [getValues]);\n\n return (\n
\n
\n

\n Wallet Information{' '}\n \n

\n
\n
\n {data.map(({ value, name }) => (\n \n ))}\n
\n
\n );\n};\n\nconst mapStateToProps = (state: any) => ({\n walletBalance: walletBalanceDataSelector(state),\n ergPrice: ergPriceSelector(state),\n walletAddresses: walletAddressesSelector(state),\n explorerSubdomain: explorerSelector(state),\n});\n\nconst mapDispatchToProps = (dispatch: any) => ({\n dispatchGetWalletBalance: () => dispatch(walletActions.getWalletBalance()),\n dispatchGetErgPrice: () => dispatch(walletActions.getErgPrice()),\n dispatchGetWalletAddresses: () => dispatch(walletActions.getWalletAddresses()),\n});\n\nexport default connect(mapStateToProps, mapDispatchToProps)(WalletInformationTable);\n","import React, { Component, memo } from 'react';\nimport { connect } from 'react-redux';\nimport walletActions from 'store/actions/walletActions';\nimport PaymentSendForm from './components/PaymentSendForm/index';\nimport AssetIssueForm from './components/AssetIssueForm/index';\nimport { apiKeySelector } from '../../../store/selectors/app';\nimport { explorerSelector } from '../../../store/selectors/node';\nimport {\n isWalletInitializedSelector,\n isWalletUnlockedSelector,\n walletBalanceDataSelector,\n} from '../../../store/selectors/wallet';\nimport WalletInformationTable from './components/WalletInformationTable/index';\nimport './index.scss';\n\nconst mapStateToProps = (state) => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n isWalletUnlocked: isWalletUnlockedSelector(state),\n walletBalanceData: walletBalanceDataSelector(state),\n explorerSubdomain: explorerSelector(state),\n});\n\nconst mapDispatchToProps = (dispatch) => ({\n dispatchGetWalletBalance: () => dispatch(walletActions.getWalletBalance()),\n});\n\nclass Wallet extends Component {\n renderState = (state) =>\n ({\n unlocked: (apiKey, walletBalanceData, getWalletBalance, explorerSubdomain) =>\n this.renderWalletUnlockedState(\n apiKey,\n walletBalanceData,\n getWalletBalance,\n explorerSubdomain,\n ),\n locked: () => this.renderWalletLockedState(),\n initialized: (apiKey) => this.renderInitializedState(apiKey),\n }[state]);\n\n renderWalletLockedState = () => (\n
\n

The wallet UI is locked. You need to unlock the wallet to access its UI.

\n
\n );\n\n renderInitializedState = () => (\n
\n

You need to initialize your wallet to access wallet UI.

\n
\n );\n\n renderWalletUnlockedState = (\n apiKey,\n walletBalanceData,\n dispatchGetWalletBalance,\n explorerSubdomain,\n ) => (\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n );\n\n render() {\n const {\n apiKey,\n isWalletUnlocked,\n isWalletInitialized,\n walletBalanceData,\n dispatchGetWalletBalance,\n explorerSubdomain,\n } = 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')(\n apiKey,\n walletBalanceData,\n dispatchGetWalletBalance,\n explorerSubdomain,\n );\n }\n\n return this.renderState('locked')();\n }\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(Wallet));\n","import React, { memo, useEffect } from 'react';\nimport { connect } from 'react-redux';\nimport { BrowserRouter, Switch, Route } from 'react-router-dom';\nimport nodeActions from 'store/actions/nodeActions';\nimport { Layout } from '../components/layout';\nimport Dashboard from '../components/pages/Dashboard';\nimport Wallet from '../components/pages/Wallet';\n\nconst mapDispatchToProps = (dispatch) => ({\n dispatchGetNetwork: () => dispatch(nodeActions.getNetwork()),\n});\n\nconst Router = (props) => {\n const { dispatchGetNetwork } = props;\n\n useEffect(() => {\n dispatchGetNetwork();\n }, []);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default connect(null, mapDispatchToProps)(memo(Router));\n","import { combineReducers } from 'redux';\nimport appSlice from '../slices/appSlice';\nimport nodeSlice from '../slices/nodeSlice';\nimport walletSlice from '../slices/walletSlice';\n\nexport default combineReducers({\n app: appSlice.reducer,\n node: nodeSlice.reducer,\n wallet: walletSlice.reducer,\n});\n","// import Axios from 'axios';\nimport walletActions from '../actions/walletActions';\nimport nodeApi from '../../api/api';\nimport { apiKeySelector } from '../selectors/app';\n// import oracleApi from '../../api/oracleApi';\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: walletData }) => {\n dispatch(walletActions.setIsWalletUnlocked(walletData.isUnlocked));\n dispatch(walletActions.setIsWalletInitialized(walletData.isInitialized));\n dispatch(walletActions.setWalletStatusData(walletData));\n })\n .catch(() => {});\n\n break;\n\n case walletActions.getWalletBalance.type:\n nodeApi\n .get('/wallet/balances', {\n headers: {\n api_key: apiKey,\n },\n })\n .then(({ data: walletData }) => {\n dispatch(walletActions.setWalletBalanceData(walletData));\n })\n .catch(() => {});\n\n break;\n\n // case walletActions.getErgPrice.type:\n // oracleApi\n // .get('/frontendData', {\n // transformResponse: [...Axios.defaults.transformResponse, (data) => JSON.parse(data)],\n // })\n // .then(({ data }) => {\n // dispatch(walletActions.setErgPrice(data.latest_price));\n // })\n // .catch(() => {});\n\n // break;\n\n case walletActions.getWalletAddresses.type:\n nodeApi\n .get('/wallet/addresses', {\n headers: {\n api_key: apiKey,\n },\n })\n .then(({ data: walletAddresses }) => {\n dispatch(walletActions.setWalletAddresses(walletAddresses));\n })\n .catch(() => {});\n\n break;\n\n default:\n break;\n }\n next(action);\n};\n","// import Axios from 'axios';\nimport nodeActions from '../actions/nodeActions';\nimport nodeApi from '../../api/api';\n\nexport default (store) => (next) => (action) => {\n const { dispatch } = store;\n\n switch (action.type) {\n case nodeActions.getNetwork.type:\n nodeApi\n .get('/info', {})\n .then(({ data: nodeData }) => {\n dispatch(nodeActions.setNetwork(nodeData.network));\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';\nimport nodeMiddleware from './middlewares/nodeMiddleware';\n\nexport default () => {\n const store = configureStore({\n reducer: rootReducer,\n middleware: [...getDefaultMiddleware(), walletMiddleware, nodeMiddleware],\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","// extracted by mini-css-extract-plugin\nmodule.exports = {\"backdrop\":\"Backdrop_backdrop__PmdBI\",\"content\":\"Backdrop_content__2Kmrw\",\"layer\":\"Backdrop_layer__3V2YH\"};","module.exports = __webpack_public_path__ + \"static/media/logotype_white.4dcfd639.svg\";"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["store/slices/nodeSlice.js","store/actions/nodeActions.js","utils/constants.js","store/selectors/node.js","components/common/MenuList/components/Explorer.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/utils.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/WalletSyncCard/index.js","components/common/ErgoLoader/index.js","components/pages/Dashboard/DashboardView.js","hooks/usePrevious.js","components/pages/Dashboard/DashboardContainer.js","components/pages/Dashboard/index.js","hooks/usePortal.ts","hooks/useBodyScroll.ts","hooks/useCurrentState.ts","components/common/Text/Text.tsx","utils/withDefaults.ts","components/common/CssTransition/CssTransition.tsx","components/common/Backdrop/Backdrop.tsx","components/common/InfoModal/modal-context.ts","assets/images/icons/close.svg","assets/images/icons/copy.icon.svg","assets/images/icons/remove.svg","components/common/icons/icons.tsx","components/common/icons/CloseIcon.tsx","components/common/InfoModal/InfoModal.tsx","components/pages/Wallet/components/PaymentSendForm/index.tsx","components/pages/Wallet/components/AssetIssueForm/index.tsx","assets/images/icons/redo-arrow-symbol.svg","components/common/icons/RedoIcon.tsx","components/pages/Wallet/components/WalletInformationTable/index.tsx","components/pages/Wallet/index.js","router/router.js","store/reducers/rootReducer.js","store/middlewares/walletMiddleware.js","store/middlewares/nodeMiddleware.js","App.js","store/index.js","index.js","components/common/Backdrop/Backdrop.module.scss","assets/images/logotype_white.svg"],"names":["createSlice","name","initialState","network","reducers","setNetwork","state","payload","getNetwork","createAction","nodeSlice","actions","nodeSelector","node","explorerSelector","createSelector","icon","faWpexplorer","Explorer","explorerSubdomain","this","props","ref","exporerRef","key","className","clsx","href","rel","target","Component","connect","localRouteList","dashboard","faChartLine","title","wallet","faExchangeAlt","externalRouteList","swaggerInterface","constants","faBook","website","faGlobe","withRouter","pathname","location","Object","values","map","index","active","to","apiKeySelector","app","apiKey","walletSelector","isWalletUnlockedSelector","isWalletUnlocked","isWalletInitializedSelector","isWalletInitialized","walletStatusDataSelector","walletStatusData","walletBalanceDataSelector","walletBalanceData","walletAddressesSelector","walletAddresses","ergPriceSelector","ergPrice","setIsWalletUnlocked","setIsWalletInitialized","setWalletStatusData","setWalletBalanceData","setErgPrice","setWalletAddresses","checkWalletStatus","getWalletBalance","getErgPrice","getWalletAddresses","walletSlice","setApiKey","action","appSlice","nodeApiLink","oracleApiLink","NetworkError","status","message","data","statusText","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","uuid","uuidv4","type","onClick","renderButton","Modal","show","onHide","centered","initialValues","onSubmit","Header","closeButton","Title","Body","placeholder","Footer","ApiKeyModalContainer","dispatch","dispatchSetApiKey","appActions","memo","useState","setShowModal","get","api_key","then","trim","customToast","catch","MODAL_STATES","WalletStatusForm","onOpen","setState","walletUnlock","pass","post","walletLock","submitWalletUnlockForm","setSubmitting","resetForm","setStatus","dispatchSetIsWalletUnlocked","err","errMessage","detail","submitWalletLockForm","confirm","aria-labelledby","isSubmitting","id","htmlFor","disabled","isWalletUnlock","walletActions","CopyToClipboard","startTimer","timerId","setTimeout","showTooltip","onCopy","e","preventDefault","copy","children","handleOnTooltipClose","myRef","React","createRef","clearTimeout","Overlay","current","placement","Tooltip","PureComponent","initialFormValues","walletPassword","mnemonicPass","WalletInitializeForm","isShowMnemonic","walletInit","a","handleSubmit","result","msg","mnemonic","role","aria-hidden","RestoreWalletForm","walletRestore","String","required","WalletInitModal","dispatchCheckWalletStatus","size","isApiKeySetted","openedModal","setOpenedModal","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","WalletSyncCard","renderErrorSynchronization","faTimes","length","walletHeight","LoaderLogo","xmlns","width","height","viewBox","fill","d","fillRule","clipRule","DashboardView","faExclamationTriangle","peersCount","bestHeaderId","launchTime","appVersion","isMining","format","Date","getWalletStatus","isUnlocked","balance","Number","toFixed","assets","usePrevious","value","useRef","dispatchGetWalletBalance","dispatchGetErgPrice","setNodeInfo","setError","setTimerId","setNodeCurrentState","useCallback","setTimer","newTimerId","setInterval","prevError","clearInterval","DashboardContainer","useSSR","browser","setBrowser","Boolean","window","document","createElement","isBrowser","isServer","el","setAttribute","usePortal","selectId","Math","random","toString","slice","isUsingBrowser","elSnapshot","setElSnapshot","hasElement","querySelector","body","appendChild","defaultOptions","scrollLayer","elementStack","Map","touchHandler","event","touches","useBodyScroll","elementRef","t","elRef","hidden","setHidden","safeOptions","isIosWithCustom","navigator","test","platform","lastOverflow","style","overflow","has","addEventListener","passive","set","last","removeEventListener","store","delete","useCurrentState","val","Text","variant","component","dangerouslySetInnerHTML","currentVariant","cn","colored","withDefaults","defaultProps","visible","enterTime","leaveTime","clearTime","classes","setClasses","renderable","setRenderable","statusClassName","time","timer","clearClassesTimer","isValidElement","cloneElement","setIsContentMouseDown","IsContentMouseDownRef","childrenClickHandler","stopPropagation","CssTransition","styles","backdrop","onMouseUp","layer","content","onMouseDown","ModalContext","createContext","_extends","assign","i","arguments","source","hasOwnProperty","call","apply","_objectWithoutProperties","excluded","sourceKeys","keys","indexOf","_objectWithoutPropertiesLoose","getOwnPropertySymbols","sourceSymbolKeys","propertyIsEnumerable","_ref","svgRef","ForwardRef","forwardRef","makeIcon","Icon","focusable","RemoveIcon","removeIcon","CloseIcon","closeImage","InfoModal","description","primaryButtonContent","secondaryButtonContent","disableBackdropClick","onClose","onPrimaryHandler","onSecondaryHandler","open","portal","setBodyHidden","setVisible","visibleRef","closeModal","undefined","modalConfig","useMemo","close","createPortal","Provider","xl","sm","wrapClassName","PaymentSendForm","currentBalance","transactionId","setTransactionId","isSentModalOpen","setIsSentModalOpen","assetCheckbox","setAssetCheckbox","paymentSend","recipientAddress","amount","fee","asset","assetAmount","request","address","parseFloat","tokenId","requests","sendForm","validateForm","errors","totalFeeAndAmount","abs","validate","render","submitting","pristine","form","input","meta","touched","blur","checked","onChange","restart","AssetIssueForm","setAssetAmount","assetName","setAssetName","issueAsset","decimals","formData","isInteger","_ref10","_ref11","_ref12","_ref13","_ref14","_ref15","_ref16","_ref17","x","y","enableBackground","xmlSpace","RedoIcon","redoImage","WalletInformationTableItem","resultTitle","resultContent","isOpen","setIsOpen","Array","isArray","item","prev","walletBalance","dispatchGetWalletAddresses","getValues","getAddreses","addresses","subdomain","getAssets","updateValues","Wallet","renderState","unlocked","renderWalletUnlockedState","locked","renderWalletLockedState","initialized","renderInitializedState","dispatchGetNetwork","nodeActions","basename","exact","path","Dashboard","combineReducers","reducer","next","getState","walletData","isInitialized","nodeData","configure","configureStore","rootReducer","middleware","getDefaultMiddleware","walletMiddleware","nodeMiddleware","App","ReactDOM","getElementById","module","exports"],"mappings":"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","file":"static/js/main.2df85f5c.chunk.js","sourcesContent":["import { createSlice } from 'redux-starter-kit';\n\nconst initialState = {\n network: 'mainnet',\n};\n\nexport default createSlice({\n name: 'nodeSlice',\n initialState,\n reducers: {\n setNetwork: (state, { payload }) => {\n state.network = payload;\n },\n },\n});\n","import { createAction } from 'redux-starter-kit';\nimport nodeSlice from '../slices/nodeSlice';\n\nconst getNetwork = createAction('getNetwork');\n\nexport default {\n ...nodeSlice.actions,\n getNetwork,\n};\n","export default {\n swaggerInterface: '/swagger',\n website: 'https://ergoplatform.org',\n nanoErgInErg: 1000000000,\n};\n","import { createSelector } from 'redux-starter-kit';\n\nexport const nodeSelector = (state) => state.node;\n\nexport const networkSelector = createSelector(nodeSelector, (node) => node.network);\n\nexport const explorerSelector = createSelector(nodeSelector, (node) =>\n node.network === 'mainnet' ? 'explorer' : node.network,\n);\n","import React, { Component } from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { connect } from 'react-redux';\nimport clsx from 'clsx';\nimport { faWpexplorer } from '@fortawesome/free-brands-svg-icons';\nimport { explorerSelector } from 'store/selectors/node';\n\nconst mapStateToProps = (state) => ({ explorerSubdomain: explorerSelector(state) });\n\nconst icon = ;\nconst title = 'Explorer';\n\nclass Explorer extends Component {\n render() {\n const { explorerSubdomain } = this.props;\n\n return (\n \n {icon} {title}\n \n );\n }\n}\n\nexport default connect(mapStateToProps, null)(Explorer);\n","import React from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faChartLine, faExchangeAlt, faGlobe, faBook } from '@fortawesome/free-solid-svg-icons';\nimport clsx from 'clsx';\nimport { withRouter, Link } from 'react-router-dom';\nimport constants from '../../../utils/constants';\nimport Explorer from './components/Explorer';\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 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(({ 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\nexport const walletStatusDataSelector = createSelector(\n walletSelector,\n (wallet) => wallet.walletStatusData,\n);\n\nexport const walletBalanceDataSelector = createSelector(\n walletSelector,\n (wallet) => wallet.walletBalanceData,\n);\n\nexport const walletAddressesSelector = createSelector(\n walletSelector,\n (wallet) => wallet.walletAddresses,\n);\n\nexport const ergPriceSelector = createSelector(walletSelector, (wallet) => wallet.ergPrice);\n","import { createSlice } from 'redux-starter-kit';\n\nconst initialState = {\n isWalletUnlocked: null,\n isWalletInitialized: null,\n walletStatusData: null,\n walletBalanceData: null,\n ergPrice: null,\n walletAddresses: 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 setWalletStatusData: (state, { payload }) => {\n state.walletStatusData = payload;\n },\n setWalletBalanceData: (state, { payload }) => {\n state.walletBalanceData = payload;\n },\n setErgPrice: (state, { payload }) => {\n state.ergPrice = payload;\n },\n setWalletAddresses: (state, { payload }) => {\n state.walletAddresses = payload;\n },\n },\n});\n","import { createAction } from 'redux-starter-kit';\nimport walletSlice from '../slices/walletSlice';\n\nconst checkWalletStatus = createAction('checkWalletStatus');\nconst getWalletBalance = createAction('getWalletBalance');\nconst getErgPrice = createAction('getErgPrice');\nconst getWalletAddresses = createAction('getWalletAddresses');\n\nexport default {\n ...walletSlice.actions,\n checkWalletStatus,\n getWalletBalance,\n getErgPrice,\n getWalletAddresses,\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 oracleApiLink: 'https://erg-usd-ergo-oracle.emurgo.io',\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] ? toastStates[state](text, options) : new Error(`Bad toast state`);\n","import React from 'react';\nimport { Modal } from 'react-bootstrap';\nimport { Formik, Form, Field } from 'formik';\nimport { v4 as uuidv4 } from 'uuid';\n\nconst renderButton = (apiKey, handleShow) => {\n if (apiKey === '') {\n return (\n \n );\n }\n\n return (\n \n );\n};\n\nconst ApiKeyModalView = ({ showModal, handleHide, submitForm, apiKey, handleShow }) => {\n const uuid = uuidv4();\n return (\n
\n {renderButton(apiKey, handleShow)}\n handleHide()} centered>\n submitForm(values, uuid)}\n >\n {() => (\n
\n \n Authorization\n \n \n

Set API key to access Node requests

\n
\n \n
\n
\n\n \n \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, uuid) => {\n // Check API key for random get method\n nodeApi\n .get('/wallet/status', {\n headers: {\n api_key: values[`apiKey${uuid}`],\n },\n })\n .then(() => {\n dispatchSetApiKey(values[`apiKey${uuid}`].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(mapStateToProps, mapDispatchToProps)(memo(ApiKeyModalContainer));\n","export const MODAL_STATES = {\n INIT: 'INIT',\n STATUS: 'STATUS',\n};\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';\nimport { MODAL_STATES } from '../utils';\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.props.onOpen(MODAL_STATES.STATUS);\n this.setState({ showModal: true });\n };\n\n handleHide = () => {\n this.props.onOpen(null);\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 = ({ pass }, { setSubmitting, resetForm, setStatus }) => {\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 );\n }\n\n return (\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 Unlock wallet form\n \n \n \n
\n \n \n \n * If you have it or leave field empty\n \n
\n
\n\n \n \n Close\n \n \n \n
\n )}\n
\n \n
\n );\n }\n}\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(WalletStatusForm));\n","import React from 'react';\nimport copy from 'clipboard-copy';\nimport { Overlay, Tooltip } from 'react-bootstrap';\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 componentWillUnmount() {\n clearTimeout(this.state.timerId);\n }\n\n startTimer = () => {\n const timerId = setTimeout(() => this.setState({ showTooltip: false }), 1500);\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 render() {\n return (\n <>\n \n {this.props.children}\n \n \n Copied!\n \n \n );\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 {status && status.state === 'error' && (\n
\n {status.msg}\n
\n )}\n {status && status.state === 'success' && this.state.isShowMnemonic && (\n
\n this.setState({ isShowMnemonic: false })}\n >\n ×\n \n {status.msg}\n
\n )}\n
\n \n \n
\n
\n \n \n
\n \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 { v4 as uuidv4 } from 'uuid';\nimport nodeApi from '../../../api/api';\nimport customToast from '../../../utils/toast';\n\nconst initialFormValues = {\n walletPassword: '',\n mnemonicPass: '',\n mnemonic: '',\n};\n\nclass RestoreWalletForm extends Component {\n walletRestore = async (values, uuid) => {\n const { walletPassword, mnemonicPass = '' } = values;\n if (!values[`mnemonic${uuid}`] || !String(values[`mnemonic${uuid}`]).trim()) {\n throw Error('Need to set mnemonic');\n }\n\n return nodeApi.post(\n '/wallet/restore',\n {\n pass: walletPassword || '',\n mnemonicPass: mnemonicPass || '',\n mnemonic: values[`mnemonic${uuid}`],\n },\n {\n headers: {\n api_key: this.props.apiKey,\n },\n },\n );\n };\n\n handleSubmit = (values, { setSubmitting, resetForm, setStatus }, uuid) => {\n setStatus({ status: 'submitting' });\n this.walletRestore(values, uuid)\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 const uuid = uuidv4();\n\n return (\n
\n

Re-store wallet

\n this.handleSubmit(values, props, uuid)}\n >\n {({ status, isSubmitting }) => (\n
\n {status && status.state === 'error' && (\n
\n {status.msg}\n
\n )}\n {status && status.state === 'success' && (\n
{status.msg}
\n )}\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n
\n )}\n \n
\n );\n }\n}\n\nexport default memo(RestoreWalletForm);\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';\nimport { MODAL_STATES } from '../utils';\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.props.onOpen(MODAL_STATES.INIT);\n this.setState({ showModal: true });\n };\n\n handleHide = () => {\n this.props.onOpen(null);\n this.props.dispatchCheckWalletStatus();\n this.setState({ showModal: false });\n };\n\n renderButton = () => {\n return (\n \n );\n };\n\n render() {\n const { apiKey } = this.props;\n\n return (\n
\n {this.renderButton()}\n this.handleHide()} centered size=\"lg\">\n \n Wallet initialization\n \n \n
\n \n
\n
\n \n
\n
\n \n \n \n
\n
\n );\n }\n}\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(WalletInitModal));\n","import React, { memo, useState } 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';\nimport { MODAL_STATES } from './utils';\n\nconst renderWalletForms = (isWalletInitialized, openedModal, setOpenedModal) => {\n if (isWalletInitialized === null) {\n return <>;\n }\n\n if (isWalletInitialized && openedModal !== MODAL_STATES.INIT) {\n return (\n
\n \n
\n );\n }\n\n return (\n
\n \n
\n );\n};\n\nconst HeaderView = ({ isApiKeySetted, isWalletInitialized }) => {\n const [openedModal, setOpenedModal] = useState(null);\n\n return (\n \n \n \n \"logotype\"\n \n \n
\n \n
\n {isApiKeySetted && renderWalletForms(isWalletInitialized, openedModal, setOpenedModal)}\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\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(HeaderContainer));\n","import React from 'react';\nimport { withRouter } from 'react-router-dom';\nimport MenuList from '../common/MenuList';\nimport './index.scss';\nimport Header from '../Header';\n\nexport const Layout = withRouter((props) => {\n return (\n
\n
\n
\n \n
\n
\n
{props.children}
\n
\n
\n );\n});\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';\n\nexport default class SynchCard extends Component {\n shouldComponentUpdate(nextProps) {\n if (\n this.getSynchronizationState(nextProps) !== this.getSynchronizationState(this.props.nodeInfo)\n ) {\n return true;\n }\n\n return false;\n }\n\n renderActiveSynchronization = () => (\n <>\n

Synchronization state

\n

\n Active synchronization\n

\n \n );\n\n renderCompleteSynchronization = () => (\n <>\n

Synchronization 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 (fullHeight !== null && headersHeight !== null && fullHeight === headersHeight) {\n return 'complete';\n }\n\n return 'active';\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, { Component } from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faSync, faCheck, faTimes } from '@fortawesome/free-solid-svg-icons';\nimport InfoCard from '../InfoCard';\n\nexport default class WalletSyncCard extends Component {\n renderActiveSynchronization = () => (\n <>\n

Synchronization state

\n

\n Active synchronization\n

\n \n );\n\n renderCompleteSynchronization = () => (\n <>\n

Synchronization state

\n

\n Wallet is synced\n

\n \n );\n\n renderErrorSynchronization = () => (\n <>\n

Synchronization state

\n

\n Error - {this.props.walletStatusData.error}\n

\n \n );\n\n renderSynchronizationState = (state) =>\n ({\n active: this.renderActiveSynchronization,\n complete: this.renderCompleteSynchronization,\n error: this.renderErrorSynchronization,\n }[state]);\n\n getSynchronizationState = (walletStatusData, headersHeight) => {\n if (walletStatusData.error?.trim().length !== 0) {\n return 'error';\n }\n\n if (\n walletStatusData.walletHeight !== null &&\n headersHeight !== null &&\n walletStatusData.walletHeight === headersHeight\n ) {\n return 'complete';\n }\n\n return 'active';\n };\n\n render() {\n const { walletStatusData, headersHeight } = this.props;\n const currentSynchState = this.getSynchronizationState(walletStatusData, headersHeight);\n return (\n \n {this.renderSynchronizationState(currentSynchState)()}\n \n );\n }\n}\n","import React from 'react';\nimport './index.scss';\n\nconst LoaderLogo = () => {\n return (\n
\n \n \n \n \n
\n );\n};\n\nexport default LoaderLogo;\n","import React from 'react';\nimport { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { format } from 'date-fns';\nimport constants from 'utils/constants';\nimport InfoCard from './InfoCard';\nimport SynchCard from './SynchCard';\nimport WalletSyncCard from './WalletSyncCard';\nimport LoaderLogo from '../../common/ErgoLoader/index';\n\nconst getWalletStatus = (isWalletInitialized) => {\n if (!isWalletInitialized) {\n return 'Not initialized';\n }\n\n return 'Initialized';\n};\n\nconst DashboardView = ({\n error,\n nodeInfo,\n isWalletInitialized,\n walletStatusData,\n walletBalanceData,\n ergPrice,\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 { peersCount, bestHeaderId, launchTime, fullHeight, appVersion, isMining } = nodeInfo;\n\n return (\n <>\n
\n

Node Information

\n
\n
\n \n

Version

\n

{appVersion}

\n
\n
\n
\n \n
\n
\n \n

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 ? 'Yes' : 'No'}

\n
\n
\n
\n \n

Peers connected

\n

{peersCount}

\n
\n
\n
\n
\n {ergPrice && (\n
\n

ERG Information

\n
\n
\n \n

\n ERG price in $
\n (based on oracle pool data)\n

\n

{ergPrice}

\n
\n
\n
\n
\n )}\n {walletStatusData && (\n
\n

Wallet Information

\n
\n
\n \n

Initialization state

\n

{getWalletStatus(isWalletInitialized)}

\n
\n
\n
\n \n

Lock state

\n

\n {walletStatusData.isUnlocked ? 'Unlocked' : 'Locked'}\n

\n
\n
\n
\n \n
\n {walletBalanceData && (\n
\n \n

Balance

\n

\n {walletBalanceData.balance / constants.nanoErgInErg} ERG{' '}\n {ergPrice &&\n `~ $${Number(\n ergPrice * (walletBalanceData.balance / constants.nanoErgInErg),\n ).toFixed(2)}`}\n

\n
\n
\n )}\n {walletBalanceData && (\n
\n \n

Assets

\n

\n {Object.values(walletBalanceData.assets).length || '0'}\n

\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 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 walletStatusDataSelector,\n walletBalanceDataSelector,\n ergPriceSelector,\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 walletStatusData: walletStatusDataSelector(state),\n walletBalanceData: walletBalanceDataSelector(state),\n ergPrice: ergPriceSelector(state),\n});\n\nconst mapDispatchToProps = (dispatch) => ({\n dispatchCheckWalletStatus: () => dispatch(walletActions.checkWalletStatus()),\n dispatchGetWalletBalance: () => dispatch(walletActions.getWalletBalance()),\n dispatchGetErgPrice: () => dispatch(walletActions.getErgPrice()),\n});\n\nconst DashboardContainer = (props) => {\n const {\n isWalletInitialized,\n isWalletUnlocked,\n apiKey,\n dispatchCheckWalletStatus,\n dispatchGetWalletBalance,\n dispatchGetErgPrice,\n walletStatusData,\n walletBalanceData,\n ergPrice,\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(() => {\n setNodeCurrentState();\n dispatchGetErgPrice();\n\n if (apiKey) {\n dispatchCheckWalletStatus();\n dispatchGetWalletBalance();\n }\n }, 2000);\n\n setTimerId(newTimerId);\n }, [\n apiKey,\n dispatchCheckWalletStatus,\n dispatchGetErgPrice,\n dispatchGetWalletBalance,\n setNodeCurrentState,\n ]);\n\n const prevError = usePrevious(error);\n useEffect(() => {\n if (prevError && prevError !== error) {\n dispatchCheckWalletStatus();\n dispatchGetWalletBalance();\n dispatchGetErgPrice();\n }\n }, [dispatchCheckWalletStatus, dispatchGetErgPrice, dispatchGetWalletBalance, error, prevError]);\n\n useEffect(() => {\n setNodeCurrentState();\n dispatchGetErgPrice();\n\n if (apiKey) {\n dispatchCheckWalletStatus();\n dispatchGetWalletBalance();\n }\n\n setTimer();\n // eslint-disable-next-line\n }, [apiKey]);\n\n useEffect(\n () => () => {\n clearInterval(timerId);\n },\n [timerId, apiKey],\n );\n\n return (\n \n );\n};\n\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(DashboardContainer));\n","import DashboardContainer from './DashboardContainer';\nimport './index.scss';\n\nexport default DashboardContainer;\n","import { useEffect, useState } from 'react';\n\nconst isBrowser = (): boolean => {\n return Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);\n};\n\ntype SSRState = {\n isBrowser: boolean;\n isServer: boolean;\n};\n\nconst useSSR = (): SSRState => {\n const [browser, setBrowser] = useState(false);\n useEffect(() => {\n setBrowser(isBrowser());\n }, []);\n\n return {\n isBrowser: browser,\n isServer: !browser,\n };\n};\n\nconst createElement = (id: string): HTMLElement => {\n const el = document.createElement('div');\n el.setAttribute('id', id);\n return el;\n};\n\nconst usePortal = (\n selectId: string = Math.random().toString(32).slice(2, 10),\n): HTMLElement | null => {\n const id = `id-${selectId}`;\n const isUsingBrowser = useSSR().isBrowser;\n const [elSnapshot, setElSnapshot] = useState(\n isUsingBrowser ? createElement(id) : null,\n );\n\n useEffect(() => {\n const hasElement = document.querySelector(`#${id}`);\n const el = hasElement || createElement(id);\n\n if (!hasElement) {\n document.body.appendChild(el);\n }\n setElSnapshot(el);\n }, []);\n\n return elSnapshot;\n};\n\nexport default usePortal;\n","import { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react';\n/* eslint-disable */\nexport type ElementStackItem = {\n last: string;\n};\n\nexport type BodyScrollOptions = {\n scrollLayer: boolean;\n};\n\nconst defaultOptions: BodyScrollOptions = {\n scrollLayer: false,\n};\n\nconst elementStack = new Map();\n\nconst isIos = () => {\n /* istanbul ignore next */\n if (typeof window === 'undefined' || !window.navigator) return false;\n return /iP(ad|hone|od)/.test(window.navigator.platform);\n};\n\nconst touchHandler = (event: TouchEvent): boolean => {\n if (event.touches && event.touches.length > 1) return true;\n event.preventDefault();\n return false;\n};\n\nconst useBodyScroll = (\n elementRef?: RefObject | null,\n options?: BodyScrollOptions\n): [boolean, Dispatch>] => {\n if (typeof document === 'undefined')\n return [false, (t: SetStateAction) => t];\n const elRef = elementRef || useRef(document.body);\n const [hidden, setHidden] = useState(false);\n const safeOptions = {\n ...defaultOptions,\n ...(options || {}),\n };\n\n // don't prevent touch event when layer contain scroll\n const isIosWithCustom = () => {\n if (safeOptions.scrollLayer) return false;\n return isIos();\n };\n\n useEffect(() => {\n if (!elRef || !elRef.current) return;\n const lastOverflow = elRef.current.style.overflow;\n if (hidden) {\n if (elementStack.has(elRef.current)) return;\n if (!isIosWithCustom()) {\n elRef.current.style.overflow = 'hidden';\n } else {\n document.addEventListener('touchmove', touchHandler, {\n passive: false,\n });\n }\n elementStack.set(elRef.current, {\n last: lastOverflow,\n });\n return;\n }\n\n // reset element overflow\n if (!elementStack.has(elRef.current)) return;\n if (!isIosWithCustom()) {\n const store = elementStack.get(elRef.current) as ElementStackItem;\n elRef.current.style.overflow = store.last;\n } else {\n document.removeEventListener('touchmove', touchHandler);\n }\n elementStack.delete(elRef.current);\n }, [hidden, elRef]);\n\n return [hidden, setHidden];\n};\n\nexport default useBodyScroll;\n\n/* eslint-disable */\n","import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react';\n\nexport type CurrentStateType = [S, Dispatch>, MutableRefObject];\n\n// Добавляет ref, по которому текущее значение стейта можно получить\n// сразу вместо того чтобы ждать следующего рендера\nconst useCurrentState = (initialState: S | (() => S)): CurrentStateType => {\n const [state, setState] = useState(() => {\n return typeof initialState === 'function' ? (initialState as () => S)() : initialState;\n });\n const ref = useRef(initialState as S);\n\n useEffect(() => {\n ref.current = state;\n }, [state]);\n\n const setValue = (val: SetStateAction) => {\n const result = typeof val === 'function' ? (val as (prevState: S) => S)(ref.current) : val;\n ref.current = result;\n setState(result);\n };\n\n return [state, setValue, ref];\n};\n\nexport default useCurrentState;\n","import React, { ReactNode } from 'react';\nimport cn from 'classnames';\n\ntype TextVariant =\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'subtitle1'\n | 'subtitle2'\n | 'body-text1'\n | 'body-text2'\n | 'small-text1'\n | 'small-text2'\n | 'small-text3';\n\nexport type TextColor =\n | 'brandOrange'\n | 'purple'\n | 'black'\n | 'spaceGray'\n | 'gray5'\n | 'gray4'\n | 'gray3'\n | 'gray2'\n | 'gray1'\n | 'white'\n | 'orange'\n | 'red'\n | 'green'\n | 'blue'\n | 'blueHover'\n | 'brandOrangeHover'\n | 'brandOrangeActive'\n | 'purpleHover'\n | 'purpleActive';\n\nexport type TextComponent = 'span' | 'p' | 'div' | 'h1' | 'label' | 'h2' | 'h3';\n\ninterface IText {\n children?: ReactNode;\n variant?: TextVariant;\n xl?: TextVariant;\n lg?: TextVariant;\n md?: TextVariant;\n sm?: TextVariant;\n component?: TextComponent;\n color?: TextColor;\n className?: string;\n dangerouslySetInnerHTML?: any;\n htmlFor?: string;\n}\n\nconst Text = ({\n children,\n variant,\n component,\n color,\n className,\n dangerouslySetInnerHTML,\n ...props\n}: IText) => {\n const currentVariant = variant;\n\n const Component = component || 'p';\n\n if (dangerouslySetInnerHTML) {\n return (\n <>\n \n\n \n \n );\n }\n\n return (\n \n {children}\n \n \n );\n};\n\nexport default Text;\n","import React from 'react';\n\n// Прокидывает default параметры в компонент\nconst withDefaults = (component: React.ComponentType

, defaultProps: DP) => {\n type Props = Partial & Omit;\n // eslint-disable-next-line\n component.defaultProps = defaultProps;\n return component as React.ComponentType;\n};\n\nexport default withDefaults;\n","import React, { useEffect, useState } from 'react';\nimport withDefaults from 'utils/withDefaults';\n\ninterface Props {\n visible?: boolean;\n enterTime?: number;\n leaveTime?: number;\n clearTime?: number;\n className?: string;\n name?: string;\n}\n\nconst defaultProps = {\n visible: false,\n enterTime: 60,\n leaveTime: 60,\n clearTime: 60,\n className: '',\n name: 'transition',\n};\n\nexport type CSSTransitionProps = Props & typeof defaultProps;\n\nconst CSSTransition: React.FC> = ({\n children,\n className,\n visible,\n enterTime,\n leaveTime,\n clearTime,\n name,\n ...props\n}) => {\n const [classes, setClasses] = useState('');\n const [renderable, setRenderable] = useState(visible);\n\n useEffect(() => {\n const statusClassName = visible ? 'enter' : 'leave';\n const time = visible ? enterTime : leaveTime;\n if (visible && !renderable) {\n setRenderable(true);\n }\n\n setClasses(`${name}-${statusClassName}`);\n\n // set class to active\n const timer = setTimeout(() => {\n setClasses(`${name}-${statusClassName} ${name}-${statusClassName}-active`);\n clearTimeout(timer);\n }, time);\n\n // remove classess when animation over\n const clearClassesTimer = setTimeout(() => {\n if (!visible) {\n setClasses('');\n setRenderable(false);\n }\n clearTimeout(clearClassesTimer);\n }, time + clearTime);\n\n return () => {\n clearTimeout(timer);\n clearTimeout(clearClassesTimer);\n };\n }, [visible, renderable]);\n if (!React.isValidElement(children) || !renderable) return null;\n\n return React.cloneElement(children, {\n ...props,\n className: `${children.props.className} ${className} ${classes}`,\n });\n};\n\nexport default withDefaults(CSSTransition, defaultProps);\n","import React, { MouseEvent, useCallback, ReactElement } from 'react';\nimport withDefaults from 'utils/withDefaults';\nimport useCurrentState from 'hooks/useCurrentState';\nimport cn from 'classnames';\nimport CssTransition from '../CssTransition/CssTransition';\nimport styles from './Backdrop.module.scss';\n\ninterface Props {\n onClick?: (event: MouseEvent) => void;\n visible?: boolean;\n children?: ReactElement;\n className?: string;\n}\n\nconst defaultProps = {\n onClick: () => {},\n visible: false,\n};\n\nexport type BackdropProps = Props & typeof defaultProps;\n\nconst Backdrop: React.FC> = React.memo(\n ({ children, onClick, visible, className }: BackdropProps) => {\n const [, setIsContentMouseDown, IsContentMouseDownRef] = useCurrentState(false);\n const clickHandler = (event: MouseEvent) => {\n if (IsContentMouseDownRef.current) return;\n if (onClick) {\n onClick(event);\n }\n };\n const childrenClickHandler = useCallback((event: MouseEvent) => {\n event.stopPropagation();\n }, []);\n const mouseUpHandler = () => {\n if (!IsContentMouseDownRef.current) return;\n const timer = setTimeout(() => {\n setIsContentMouseDown(false);\n clearTimeout(timer);\n }, 0);\n };\n\n return (\n \n

\n
\n setIsContentMouseDown(true)}\n >\n {children}\n
\n
\n \n );\n },\n);\n\nexport default withDefaults(Backdrop, defaultProps);\n","import React from 'react';\n\nexport interface ModalConfig {\n close?: () => void;\n}\n\nconst defaultContext = {};\n\nexport const ModalContext = React.createContext(defaultContext);\n\nexport const useModalContext = (): ModalConfig => React.useContext(ModalContext);\n","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M10.7951 9.30799C10.3844 8.89734 9.71865 8.89734 9.30799 9.30799C8.89734 9.71865 8.89734 10.3844 9.30799 10.7951L14.5129 16L9.30799 21.2049C8.89734 21.6156 8.89734 22.2814 9.30799 22.692C9.71865 23.1027 10.3844 23.1027 10.7951 22.692L16 17.4871L21.2049 22.692C21.6156 23.1027 22.2814 23.1027 22.692 22.692C23.1027 22.2814 23.1027 21.6156 22.692 21.2049L17.4871 16L22.692 10.7951C23.1027 10.3844 23.1027 9.71865 22.692 9.30799C22.2814 8.89734 21.6156 8.89734 21.2049 9.30799L16 14.5129L10.7951 9.30799Z\",\n fill: \"#76767A\"\n});\n\nvar SvgClose = function SvgClose(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 32,\n height: 32,\n viewBox: \"0 0 32 32\",\n fill: \"none\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgClose, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/close.feae5a5c.svg\";\nexport { ForwardRef as ReactComponent };","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M10.6668 0.666748H2.66683C1.9335 0.666748 1.3335 1.26675 1.3335 2.00008V11.3334H2.66683V2.00008H10.6668V0.666748ZM10.0002 3.33341L14.0002 7.33341V14.0001C14.0002 14.7334 13.4002 15.3334 12.6668 15.3334H5.32683C4.5935 15.3334 4.00016 14.7334 4.00016 14.0001L4.00683 4.66675C4.00683 3.93341 4.60016 3.33341 5.3335 3.33341H10.0002ZM9.3335 8.00008H13.0002L9.3335 4.33341V8.00008Z\",\n fill: \"#0078FF\"\n});\n\nvar SvgCopyicon = function SvgCopyicon(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 16,\n height: 16,\n viewBox: \"0 0 16 16\",\n fill: \"none\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgCopyicon, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/copy.icon.835ebda7.svg\";\nexport { ForwardRef as ReactComponent };","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M13.3332 7.99992C13.3332 8.36811 13.0347 8.66659 12.6665 8.66659H3.33317C2.96498 8.66659 2.6665 8.36811 2.6665 7.99992C2.6665 7.63173 2.96498 7.33325 3.33317 7.33325H12.6665C13.0347 7.33325 13.3332 7.63173 13.3332 7.99992Z\",\n fill: \"#0078FF\"\n});\n\nvar SvgRemove = function SvgRemove(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 16,\n height: 16,\n viewBox: \"0 0 16 16\",\n fill: \"none\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgRemove, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/remove.94c0849a.svg\";\nexport { ForwardRef as ReactComponent };","import React from 'react';\n\nimport { ReactComponent as copyIcon } from '../../../assets/images/icons/copy.icon.svg';\nimport { ReactComponent as removeIcon } from '../../../assets/images/icons/remove.svg';\n\nexport interface IconProps {\n className?: string;\n}\n\nexport const makeIcon = (Icon: any, className?: string) => {\n return ;\n};\n\nexport const CopyIcon = ({ className }: IconProps) => {\n return makeIcon(copyIcon, className);\n};\n\nexport const RemoveIcon = ({ className }: IconProps) => {\n return makeIcon(removeIcon, className);\n};\n","import { ReactComponent as closeImage } from 'assets/images/icons/close.svg';\nimport { makeIcon, IconProps } from './icons';\n\nexport const CloseIcon = ({ className }: IconProps) => {\n return makeIcon(closeImage, className);\n};\n","import React, { useEffect, useMemo, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport usePortal from 'hooks/usePortal';\nimport useBodyScroll from 'hooks/useBodyScroll';\nimport useCurrentState from 'hooks/useCurrentState';\nimport Text from 'components/common/Text/Text';\nimport Backdrop from '../Backdrop/Backdrop';\nimport { ModalConfig, ModalContext } from './modal-context';\nimport CssTransition from '../CssTransition/CssTransition';\nimport { CloseIcon } from '../icons/CloseIcon';\n\ninterface Props {\n title?: React.ReactNode;\n description?: React.ReactNode;\n primaryButtonContent: React.ReactNode;\n secondaryButtonContent: React.ReactNode;\n disableBackdropClick?: boolean;\n onClose?: () => void;\n onOpen?: () => void;\n onPrimaryHandler?: () => void;\n onSecondaryHandler?: () => void;\n open?: boolean;\n width?: string;\n wrapClassName?: string;\n}\n\nconst defaultProps = {\n width: '26rem',\n wrapClassName: '',\n disableBackdropClick: false,\n};\n\ntype NativeAttrs = Omit, keyof Props>;\nexport type ModalProps = Props & typeof defaultProps & NativeAttrs;\n\nconst InfoModal: React.FC> = ({\n title,\n description,\n primaryButtonContent,\n secondaryButtonContent,\n disableBackdropClick,\n onClose,\n onOpen,\n onPrimaryHandler,\n onSecondaryHandler,\n open,\n}) => {\n const portal = usePortal('modal');\n const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true });\n const [visible, setVisible, visibleRef] = useCurrentState(false);\n\n const closeModal = useCallback(() => {\n if (onClose) {\n onClose();\n }\n setVisible(false);\n setBodyHidden(false);\n }, [onClose, setVisible, setBodyHidden]);\n\n useEffect(() => {\n if (open === undefined) return;\n if (open && onOpen) {\n onOpen();\n }\n if (!open && visibleRef.current && onClose) {\n onClose();\n }\n\n setVisible(open);\n setBodyHidden(open);\n }, [open]);\n\n const closeFromBackdrop = () => {\n if (disableBackdropClick) return;\n closeModal();\n };\n\n const primaryButtonClickHandler = () => {\n if (onPrimaryHandler) {\n onPrimaryHandler();\n }\n if (onClose) {\n onClose();\n }\n };\n\n const secondaryButtonClickHandler = () => {\n if (onSecondaryHandler) {\n onSecondaryHandler();\n }\n if (onClose) {\n onClose();\n }\n };\n\n const modalConfig: ModalConfig = useMemo(\n () => ({\n close: closeModal,\n }),\n [closeModal],\n );\n\n if (!portal) return null;\n\n return createPortal(\n \n \n \n
\n
\n

{title}

\n \n {description}\n \n \n {primaryButtonContent}\n \n \n {secondaryButtonContent}\n \n
\n \n\n \n
\n
\n
\n
,\n portal,\n );\n};\n\ntype ModalComponent

= React.FC

;\ntype ComponentProps = Partial &\n Omit &\n NativeAttrs;\n\nInfoModal.defaultProps = defaultProps;\n\nexport default InfoModal as ModalComponent;\n","import React, { useState, useCallback } from 'react';\nimport { Form, Field } from 'react-final-form';\nimport InfoModal from 'components/common/InfoModal/InfoModal';\nimport cn from 'classnames';\nimport nodeApi from '../../../../../api/api';\nimport customToast from '../../../../../utils/toast';\nimport CopyToClipboard from '../../../../common/CopyToClipboard';\nimport constants from '../../../../../utils/constants';\n\ntype Errors = {\n recipientAddress?: string;\n amount?: string;\n fee?: string;\n asset?: string;\n assetAmount?: string;\n};\n\nconst PaymentSendForm = ({\n apiKey,\n walletBalanceData,\n getWalletBalance,\n explorerSubdomain,\n}: {\n apiKey: string;\n walletBalanceData: any;\n getWalletBalance: any;\n explorerSubdomain: string;\n}) => {\n const currentBalance = walletBalanceData?.balance;\n\n const [transactionId, setTransactionId] = useState(null);\n const [isSentModalOpen, setIsSentModalOpen] = useState(false);\n const [assetCheckbox, setAssetCheckbox] = useState(false);\n\n const paymentSend = useCallback(\n ({ recipientAddress, amount, fee, asset, assetAmount }) => {\n const request = {\n address: recipientAddress.trim(),\n value: Number((parseFloat(amount) * constants.nanoErgInErg).toFixed(1)),\n assets:\n assetCheckbox && asset !== 'none' && assetAmount > 0\n ? [{ tokenId: asset, amount: Number(assetAmount) }]\n : [],\n };\n return nodeApi.post(\n '/wallet/transaction/send',\n {\n requests: [request],\n fee: Number((parseFloat(fee) * constants.nanoErgInErg).toFixed(1)),\n },\n {\n headers: {\n api_key: apiKey,\n },\n },\n );\n },\n [assetCheckbox, apiKey],\n );\n\n const resetForm = (form: any) => {\n form.restart();\n setIsSentModalOpen(false);\n };\n\n const sendForm = useCallback(\n (values) => {\n if (values.recipientAddress.trim() === '' || !values.recipientAddress) {\n return;\n }\n\n paymentSend(values)\n .then(({ data }) => {\n setTransactionId(data);\n setIsSentModalOpen(true);\n getWalletBalance();\n })\n .catch((err) => {\n const errMessage = err.data ? err.data.detail : err.message;\n customToast('error', errMessage);\n });\n },\n [paymentSend, getWalletBalance],\n );\n\n const validateForm = useCallback(\n (values) => {\n const errors: Errors = {};\n\n const totalFeeAndAmount =\n (Number(values.amount) + Number(values.fee)) * constants.nanoErgInErg;\n\n if (!values.recipientAddress || values.recipientAddress?.trim() === '') {\n errors.recipientAddress = 'The field cannot be empty';\n }\n\n if (!values.fee || values.fee < 0.001) {\n errors.fee = 'Minimum 0.001 ERG';\n }\n\n if (values.asset === 'none') {\n errors.asset = 'You need to choose asset';\n }\n\n if (\n walletBalanceData &&\n values.assetAmount &&\n values.asset !== 'none' &&\n values.assetAmount > walletBalanceData.assets[values.asset]\n ) {\n errors.assetAmount = `Maximum ${walletBalanceData.assets[values.asset]}`;\n }\n\n if (assetCheckbox && !values.assetAmount) {\n errors.assetAmount = \"The field can't be empty\";\n }\n\n if (currentBalance < totalFeeAndAmount) {\n errors.amount = `Maximum ${Math.abs(\n currentBalance / constants.nanoErgInErg - Number(values.fee),\n )} ERG`;\n }\n if (values.amount < 0) {\n errors.amount = \"Amount can't be negative\";\n }\n\n if (currentBalance === 0) {\n errors.amount = 'Your balance is empty';\n }\n\n if (values.fee < 0) {\n errors.fee = \"Fee can't be negative\";\n }\n\n return errors;\n },\n [assetCheckbox, walletBalanceData, currentBalance],\n );\n\n return (\n

\n
\n

Payment send

\n {\n return (\n <>\n
\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n
\n \n (\n <>\n \n {currentBalance !== 0 && (\n {\n values.amount = Math.abs(\n (currentBalance - values.fee * constants.nanoErgInErg) /\n constants.nanoErgInErg,\n );\n form.blur('amount');\n }}\n >\n Maximum\n \n )}\n
{meta.error}
\n \n )}\n />\n
\n\n {Object.keys(walletBalanceData?.assets || {}).length > 0 && (\n
\n {\n setAssetCheckbox(e.target.checked);\n }}\n id=\"assetCheckbox\"\n />\n \n
\n )}\n\n {assetCheckbox && (\n <>\n
\n \n \n \n {Object.keys(walletBalanceData?.assets || {}).map((tokenId) => (\n \n ))}\n \n
\n {values.asset && values.asset !== 'none' && (\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n )}\n \n )}\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n 0 ||\n pristine ||\n values.asset === 'none'\n }\n >\n Send\n \n \n\n {\n setIsSentModalOpen(false);\n }}\n title=\"Payment sent\"\n description={\n <>\n

\n Your payment has been sent successfully. The transaction ID is -{' '}\n {transactionId}\n

\n

\n \n Click Here To Go To The Explorer\n \n

\n \n }\n primaryButtonContent={OK}\n secondaryButtonContent=\"Send again\"\n onPrimaryHandler={() => resetForm(form)}\n />\n \n );\n }}\n />\n
\n
\n );\n};\n\nexport default PaymentSendForm;\n","import React, { useCallback, useState } from 'react';\nimport { Field, Form } from 'react-final-form';\n\nimport cn from 'classnames';\nimport InfoModal from 'components/common/InfoModal/InfoModal';\nimport CopyToClipboard from 'components/common/CopyToClipboard';\nimport customToast from 'utils/toast';\nimport nodeApi from 'api/api';\nimport constants from '../../../../../utils/constants';\n\ntype AssetIssueFormData = {\n name?: string;\n amount?: string;\n decimals?: string;\n description?: string;\n fee?: string;\n};\n\nconst AssetIssueForm = ({\n apiKey,\n getWalletBalance,\n explorerSubdomain,\n}: {\n apiKey: string;\n getWalletBalance: any;\n explorerSubdomain: string;\n}) => {\n const [transactionId, setTransactionId] = useState(null);\n const [isSentModalOpen, setIsSentModalOpen] = useState(false);\n const [assetAmount, setAssetAmount] = useState(null);\n const [assetName, setAssetName] = useState(null);\n\n const issueAsset = useCallback(\n ({ name, amount, decimals, description, fee }) => {\n setAssetAmount(amount);\n setAssetName(name);\n const request = {\n name,\n amount,\n decimals,\n description,\n };\n\n return nodeApi.post(\n '/wallet/transaction/send',\n {\n requests: [request],\n fee: Number((parseFloat(fee) * constants.nanoErgInErg).toFixed(1)),\n },\n {\n headers: {\n api_key: apiKey,\n },\n },\n );\n },\n [apiKey, setAssetAmount, setAssetName],\n );\n\n const submitForm = useCallback(\n (formData) => {\n return issueAsset(formData)\n .then(({ data }) => {\n const generatedTransactionId = data;\n setTransactionId(generatedTransactionId);\n setIsSentModalOpen(true);\n getWalletBalance();\n })\n .catch((err) => {\n const errMessage = err.data ? err.data.detail : err.message;\n customToast('error', errMessage);\n });\n },\n [issueAsset, getWalletBalance],\n );\n\n const resetForm = (form: any) => {\n form.restart();\n setIsSentModalOpen(false);\n };\n\n const validateForm = (values: AssetIssueFormData) => {\n const errors: AssetIssueFormData = {};\n\n if (!values.name) {\n errors.name = 'The field cannot be empty';\n }\n\n if (!values.amount) {\n errors.amount = 'The field cannot be empty';\n }\n\n if (!values.decimals) {\n errors.decimals = 'The field cannot be empty';\n }\n\n if (!values.description) {\n errors.description = 'The field cannot be empty';\n }\n\n if (!values.fee || Number(values.fee) < 0.001) {\n errors.fee = 'Minimum 0.001 ERG';\n }\n\n if (!Number.isInteger(Number(values.amount)) && values.amount) {\n errors.amount = 'Should be an integer';\n }\n\n if (!Number.isInteger(Number(values.decimals)) && values.decimals) {\n errors.decimals = 'Should be an integer';\n }\n\n if (Number(values.fee) < 0) {\n errors.fee = \"Fee can't be negative\";\n }\n\n return errors;\n };\n\n return (\n
\n

Issue Tokens

\n {\n return (\n <>\n
\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n
\n \n (\n <>\n \n
{meta.error}
\n \n )}\n />\n
\n\n 0 || pristine}\n >\n Issue\n \n \n\n {\n setIsSentModalOpen(false);\n }}\n title=\"Congratulations!\"\n description={\n <>\n

\n {`You have successfully issued ${assetAmount} ${assetName} tokens! The transaction ID is\\n`}\n {transactionId}\n

\n

\n \n Click Here To View Transaction\n \n

\n \n }\n primaryButtonContent={OK}\n secondaryButtonContent=\"Send again\"\n onPrimaryHandler={() => resetForm(form)}\n />\n \n );\n }}\n />\n
\n );\n};\n\nexport default AssetIssueForm;\n","function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport React from \"react\";\n\nvar _ref2 = /*#__PURE__*/React.createElement(\"g\", null, /*#__PURE__*/React.createElement(\"path\", {\n d: \"M39.622,21.746l-6.749,6.75c-0.562,0.562-1.326,0.879-2.122,0.879s-1.56-0.316-2.121-0.879l-6.75-6.75 c-1.171-1.171-1.171-3.071,0-4.242c1.171-1.172,3.071-1.172,4.242,0l1.832,1.832C27.486,13.697,22.758,9.25,17,9.25 c-6.064,0-11,4.935-11,11c0,6.064,4.936,11,11,11c1.657,0,3,1.343,3,3s-1.343,3-3,3c-9.373,0-17-7.626-17-17s7.627-17,17-17 c8.936,0,16.266,6.933,16.936,15.698l1.442-1.444c1.172-1.172,3.072-1.172,4.242,0C40.792,18.674,40.792,20.574,39.622,21.746z\"\n}));\n\nvar _ref3 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref4 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref5 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref6 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref7 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref8 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref9 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref10 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref11 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref12 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref13 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref14 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref15 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref16 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar _ref17 = /*#__PURE__*/React.createElement(\"g\", null);\n\nvar SvgRedoArrowSymbol = function SvgRedoArrowSymbol(_ref) {\n var svgRef = _ref.svgRef,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"svgRef\", \"title\"]);\n\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n id: \"Capa_1\",\n x: \"0px\",\n y: \"0px\",\n width: \"32px\",\n height: \"32px\",\n viewBox: \"0 0 40.499 40.5\",\n style: {\n enableBackground: \"new 0 0 40.499 40.5\"\n },\n xmlSpace: \"preserve\",\n ref: svgRef\n }, props), title ? /*#__PURE__*/React.createElement(\"title\", null, title) : null, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17);\n};\n\nvar ForwardRef = /*#__PURE__*/React.forwardRef(function (props, ref) {\n return /*#__PURE__*/React.createElement(SvgRedoArrowSymbol, _extends({\n svgRef: ref\n }, props));\n});\nexport default __webpack_public_path__ + \"static/media/redo-arrow-symbol.e801de31.svg\";\nexport { ForwardRef as ReactComponent };","import { ReactComponent as redoImage } from 'assets/images/icons/redo-arrow-symbol.svg';\nimport { makeIcon, IconProps } from './icons';\n\nexport const RedoIcon = ({ className }: IconProps) => {\n return makeIcon(redoImage, className);\n};\n","import React, { useState, useEffect, useCallback, useMemo } from 'react';\nimport cn from 'classnames';\nimport './index.scss';\nimport { connect } from 'react-redux';\nimport { RedoIcon } from 'components/common/icons/RedoIcon';\nimport constants from 'utils/constants';\nimport { RemoveIcon } from '../../../../common/icons/icons';\nimport {\n walletBalanceDataSelector,\n ergPriceSelector,\n walletAddressesSelector,\n} from '../../../../../store/selectors/wallet';\nimport { explorerSelector } from '../../../../../store/selectors/node';\nimport walletActions from '../../../../../store/actions/walletActions';\n\nconst WalletInformationTableItem = ({ name, value }: any) => {\n const [isOpen, setIsOpen] = useState(false);\n let resultTitle;\n let resultContent;\n\n if (Array.isArray(value)) {\n resultTitle = value.length;\n\n resultContent = (\n
\n {value.map((item) => (\n
\n
\n {item.value || ''} {item.name || ''}\n
\n
\n
\n ))}\n
\n );\n } else {\n resultTitle = value;\n resultContent = value;\n }\n\n return (\n
\n \n
{name}
\n \n
\n {isOpen &&
{resultContent}
}\n \n );\n};\n\nconst WalletInformationTable = (props: any) => {\n const {\n walletBalance,\n dispatchGetWalletBalance,\n dispatchGetErgPrice,\n dispatchGetWalletAddresses,\n walletAddresses,\n explorerSubdomain,\n } = props;\n\n const getValues = useCallback(() => {\n dispatchGetWalletBalance();\n dispatchGetErgPrice();\n dispatchGetWalletAddresses();\n }, [dispatchGetWalletBalance, dispatchGetErgPrice, dispatchGetWalletAddresses]);\n\n const getAddreses = useCallback((addresses: String[], subdomain: String) => {\n if (addresses.length === 0) {\n return 0;\n }\n\n return addresses.map((item) => ({\n value: (\n \n {item}\n \n ),\n }));\n }, []);\n\n const getAssets = useCallback((assets) => {\n if (Object.values(assets).length === 0) {\n return 0;\n }\n\n return Object.keys(assets).map((key) => ({\n name: {key},\n value: {assets[key]},\n }));\n }, []);\n\n useEffect(() => {\n getValues();\n }, [getValues]);\n\n const data = useMemo(\n () => [\n {\n name: 'Balance',\n value: walletBalance\n ? `${walletBalance.balance / constants.nanoErgInErg} ERG`\n : 'loading...',\n },\n // {\n // name: 'Balance in USD',\n // value: walletBalance\n // ? `$ ${(walletBalance.balance / constants.nanoErgInErg) * ergPrice}`\n // : 'Loading...',\n // },\n {\n name: 'Assets',\n value: walletBalance ? getAssets(walletBalance.assets) : `Loading...`,\n },\n {\n name: 'Addresses',\n value: walletAddresses ? getAddreses(walletAddresses, explorerSubdomain) : `Loading...`,\n },\n ],\n [walletBalance, getAssets, walletAddresses, getAddreses, explorerSubdomain],\n );\n\n const updateValues = useCallback(() => {\n getValues();\n }, [getValues]);\n\n return (\n
\n
\n

\n Wallet Information{' '}\n \n

\n
\n
\n {data.map(({ value, name }) => (\n \n ))}\n
\n
\n );\n};\n\nconst mapStateToProps = (state: any) => ({\n walletBalance: walletBalanceDataSelector(state),\n ergPrice: ergPriceSelector(state),\n walletAddresses: walletAddressesSelector(state),\n explorerSubdomain: explorerSelector(state),\n});\n\nconst mapDispatchToProps = (dispatch: any) => ({\n dispatchGetWalletBalance: () => dispatch(walletActions.getWalletBalance()),\n dispatchGetErgPrice: () => dispatch(walletActions.getErgPrice()),\n dispatchGetWalletAddresses: () => dispatch(walletActions.getWalletAddresses()),\n});\n\nexport default connect(mapStateToProps, mapDispatchToProps)(WalletInformationTable);\n","import React, { Component, memo } from 'react';\nimport { connect } from 'react-redux';\nimport walletActions from 'store/actions/walletActions';\nimport PaymentSendForm from './components/PaymentSendForm/index';\nimport AssetIssueForm from './components/AssetIssueForm/index';\nimport { apiKeySelector } from '../../../store/selectors/app';\nimport { explorerSelector } from '../../../store/selectors/node';\nimport {\n isWalletInitializedSelector,\n isWalletUnlockedSelector,\n walletBalanceDataSelector,\n} from '../../../store/selectors/wallet';\nimport WalletInformationTable from './components/WalletInformationTable/index';\nimport './index.scss';\n\nconst mapStateToProps = (state) => ({\n apiKey: apiKeySelector(state),\n isWalletInitialized: isWalletInitializedSelector(state),\n isWalletUnlocked: isWalletUnlockedSelector(state),\n walletBalanceData: walletBalanceDataSelector(state),\n explorerSubdomain: explorerSelector(state),\n});\n\nconst mapDispatchToProps = (dispatch) => ({\n dispatchGetWalletBalance: () => dispatch(walletActions.getWalletBalance()),\n});\n\nclass Wallet extends Component {\n renderState = (state) =>\n ({\n unlocked: (apiKey, walletBalanceData, getWalletBalance, explorerSubdomain) =>\n this.renderWalletUnlockedState(\n apiKey,\n walletBalanceData,\n getWalletBalance,\n explorerSubdomain,\n ),\n locked: () => this.renderWalletLockedState(),\n initialized: (apiKey) => this.renderInitializedState(apiKey),\n }[state]);\n\n renderWalletLockedState = () => (\n
\n

The wallet UI is locked. You need to unlock the wallet to access its UI.

\n
\n );\n\n renderInitializedState = () => (\n
\n

You need to initialize your wallet to access wallet UI.

\n
\n );\n\n renderWalletUnlockedState = (\n apiKey,\n walletBalanceData,\n dispatchGetWalletBalance,\n explorerSubdomain,\n ) => (\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n );\n\n render() {\n const {\n apiKey,\n isWalletUnlocked,\n isWalletInitialized,\n walletBalanceData,\n dispatchGetWalletBalance,\n explorerSubdomain,\n } = 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')(\n apiKey,\n walletBalanceData,\n dispatchGetWalletBalance,\n explorerSubdomain,\n );\n }\n\n return this.renderState('locked')();\n }\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(memo(Wallet));\n","import React, { memo, useEffect } from 'react';\nimport { connect } from 'react-redux';\nimport { BrowserRouter, Switch, Route } from 'react-router-dom';\nimport nodeActions from 'store/actions/nodeActions';\nimport { Layout } from '../components/layout';\nimport Dashboard from '../components/pages/Dashboard';\nimport Wallet from '../components/pages/Wallet';\n\nconst mapDispatchToProps = (dispatch) => ({\n dispatchGetNetwork: () => dispatch(nodeActions.getNetwork()),\n});\n\nconst Router = (props) => {\n const { dispatchGetNetwork } = props;\n\n useEffect(() => {\n dispatchGetNetwork();\n }, []);\n\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default connect(null, mapDispatchToProps)(memo(Router));\n","import { combineReducers } from 'redux';\nimport appSlice from '../slices/appSlice';\nimport nodeSlice from '../slices/nodeSlice';\nimport walletSlice from '../slices/walletSlice';\n\nexport default combineReducers({\n app: appSlice.reducer,\n node: nodeSlice.reducer,\n wallet: walletSlice.reducer,\n});\n","// import Axios from 'axios';\nimport walletActions from '../actions/walletActions';\nimport nodeApi from '../../api/api';\nimport { apiKeySelector } from '../selectors/app';\n// import oracleApi from '../../api/oracleApi';\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: walletData }) => {\n dispatch(walletActions.setIsWalletUnlocked(walletData.isUnlocked));\n dispatch(walletActions.setIsWalletInitialized(walletData.isInitialized));\n dispatch(walletActions.setWalletStatusData(walletData));\n })\n .catch(() => {});\n\n break;\n\n case walletActions.getWalletBalance.type:\n nodeApi\n .get('/wallet/balances', {\n headers: {\n api_key: apiKey,\n },\n })\n .then(({ data: walletData }) => {\n dispatch(walletActions.setWalletBalanceData(walletData));\n })\n .catch(() => {});\n\n break;\n\n // case walletActions.getErgPrice.type:\n // oracleApi\n // .get('/frontendData', {\n // transformResponse: [...Axios.defaults.transformResponse, (data) => JSON.parse(data)],\n // })\n // .then(({ data }) => {\n // dispatch(walletActions.setErgPrice(data.latest_price));\n // })\n // .catch(() => {});\n\n // break;\n\n case walletActions.getWalletAddresses.type:\n nodeApi\n .get('/wallet/addresses', {\n headers: {\n api_key: apiKey,\n },\n })\n .then(({ data: walletAddresses }) => {\n dispatch(walletActions.setWalletAddresses(walletAddresses));\n })\n .catch(() => {});\n\n break;\n\n default:\n break;\n }\n next(action);\n};\n","// import Axios from 'axios';\nimport nodeActions from '../actions/nodeActions';\nimport nodeApi from '../../api/api';\n\nexport default (store) => (next) => (action) => {\n const { dispatch } = store;\n\n switch (action.type) {\n case nodeActions.getNetwork.type:\n nodeApi\n .get('/info', {})\n .then(({ data: nodeData }) => {\n dispatch(nodeActions.setNetwork(nodeData.network));\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';\nimport nodeMiddleware from './middlewares/nodeMiddleware';\n\nexport default () => {\n const store = configureStore({\n reducer: rootReducer,\n middleware: [...getDefaultMiddleware(), walletMiddleware, nodeMiddleware],\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","// extracted by mini-css-extract-plugin\nmodule.exports = {\"backdrop\":\"Backdrop_backdrop__PmdBI\",\"content\":\"Backdrop_content__2Kmrw\",\"layer\":\"Backdrop_layer__3V2YH\"};","module.exports = __webpack_public_path__ + \"static/media/logotype_white.4dcfd639.svg\";"],"sourceRoot":""} \ No newline at end of file diff --git a/src/main/resources/testnet.conf b/src/main/resources/testnet.conf index c671473275..9ce6764cab 100644 --- a/src/main/resources/testnet.conf +++ b/src/main/resources/testnet.conf @@ -61,7 +61,7 @@ scorex { network { magicBytes = [2, 0, 0, 2] bindAddress = "0.0.0.0:9020" - nodeName = "ergo-testnet-4.0.21.1" + nodeName = "ergo-testnet-4.0.22" nodeName = ${?NODENAME} knownPeers = [ "213.239.193.208:9020", diff --git a/src/main/scala/org/ergoplatform/GlobalConstants.scala b/src/main/scala/org/ergoplatform/GlobalConstants.scala index 3effa02a08..0025ba4fc9 100644 --- a/src/main/scala/org/ergoplatform/GlobalConstants.scala +++ b/src/main/scala/org/ergoplatform/GlobalConstants.scala @@ -10,6 +10,4 @@ object GlobalConstants { * (to avoid clashing between blockchain processing and API actors) */ val ApiDispatcher = "api-dispatcher" - - val NetworkDispatcher = "network-dispatcher" } diff --git a/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala index 8c0e0128d0..66d845eeef 100644 --- a/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala @@ -1,12 +1,18 @@ package org.ergoplatform.http.api +import akka.actor.ActorRef import akka.http.scaladsl.server.{Directive1, ValidationRejection} -import org.ergoplatform.settings.Algos +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} +import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoStateReader} +import org.ergoplatform.settings.{Algos, ErgoSettings} import scorex.core.api.http.ApiRoute import scorex.util.{ModifierId, bytesToId} +import akka.pattern.ask -import scala.concurrent.ExecutionContextExecutor -import scala.util.Success +import scala.concurrent.{ExecutionContextExecutor, Future} +import scala.util.{Success, Try} trait ErgoBaseApiRoute extends ApiRoute { @@ -17,10 +23,37 @@ trait ErgoBaseApiRoute extends ApiRoute { val modifierIdGet: Directive1[ModifierId] = parameters("id".as[String]) .flatMap(handleModifierId) - private def handleModifierId(value: String): Directive1[ModifierId] = + private def handleModifierId(value: String): Directive1[ModifierId] = { Algos.decode(value) match { case Success(bytes) => provide(bytesToId(bytes)) case _ => reject(ValidationRejection("Wrong modifierId format")) } + } + + private def getStateAndPool(readersHolder: ActorRef): Future[(ErgoStateReader, ErgoMemPoolReader)] = { + (readersHolder ? GetReaders).mapTo[Readers].map { rs => + (rs.s, rs.m) + } + } + + /** + * Helper method to verify transaction against UTXO set (and unconfirmed outputs in the mempool), or check + * transaction syntax only if UTXO set is not available (the node is running in "digest" mode) + * + * Used in /transactions (POST /transactions and /transactions/check methods) and /wallet (/wallet/payment/send + * and /wallet/transaction/send) API methods to check submitted or generated transaction + */ + protected def verifyTransaction(tx: ErgoTransaction, + readersHolder: ActorRef, + ergoSettings: ErgoSettings): Future[Try[ErgoTransaction]] = { + getStateAndPool(readersHolder) + .map { + case (utxo: UtxoStateReader, mp: ErgoMemPoolReader) => + val maxTxCost = ergoSettings.nodeSettings.maxTransactionCost + utxo.withMempool(mp).validateWithCost(tx, maxTxCost).map(_ => tx) + case _ => + tx.statelessValidity().map(_ => tx) + } + } } diff --git a/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala index aba3fc946f..7462c7f56d 100644 --- a/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ScriptApiRoute.scala @@ -20,6 +20,7 @@ import sigmastate._ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.eval.{CompiletimeIRContext, IRContext, RuntimeIRContext} import sigmastate.lang.{CompilerSettings, SigmaCompiler, TransformingSigmaBuilder} +import sigmastate.interpreter.Interpreter import sigmastate.serialization.ValueSerializer import scala.concurrent.Future @@ -102,8 +103,7 @@ case class ScriptApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSettings) tree => { implicit val irc: IRContext = new RuntimeIRContext() val interpreter: ErgoLikeInterpreter = new ErgoLikeInterpreter() - val prop = interpreter.propositionFromErgoTree(tree, req.ctx.asInstanceOf[interpreter.CTX]) - val res = interpreter.reduceToCrypto(req.ctx.asInstanceOf[interpreter.CTX], prop) + val res = Try(interpreter.fullReduction(tree, req.ctx.asInstanceOf[interpreter.CTX], Interpreter.emptyEnv)) res.fold( e => BadRequest(e.getMessage), s => ApiResponse(CryptoResult(s.value, s.cost).asJson) diff --git a/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala index b4ec406b19..3af867a7b4 100644 --- a/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala @@ -9,7 +9,6 @@ import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.mempool.HistogramStats.getFeeHistogram -import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoStateReader} import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedTransaction import org.ergoplatform.settings.ErgoSettings import scorex.core.api.http.ApiError.BadRequest @@ -38,25 +37,13 @@ case class TransactionsApiRoute(readersHolder: ActorRef, private def getMemPool: Future[ErgoMemPoolReader] = (readersHolder ? GetReaders).mapTo[Readers].map(_.m) - private def getStateAndPool: Future[(ErgoStateReader, ErgoMemPoolReader)] = - (readersHolder ? GetReaders).mapTo[Readers].map { rs => - (rs.s, rs.m) - } - private def getUnconfirmedTransactions(offset: Int, limit: Int): Future[Json] = getMemPool.map { p => p.getAll.slice(offset, offset + limit).map(_.asJson).asJson } private def validateTransactionAndProcess(tx: ErgoTransaction)(processFn: ErgoTransaction => Any): Route = { onSuccess { - getStateAndPool - .map { - case (utxo: UtxoStateReader, mp: ErgoMemPoolReader) => - val maxTxCost = ergoSettings.nodeSettings.maxTransactionCost - utxo.withMempool(mp).validateWithCost(tx, maxTxCost) - case _ => - tx.statelessValidity() - } + verifyTransaction(tx, readersHolder, ergoSettings) } { _.fold( e => BadRequest(s"Malformed transaction: ${e.getMessage}"), diff --git a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala index 18ef2af978..516c0e1287 100644 --- a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala @@ -26,7 +26,9 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} -case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, ergoSettings: ErgoSettings) +case class WalletApiRoute(readersHolder: ActorRef, + nodeViewActorRef: ActorRef, + ergoSettings: ErgoSettings) (implicit val context: ActorRefFactory) extends WalletApiOperations with ApiCodecs { implicit val paymentRequestDecoder: PaymentRequestDecoder = new PaymentRequestDecoder(ergoSettings) @@ -130,8 +132,12 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e private def generateTransactionAndProcess(requests: Seq[TransactionGenerationRequest], inputsRaw: Seq[String], dataInputsRaw: Seq[String], + verifyFn: ErgoTransaction => Future[Try[ErgoTransaction]], processFn: ErgoTransaction => Route): Route = { - withWalletOp(_.generateTransaction(requests, inputsRaw, dataInputsRaw)) { + withWalletOp(_.generateTransaction(requests, inputsRaw, dataInputsRaw).flatMap(txTry => txTry match { + case Success(tx) => verifyFn(tx) + case f: Failure[ErgoTransaction] => Future(f) + })) { case Failure(e) => BadRequest(s"Bad request $requests. ${Option(e.getMessage).getOrElse(e.toString)}") case Success(tx) => processFn(tx) } @@ -140,7 +146,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e private def generateTransaction(requests: Seq[TransactionGenerationRequest], inputsRaw: Seq[String], dataInputsRaw: Seq[String]): Route = { - generateTransactionAndProcess(requests, inputsRaw, dataInputsRaw, tx => ApiResponse(tx)) + generateTransactionAndProcess(requests, inputsRaw, dataInputsRaw, tx => Future(Success(tx)), tx => ApiResponse(tx)) } private def generateUnsignedTransaction(requests: Seq[TransactionGenerationRequest], @@ -155,25 +161,27 @@ 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 => - nodeViewActorRef ! LocallyGeneratedTransaction(tx) - ApiResponse(tx.id) - }) + generateTransactionAndProcess(requests, inputsRaw, dataInputsRaw, + tx => verifyTransaction(tx, readersHolder, ergoSettings), + { tx => + nodeViewActorRef ! LocallyGeneratedTransaction(tx) + ApiResponse(tx.id) + }) } def sendTransactionR: Route = (path("transaction" / "send") & post & entity(as[RequestsHolder])) { holder => - sendTransaction(holder.withFee, holder.inputsRaw, holder.dataInputsRaw) + sendTransaction(holder.withFee(), holder.inputsRaw, holder.dataInputsRaw) } def generateTransactionR: Route = (path("transaction" / "generate") & post & entity(as[RequestsHolder])) { holder => - generateTransaction(holder.withFee, holder.inputsRaw, holder.dataInputsRaw) + generateTransaction(holder.withFee(), holder.inputsRaw, holder.dataInputsRaw) } def generateUnsignedTransactionR: Route = (path("transaction" / "generateUnsigned") & post & entity(as[RequestsHolder])) { holder => - generateUnsignedTransaction(holder.withFee, holder.inputsRaw, holder.dataInputsRaw) + generateUnsignedTransaction(holder.withFee(), holder.inputsRaw, holder.dataInputsRaw) } def generateCommitmentsR: Route = (path("generateCommitments") diff --git a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala index 3d3c8b4b95..17080fb8d9 100644 --- a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala +++ b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala @@ -112,19 +112,23 @@ class MempoolAuditor(nodeViewHolderRef: ActorRef, private def rebroadcastTransactions(): Unit = { log.debug("Rebroadcasting transactions") poolReaderOpt.foreach { pr => - pr.random(settings.nodeSettings.rebroadcastCount).foreach { tx => - stateReaderOpt match { - case Some(utxoState: UtxoStateReader) => - if (tx.inputIds.forall(inputBoxId => utxoState.boxById(inputBoxId).isDefined)) { + val toBroadcast = pr.random(settings.nodeSettings.rebroadcastCount).toSeq + stateReaderOpt match { + case Some(utxoState: UtxoStateReader) => + val stateToCheck = utxoState.withTransactions(toBroadcast) + toBroadcast.foreach { tx => + if (tx.inputIds.forall(inputBoxId => stateToCheck.boxById(inputBoxId).isDefined)) { log.info(s"Rebroadcasting $tx") broadcastTx(tx) } else { log.info(s"Not rebroadcasting $tx as not all the inputs are in place") } - case _ => + } + case _ => + toBroadcast.foreach { tx => log.warn(s"Rebroadcasting $tx while state is not ready or not UTXO set") broadcastTx(tx) - } + } } } } diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 7826708be2..6a8b8a5db8 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -769,9 +769,12 @@ object CandidateGenerator extends ScorexLogging { val res = loop(transactions, Seq.empty, None, Seq.empty) log.info( - s"Collected ${res._1.length} transactions For block #$currentHeight, " + + s"Collected ${res._1.length} transactions for block #$currentHeight, " + s"${res._2.length} transactions turned out to be invalid" ) + log.whenDebugEnabled { + log.debug(s"Invalid trandaction ids for block #$currentHeight : ${res._2}") + } res } diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index c99b76492b..0ebdb7b421 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -4,7 +4,6 @@ import akka.actor.SupervisorStrategy.{Restart, Stop} import java.net.InetSocketAddress import akka.actor.{Actor, ActorInitializationException, ActorKilledException, ActorRef, ActorRefFactory, DeathPactException, OneForOneStrategy, Props} -import org.ergoplatform.GlobalConstants import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.modifiers.{ErgoFullBlock, ErgoPersistentModifier} @@ -84,12 +83,18 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, protected val deliveryTracker: DeliveryTracker = DeliveryTracker.empty(context.system, deliveryTimeout, maxDeliveryChecks, self, settings) - private val minModifiersPerBucket = 10 // minimum of persistent modifiers (excl. headers) to download by single peer - private val maxModifiersPerBucket = 30 // maximum of persistent modifiers (excl. headers) to download by single peer + private val minModifiersPerBucket = 16 // minimum of persistent modifiers (excl. headers) to download by single peer + private val maxModifiersPerBucket = 32 // maximum of persistent modifiers (excl. headers) to download by single peer private val minHeadersPerBucket = 50 // minimum of headers to download by single peer private val maxHeadersPerBucket = 400 // maximum of headers to download by single peer + // It could be the case that adversarial peers are sending sync messages to the node to cause + // resource exhaustion. To prevent it, we do not answer on sync message, if previous one was sent + // no more than `GlobalSyncLockTime` milliseconds ago. There's also per-peer limit `PerPeerSyncLockTime` + private val GlobalSyncLockTime = 50 + private val PerPeerSyncLockTime = 100 + /** * Register periodic events */ @@ -120,7 +125,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, val interval = networkSettings.syncInterval context.system.scheduler.scheduleWithFixedDelay(2.seconds, interval, self, SendLocalSyncInfo) - val healthCheckRate = settings.nodeSettings.acceptableChainUpdateDelay / 5 + val healthCheckRate = settings.nodeSettings.acceptableChainUpdateDelay / 3 context.system.scheduler.scheduleAtFixedRate(healthCheckRate, healthCheckRate, viewHolderRef, IsChainHealthy)(ex, self) } @@ -198,12 +203,12 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, val newGlobal = timeProvider.time() val globalDiff = newGlobal - globalSyncGot - if(globalDiff > 250) { + if(globalDiff > GlobalSyncLockTime) { globalSyncGot = newGlobal val diff = syncTracker.updateLastSyncGetTime(remote) - if (diff > 1000 ) { - // process sync if sent in more than 1 second after previous sync + if (diff > PerPeerSyncLockTime) { + // process sync if sent in more than 200 ms after previous sync log.debug(s"Processing sync from $remote") syncInfo match { case syncV1: ErgoSyncInfoV1 => processSyncV1(hr, syncV1, remote) @@ -217,8 +222,6 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } } - var globalExtSend = 0L - /** * Processing sync V1 message `syncInfo` got from neighbour peer `remote` */ @@ -239,20 +242,12 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, // we do not know what to send to a peer with such status log.debug(s"Got nonsense status for $remote") case Younger | Fork => - val newExtSend = timeProvider.time() - - if(newExtSend - globalExtSend > 250) { - globalExtSend = newExtSend - - // send extension (up to 400 header ids) to a peer which chain is less developed or forked - val ext = hr.continuationIds(syncInfo, size = 400) - if (ext.isEmpty) log.warn("Extension is empty while comparison is younger") - log.debug(s"Sending extension of length ${ext.length}") - log.debug(s"Extension ids: ${idsToString(ext)}") - sendExtension(remote, ext) - } else { - log.debug("Global extension send violated") - } + // send extension (up to 400 header ids) to a peer which chain is less developed or forked + val ext = hr.continuationIds(syncInfo, size = 400) + if (ext.isEmpty) log.warn("Extension is empty while comparison is younger") + log.debug(s"Sending extension of length ${ext.length}") + log.debug(s"Extension ids: ${idsToString(ext)}") + sendExtension(remote, ext) case Older => // asking headers from older peers val ids = syncInfo.lastHeaderIds.reverse @@ -305,20 +300,12 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, log.warn(s"Got nonsense status in v2 for $remote") case Younger => - val newExtSend = timeProvider.time() - - if (newExtSend - globalExtSend > 250) { - globalExtSend = newExtSend - - // send extension (up to 400 header ids) to a peer which chain is less developed or forked - val ext = hr.continuationIds(syncInfo, size = 400) - if (ext.isEmpty) log.warn("Extension is empty while comparison is younger") - log.debug(s"Sending extension of length ${ext.length}") - log.debug(s"Extension ids: ${idsToString(ext)}") - sendExtension(remote, ext) - } else { - log.debug("Global extension send violated") - } + // send extension (up to 400 header ids) to a peer which chain is less developed or forked + val ext = hr.continuationIds(syncInfo, size = 400) + if (ext.isEmpty) log.warn("Extension is empty while comparison is younger") + log.debug(s"Sending extension of length ${ext.length}") + log.debug(s"Extension ids: ${idsToString(ext)}") + sendExtension(remote, ext) case Fork => log.info(s"Fork detected with peer $remote, its sync message $syncInfo") @@ -379,7 +366,12 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, (fetchMax: Int => Map[ModifierTypeId, Seq[ModifierId]]): Unit = getPeersOpt .foreach { case (peerStatus, peers) => - val modifiersByBucket = ElementPartitioner.distribute(peers, maxModifiers, minModifiersPerBucket, maxModifiersPerBucket)(fetchMax) + // filter out peers of 4.0.17 or 4.0.18 version as they are delivering broken modifiers + val peersFiltered = peers.filterNot { cp => + val version = cp.peerInfo.map(_.peerSpec.protocolVersion).getOrElse(Version.initial) + version == Version.v4017 || version == Version.v4018 + } + val modifiersByBucket = ElementPartitioner.distribute(peersFiltered, maxModifiers, minModifiersPerBucket, maxModifiersPerBucket)(fetchMax) // collect and log useful downloading progress information, don't worry it does not run frequently modifiersByBucket.headOption.foreach { _ => modifiersByBucket @@ -406,7 +398,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, hr: ErgoHistory, data: ModifiersData, remote: ConnectedPeer, - blockAppliedTxsCache: FixedSizeBloomFilterQueue): Unit = { + blockAppliedTxsCache: FixedSizeApproximateCacheQueue): Unit = { val typeId = data.typeId val modifiers = data.modifiers log.info(s"Got ${modifiers.size} modifiers of type $typeId from remote connected peer: ${remote.connectionId}") @@ -476,7 +468,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, def processSpam(remote: ConnectedPeer, typeId: ModifierTypeId, modifiers: Map[ModifierId, Array[Byte]], - blockAppliedTxsCache: FixedSizeBloomFilterQueue): Map[ModifierId, Array[Byte]] = { + blockAppliedTxsCache: FixedSizeApproximateCacheQueue): Map[ModifierId, Array[Byte]] = { val modifiersByStatus = modifiers .groupBy { case (id, _) => deliveryTracker.status(id, typeId, Seq.empty) } @@ -512,7 +504,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, mp: ErgoMemPool, invData: InvData, peer: ConnectedPeer, - blockAppliedTxsCache: FixedSizeBloomFilterQueue): Unit = { + blockAppliedTxsCache: FixedSizeApproximateCacheQueue): Unit = { val modifierTypeId = invData.typeId val newModifierIds = modifierTypeId match { case Transaction.ModifierTypeId => @@ -525,14 +517,14 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, invData.ids.filter(mid => deliveryTracker.status(mid, modifierTypeId, Seq(mp)) == ModifiersStatus.Unknown) // filter out transactions that were already applied to history val notApplied = unknownMods.filterNot(blockAppliedTxsCache.mightContain) - log.info(s"Processing ${invData.ids.length} tx invs frpm $peer, " + + log.info(s"Processing ${invData.ids.length} tx invs from $peer, " + s"${unknownMods.size} of them are unknown, requesting $notApplied") notApplied } else { Seq.empty } case _ => - log.info(s"Processing ${invData.ids.length} non-tx invs (of type $modifierTypeId) frpm $peer") + log.info(s"Processing ${invData.ids.length} non-tx invs (of type $modifierTypeId) from $peer") invData.ids.filter(mid => deliveryTracker.status(mid, modifierTypeId, Seq(hr)) == ModifiersStatus.Unknown) } @@ -706,7 +698,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, case Message(spec, Left(msgBytes), Some(source)) => parseAndHandle(msgHandlers, spec, msgBytes, source) } - protected def viewHolderEvents(historyReader: ErgoHistory, mempoolReader: ErgoMemPool, blockAppliedTxsCache: FixedSizeBloomFilterQueue): Receive = { + protected def viewHolderEvents(historyReader: ErgoHistory, mempoolReader: ErgoMemPool, blockAppliedTxsCache: FixedSizeApproximateCacheQueue): Receive = { // 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`. @@ -779,7 +771,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, /** get handlers of messages coming from peers */ private def msgHandlers(hr: ErgoHistory, mp: ErgoMemPool, - blockAppliedTxsCache: FixedSizeBloomFilterQueue + blockAppliedTxsCache: FixedSizeApproximateCacheQueue ): PartialFunction[(MessageSpec[_], _, ConnectedPeer), Unit] = { case (_: ErgoSyncInfoMessageSpec.type @unchecked, data: ErgoSyncInfo @unchecked, remote) => processSync(hr, data, remote) @@ -791,7 +783,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, modifiersFromRemote(hr, data, remote, blockAppliedTxsCache) } - def initialized(hr: ErgoHistory, mp: ErgoMemPool, blockAppliedTxsCache: FixedSizeBloomFilterQueue): PartialFunction[Any, Unit] = { + def initialized(hr: ErgoHistory, mp: ErgoMemPool, blockAppliedTxsCache: FixedSizeApproximateCacheQueue): PartialFunction[Any, Unit] = { processDataFromPeer(msgHandlers(hr, mp, blockAppliedTxsCache)) orElse onDownloadRequest(hr) orElse getLocalSyncInfo(hr) orElse @@ -803,7 +795,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } /** Wait until both historyReader and mempoolReader instances are received so actor can be operational */ - def initializing(hr: Option[ErgoHistory], mp: Option[ErgoMemPool], blockAppliedTxsCache: FixedSizeBloomFilterQueue): PartialFunction[Any, Unit] = { + def initializing(hr: Option[ErgoHistory], mp: Option[ErgoMemPool], blockAppliedTxsCache: FixedSizeApproximateCacheQueue): PartialFunction[Any, Unit] = { case ChangedHistory(historyReader: ErgoHistory) => mp match { case Some(mempoolReader) => @@ -823,7 +815,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, context.system.scheduler.scheduleOnce(1.second, self, msg) } - override def receive: Receive = initializing(None, None, FixedSizeBloomFilterQueue.empty(bloomFilterQueueSize = 5)) + override def receive: Receive = initializing(None, None, FixedSizeApproximateCacheQueue.empty(cacheQueueSize = 5)) } @@ -837,7 +829,7 @@ object ErgoNodeViewSynchronizer { syncTracker: ErgoSyncTracker) (implicit ex: ExecutionContext): Props = Props(new ErgoNodeViewSynchronizer(networkControllerRef, viewHolderRef, syncInfoSpec, settings, - timeProvider, syncTracker)).withDispatcher(GlobalConstants.NetworkDispatcher) + timeProvider, syncTracker)) def apply(networkControllerRef: ActorRef, viewHolderRef: ActorRef, diff --git a/src/main/scala/org/ergoplatform/network/FixedSizeApproximateCacheQueue.scala b/src/main/scala/org/ergoplatform/network/FixedSizeApproximateCacheQueue.scala new file mode 100644 index 0000000000..729be4b5f6 --- /dev/null +++ b/src/main/scala/org/ergoplatform/network/FixedSizeApproximateCacheQueue.scala @@ -0,0 +1,112 @@ +package org.ergoplatform.network + +import com.google.common.hash.{BloomFilter, Funnels} +import org.ergoplatform.network.FixedSizeApproximateCacheQueue.UnderlyingCache +import java.nio.charset.Charset + +/** + * Any approximate data structure + * @tparam T type of element + */ +sealed trait ApproximateCacheQueueLike[T] { + def putAll(elems: Seq[T]): ApproximateCacheQueueLike[T] + def mightContain(elem: T): Boolean + def approximateElementCount: Long +} + +/** + * Fixed size queue of caches, each representing something like a block + * @param cacheQueueSize how many caches at maximum to keep in FIFO queue + * @param cacheQueue fifo collection of caches + */ +case class FixedSizeApproximateCacheQueue( + cacheQueueSize: Int, + cacheQueue: Vector[UnderlyingCache] +) extends ApproximateCacheQueueLike[String] { + + /** + * Puts elements into underlying caches. + * Ensures that subsequent invocations of mightContain with the elements will always return True + * @return new copy of this instance + */ + override def putAll(elems: Seq[String]): FixedSizeApproximateCacheQueue = { + val cache = UnderlyingCache.newCache(elems) + if (cacheQueue.size < cacheQueueSize) { + this.copy(cacheQueue = cache +: cacheQueue) + } else { + this.copy(cacheQueue = cache +: cacheQueue.dropRight(1)) + } + } + + /** + * Returns True if the element might have been put in these caches, False if this is definitely not the case. + */ + override def mightContain(elem: String): Boolean = + cacheQueue.exists(_.mightContain(elem)) + + /** + * Returns an estimate for the total number of distinct elements that have been added to these + * caches. This approximation is reasonably accurate if approximate caches have not overflown + */ + override def approximateElementCount: Long = + cacheQueue.foldLeft(0L) { + case (acc, bf) => acc + bf.approximateElementCount + } +} + +object FixedSizeApproximateCacheQueue { + val elemCountApproxThreshold = 1000 + + sealed trait UnderlyingCache { + def mightContain(elem: String): Boolean + def approximateElementCount: Long + } + + object UnderlyingCache { + + def newCache(elems: Seq[String]): UnderlyingCache = + if (elems.size > elemCountApproxThreshold) { + ApproxCache.newApproxCache(elems) + } else { + ConciseCache.newConciseCache(elems) + } + } + + case class ConciseCache(xs: Set[String]) extends UnderlyingCache { + override def mightContain(elem: String): Boolean = xs.contains(elem) + override def approximateElementCount: Long = xs.size + } + + object ConciseCache { + def newConciseCache(elems: Seq[String]): ConciseCache = ConciseCache(elems.to[Set]) + } + + case class ApproxCache(bf: BloomFilter[String]) extends UnderlyingCache { + override def mightContain(elem: String): Boolean = bf.mightContain(elem) + override def approximateElementCount: Long = bf.approximateElementCount() + } + + object ApproxCache { + + def newApproxCache(xs: Seq[String]): ApproxCache = { + val bf = createNewFilter(xs.size) + xs.foreach(e => bf.put(e)) + ApproxCache(bf) + } + + private def createNewFilter(approxElemCount: Int) = + BloomFilter.create[String]( + Funnels.stringFunnel(Charset.forName("UTF-8")), + approxElemCount, + 0.05d + ) + } + + /** + * Create empty FixedSizeBloomFilterQueue + * @param cacheQueueSize how many bloom filters at maximum to keep in FIFO queue + * @return new FixedSizeBloomFilterQueue + */ + def empty(cacheQueueSize: Int): FixedSizeApproximateCacheQueue = + FixedSizeApproximateCacheQueue(cacheQueueSize, cacheQueue = Vector.empty) +} diff --git a/src/main/scala/org/ergoplatform/network/FixedSizeBloomFilterQueue.scala b/src/main/scala/org/ergoplatform/network/FixedSizeBloomFilterQueue.scala deleted file mode 100644 index 63068d10ab..0000000000 --- a/src/main/scala/org/ergoplatform/network/FixedSizeBloomFilterQueue.scala +++ /dev/null @@ -1,73 +0,0 @@ -package org.ergoplatform.network - -import com.google.common.hash.{BloomFilter, Funnels} - -import java.nio.charset.Charset - -/** - * Any approximate data structure - * @tparam T type of element - */ -sealed trait BloomFilterLike[T] { - def putAll(elems: Seq[T]): BloomFilterLike[T] - def mightContain(elem: T): Boolean - def approximateElementCount: Long -} - -/** - * Fixed size queue of bloomfilters - * @param bloomFilterQueueSize how many bloom filters at maximum to keep in FIFO queue - * @param bloomFilterQueue fifo collection of bloom filters - */ -case class FixedSizeBloomFilterQueue( - bloomFilterQueueSize: Int, - bloomFilterQueue: Vector[BloomFilter[String]] -) extends BloomFilterLike[String] { - - private def createNewFilter(approxElemCount: Int) = - BloomFilter.create[String]( - Funnels.stringFunnel(Charset.forName("UTF-8")), - approxElemCount, - 0.05d - ) - - /** - * Puts elements into underlying bloomfilters. - * Ensures that subsequent invocations of mightContain with the elements will always return True - * @return new copy of this instance - */ - override def putAll(elems: Seq[String]): FixedSizeBloomFilterQueue = { - val bf = createNewFilter(elems.size) - elems.foreach(e => bf.put(e)) - if (bloomFilterQueue.size < bloomFilterQueueSize) { - this.copy(bloomFilterQueue = bf +: bloomFilterQueue) - } else { - this.copy(bloomFilterQueue = bf +: bloomFilterQueue.dropRight(1)) - } - } - - /** - * Returns True if the element might have been put in these bloom filters, False if this is definitely not the case. - */ - override def mightContain(elem: String): Boolean = - bloomFilterQueue.exists(_.mightContain(elem)) - - /** - * Returns an estimate for the total number of distinct elements that have been added to these - * Bloom filters. This approximation is reasonably accurate if Bloom Filters have not overflown - */ - override def approximateElementCount: Long = - bloomFilterQueue.foldLeft(0L) { - case (acc, bf) => acc + bf.approximateElementCount() - } -} - -object FixedSizeBloomFilterQueue { - /** - * Create empty FixedSizeBloomFilterQueue - * @param bloomFilterQueueSize how many bloom filters at maximum to keep in FIFO queue - * @return new FixedSizeBloomFilterQueue - */ - def empty(bloomFilterQueueSize: Int): FixedSizeBloomFilterQueue = - FixedSizeBloomFilterQueue(bloomFilterQueueSize, bloomFilterQueue = Vector.empty) -} diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 3e10d7a35d..edfa72c542 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -1,8 +1,6 @@ package org.ergoplatform.nodeView import akka.actor.SupervisorStrategy.Escalate -import java.io.File - import akka.actor.{Actor, ActorRef, ActorSystem, OneForOneStrategy, Props} import org.ergoplatform.ErgoApp import org.ergoplatform.ErgoApp.CriticalSystemException @@ -10,16 +8,11 @@ import org.ergoplatform.modifiers.history.extension.Extension import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.modifiers.{ErgoFullBlock, ErgoPersistentModifier} -import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages._ -import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages._ -import org.ergoplatform.nodeView.ErgoNodeViewHolder.{CurrentView, DownloadRequest} -import org.ergoplatform.nodeView.ErgoNodeViewHolder.BlockAppliedTransactions import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} import org.ergoplatform.nodeView.mempool.ErgoMemPool import org.ergoplatform.nodeView.mempool.ErgoMemPool.ProcessingOutcome import org.ergoplatform.nodeView.state._ import org.ergoplatform.nodeView.wallet.ErgoWallet -import org.ergoplatform.settings.{Algos, Constants, ErgoSettings} import org.ergoplatform.wallet.utils.FileUtils import org.ergoplatform.settings.{Algos, Constants, ErgoSettings, Parameters} import scorex.core._ @@ -31,8 +24,8 @@ import scorex.core.settings.ScorexSettings import scorex.core.utils.{NetworkTimeProvider, ScorexEncoding} import scorex.util.ScorexLogging import spire.syntax.all.cfor - import java.io.File + import scala.annotation.tailrec import scala.collection.mutable import scala.util.{Failure, Success, Try} @@ -229,7 +222,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti f case (success@Success(updateInfo), modToApply) => if (updateInfo.failedMod.isEmpty) { - updateInfo.state.applyModifier(modToApply) match { + updateInfo.state.applyModifier(modToApply)(lm => pmodModify(lm.pmod, local = true)) match { case Success(stateAfterApply) => history.reportModifierIsValid(modToApply).map { newHis => context.system.eventStream.publish(SemanticallySuccessfulModifier(modToApply)) @@ -376,7 +369,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti val history = ErgoHistory.readOrGenerate(settings, timeProvider) log.info("History database read") val memPool = ErgoMemPool.empty(settings) - val constants = StateConstants(Some(self), settings) + val constants = StateConstants(settings) restoreConsistentState(ErgoState.readOrGenerate(settings, constants, parameters).asInstanceOf[State], history) match { case Success(state) => log.info("State database read, state synchronized") @@ -442,7 +435,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti log.info(s"Persistent modifier ${pmod.encodedId} applied successfully") updateNodeView(Some(newHistory), Some(newMinState), Some(newVault), Some(newMemPool)) chainProgress = - Some(ChainProgress(pmod, headersHeight, fullBlockHeight, System.currentTimeMillis())) + Some(ChainProgress(pmod, headersHeight, fullBlockHeight, timeProvider.time())) case Failure(e) => log.warn(s"Can`t apply persistent modifier (id: ${pmod.encodedId}, contents: $pmod) to minimal state", e) updateNodeView(updatedHistory = Some(newHistory)) @@ -468,7 +461,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti val dir = stateDir(settings) deleteRecursive(dir) - val constants = StateConstants(Some(self), settings) + val constants = StateConstants(settings) ErgoState.readOrGenerate(settings, constants, parameters) .asInstanceOf[State] .ensuring( @@ -505,7 +498,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti } toApply.foldLeft[Try[State]](Success(initState)) { case (acc, m) => log.info(s"Applying modifier during node start-up to restore consistent state: ${m.id}") - acc.flatMap(_.applyModifier(m)) + acc.flatMap(_.applyModifier(m)(lm => self ! lm)) } } } @@ -514,7 +507,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti * Recovers digest state from history. */ private def recoverDigestState(bestFullBlock: ErgoFullBlock, history: ErgoHistory): Try[DigestState] = { - val constants = StateConstants(Some(self), settings) + val constants = StateConstants(settings) val votingLength = settings.chainSettings.voting.votingLength val bestHeight = bestFullBlock.header.height val newEpochHeadersQty = bestHeight % votingLength // how many blocks current epoch lasts @@ -536,12 +529,16 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti recoveredStateTry match { case Success(state) => log.info("Recovering state using current epoch") - chainToApply.foldLeft[Try[DigestState]](Success(state))((acc, m) => acc.flatMap(_.applyModifier(m))) + chainToApply.foldLeft[Try[DigestState]](Success(state)) { case (acc, m) => + acc.flatMap(_.applyModifier(m)(lm => self ! lm)) + } case Failure(exception) => // recover using whole headers chain log.warn(s"Failed to recover state from current epoch, using whole chain: ${exception.getMessage}") val wholeChain = history.headerChainBack(Int.MaxValue, bestFullBlock.header, _.isGenesis).headers val genesisState = DigestState.create(None, None, stateDir(settings), constants, parameters) - wholeChain.foldLeft[Try[DigestState]](Success(genesisState))((acc, m) => acc.flatMap(_.applyModifier(m))) + wholeChain.foldLeft[Try[DigestState]](Success(genesisState)) { case (acc, m) => + acc.flatMap(_.applyModifier(m)(lm => self ! lm)) + } } } @@ -584,8 +581,9 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti protected def handleHealthCheck: Receive = { case IsChainHealthy => + log.debug(s"Check that chain is healthy, progress is $chainProgress") val healthCheckReply = chainProgress.map { progress => - ErgoNodeViewHolder.checkChainIsHealthy(progress, history(), settings) + ErgoNodeViewHolder.checkChainIsHealthy(progress, history(), timeProvider, settings) }.getOrElse(ChainIsHealthy) sender() ! healthCheckReply } @@ -652,14 +650,15 @@ object ErgoNodeViewHolder { def checkChainIsHealthy( progress: ChainProgress, history: ErgoHistory, + timeProvider: NetworkTimeProvider, settings: ErgoSettings): HealthCheckResult = { val ChainProgress(lastMod, headersHeight, blockHeight, lastUpdate) = progress - val chainUpdateDelay = System.currentTimeMillis() - lastUpdate + val chainUpdateDelay = timeProvider.time() - lastUpdate val acceptableChainUpdateDelay = settings.nodeSettings.acceptableChainUpdateDelay def chainUpdateDelayed = chainUpdateDelay > acceptableChainUpdateDelay.toMillis def blockUpdateDelayed = history.bestFullBlockOpt - .map(b => System.currentTimeMillis() - b.header.timestamp) + .map(b => timeProvider.time() - b.header.timestamp) .exists(blockUpdateDelay => blockUpdateDelay > acceptableChainUpdateDelay.toMillis) def chainSynced = @@ -670,10 +669,15 @@ object ErgoNodeViewHolder { history.bestFullBlockOpt .filter(_.id != lastMod.id) .fold("")(fb => s"\n best full block: $fb") - val repairNeeded = ErgoHistory.repairIfNeeded(history) + val repairNeeded = if(blockUpdateDelayed) { + ErgoHistory.repairIfNeeded(history) + } else { + false + } ChainIsStuck(s"Chain not modified for $chainUpdateDelay ms, headers-height: $headersHeight, " + s"block-height $blockHeight, chain synced: $chainSynced, repair needed: $repairNeeded, " + - s"last modifier applied: $lastMod $bestFullBlockOpt") + s"last modifier applied: $lastMod, " + + s"possible best full block $bestFullBlockOpt") } else { ChainIsHealthy } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala index 6aae95e385..25ede9bef8 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala @@ -190,16 +190,20 @@ trait ErgoHistory * @return */ def forgetHeader(headerId: ModifierId): Try[Unit] = Try { - typedModifierById[Header](headerId).foreach { h => - historyStorage.remove( + val hOpt = typedModifierById[Header](headerId) + val hRes = historyStorage.remove( indicesToRemove = Seq(validityKey(headerId), headerHeightKey(headerId), headerScoreKey(headerId)), idsToRemove = Seq(headerId) - ).get + ) + log.info(s"Result of removing header $headerId: " + hRes) + + hOpt.foreach { h => requiredModifiersForHeader(h).foreach { case (_, mId) => - historyStorage.remove( + val mRes = historyStorage.remove( indicesToRemove = Seq(validityKey(mId)), idsToRemove = Seq(mId) - ).get + ) + log.info(s"Result of removing modifier $mId: " + mRes) } } } @@ -227,11 +231,12 @@ object ErgoHistory extends ScorexLogging { // check if there is possible database corruption when there is header after // recognized blockchain tip marked as invalid - protected[nodeView] def repairIfNeeded(history: ErgoHistory): Boolean = { + protected[nodeView] def repairIfNeeded(history: ErgoHistory): Boolean = history.historyStorage.synchronized { val bestHeaderHeight = history.headersHeight + val bestFullBlockHeight = history.bestFullBlockOpt.map(_.height).getOrElse(-1) val afterHeaders = history.headerIdsAtHeight(bestHeaderHeight + 1) - if (afterHeaders.nonEmpty) { + if (bestHeaderHeight == bestFullBlockHeight && afterHeaders.nonEmpty) { log.warn("Found suspicious continuation, clearing it...") afterHeaders.map { hId => history.forgetHeader(hId) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala index 1872c7fdf2..a7ecef4904 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala @@ -8,7 +8,7 @@ import org.ergoplatform.nodeView.state.{ErgoState, UtxoState} import org.ergoplatform.settings.{ErgoSettings, MonetarySettings, NodeConfigurationSettings} import scorex.core.transaction.MemoryPool import scorex.core.transaction.state.TransactionValidation -import scorex.util.{ModifierId, bytesToId} +import scorex.util.{ModifierId, ScorexLogging, bytesToId} import OrderedTxPool.weighted import spire.syntax.all.cfor @@ -25,8 +25,9 @@ import scala.util.Try * @param stats - Mempool statistics, that allows to track * information about mempool's state and transactions in it. */ -class ErgoMemPool private[mempool](pool: OrderedTxPool, private[mempool] val stats : MemPoolStatistics)(implicit settings: ErgoSettings) - extends MemoryPool[ErgoTransaction, ErgoMemPool] with ErgoMemPoolReader { +class ErgoMemPool private[mempool](pool: OrderedTxPool, + private[mempool] val stats : MemPoolStatistics)(implicit settings: ErgoSettings) + extends MemoryPool[ErgoTransaction, ErgoMemPool] with ErgoMemPoolReader with ScorexLogging { import ErgoMemPool._ import EmissionRules.CoinsInOneErgo @@ -128,6 +129,8 @@ class ErgoMemPool private[mempool](pool: OrderedTxPool, private[mempool] val sta } def process(tx: ErgoTransaction, state: ErgoState[_]): (ErgoMemPool, ProcessingOutcome) = { + log.info(s"Processing mempool transaction: $tx") + val blacklistedTransactions = nodeSettings.blacklistedTransactions if(blacklistedTransactions.nonEmpty && blacklistedTransactions.contains(tx.id)) { new ErgoMemPool(pool.invalidate(tx), stats) -> ProcessingOutcome.Invalidated(new Exception("blacklisted tx")) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala index 18191423c1..a36946976c 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala @@ -1,12 +1,12 @@ package org.ergoplatform.nodeView.state import java.io.File - import org.ergoplatform.ErgoBox import org.ergoplatform.modifiers.history.ADProofs import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.modifiers.{ErgoFullBlock, ErgoPersistentModifier} +import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedModifier import org.ergoplatform.nodeView.state.ErgoState.ModifierProcessing import org.ergoplatform.settings._ import org.ergoplatform.utils.LoggingUtil @@ -35,7 +35,7 @@ class DigestState protected(override val version: VersionTag, store.lastVersionID .foreach(id => require(version == bytesToVersion(id), "version should always be equal to store.lastVersionID")) - override val constants: StateConstants = StateConstants(None, ergoSettings) + override val constants: StateConstants = StateConstants(ergoSettings) private lazy val nodeSettings = ergoSettings.nodeSettings @@ -78,7 +78,7 @@ class DigestState protected(override val version: VersionTag, Failure(new Exception(s"Modifier not validated: $a")) } - override def applyModifier(mod: ErgoPersistentModifier): Try[DigestState] = + override def applyModifier(mod: ErgoPersistentModifier)(generate: LocallyGeneratedModifier => Unit): Try[DigestState] = (processFullBlock orElse processHeader orElse processOther) (mod) @SuppressWarnings(Array("OptionGet")) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala index 5df6bbb8e0..e7917adac6 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala @@ -9,6 +9,7 @@ import org.ergoplatform.modifiers.ErgoPersistentModifier import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.modifiers.state.{Insertion, Lookup, Removal, StateChanges} +import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedModifier import org.ergoplatform.nodeView.history.ErgoHistory import org.ergoplatform.settings.ValidationRules._ import org.ergoplatform.settings.{ChainSettings, Constants, ErgoSettings, Parameters} @@ -41,7 +42,13 @@ trait ErgoState[IState <: ErgoState[IState]] extends ErgoStateReader { self: IState => - def applyModifier(mod: ErgoPersistentModifier): Try[IState] + /** + * + * @param mod modifire to apply to the state + * @param generate function that handles newly created modifier as a result of application the current one + * @return new State + */ + def applyModifier(mod: ErgoPersistentModifier)(generate: LocallyGeneratedModifier => Unit): Try[IState] def rollbackTo(version: VersionTag): Try[IState] @@ -240,7 +247,7 @@ object ErgoState extends ScorexLogging { def generateGenesisDigestState(stateDir: File, settings: ErgoSettings, parameters: Parameters): DigestState = { DigestState.create(Some(genesisStateVersion), Some(settings.chainSettings.genesisStateDigest), - stateDir, StateConstants(None, settings), parameters) + stateDir, StateConstants(settings), parameters) } val preGenesisStateDigest: ADDigest = ADDigest @@ Array.fill(32)(0: Byte) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/StateConstants.scala b/src/main/scala/org/ergoplatform/nodeView/state/StateConstants.scala index 5547a574c1..7c51acde08 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/StateConstants.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/StateConstants.scala @@ -1,16 +1,14 @@ package org.ergoplatform.nodeView.state -import akka.actor.ActorRef import org.ergoplatform.settings.{ErgoSettings, VotingSettings} import scorex.crypto.authds.ADDigest /** * Constants that do not change with state version changes * - * @param nodeViewHolderRef - actor ref of node view holder * @param settings - node settings */ -case class StateConstants(nodeViewHolderRef: Option[ActorRef], settings: ErgoSettings) { +case class StateConstants(settings: ErgoSettings) { lazy val keepVersions: Int = settings.nodeSettings.keepVersions lazy val votingSettings: VotingSettings = settings.chainSettings.voting diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala index 5cfc1e96fa..7ff992b173 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala @@ -46,11 +46,6 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 persistentProver.digest } - private def onAdProofGenerated(proof: ADProofs): Unit = { - if (constants.nodeViewHolderRef.isEmpty) log.warn("Got proof while nodeViewHolderRef is empty") - constants.nodeViewHolderRef.foreach(h => h ! LocallyGeneratedModifier(proof)) - } - import UtxoState.metadata override def rollbackTo(version: VersionTag): Try[UtxoState] = persistentProver.synchronized { @@ -96,7 +91,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 } } - override def applyModifier(mod: ErgoPersistentModifier): Try[UtxoState] = mod match { + override def applyModifier(mod: ErgoPersistentModifier)(generate: LocallyGeneratedModifier => Unit): Try[UtxoState] = mod match { case fb: ErgoFullBlock => persistentProver.synchronized { val height = fb.header.height @@ -106,12 +101,29 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 val inRoot = rootHash val stateTry = stateContext.appendFullBlock(fb).flatMap { newStateContext => - applyTransactions(fb.blockTransactions.txs, fb.header.stateRoot, newStateContext).map { _: Unit => + val tm0 = System.currentTimeMillis() + val txsTry = applyTransactions(fb.blockTransactions.txs, fb.header.stateRoot, newStateContext) + val tm = System.currentTimeMillis() + log.debug(s"Transactions at height $height checked in ${tm-tm0} ms.") + + txsTry.map { _: Unit => val emissionBox = extractEmissionBox(fb) val meta = metadata(idToVersion(fb.id), fb.header.stateRoot, emissionBox, newStateContext) + + val tp0 = System.currentTimeMillis() val proofBytes = persistentProver.generateProofAndUpdateStorage(meta) + val tp = System.currentTimeMillis() + log.debug(s"Utxo storage at height $height updated in ${tp-tp0} ms.") + val proofHash = ADProofs.proofDigest(proofBytes) - if (fb.adProofs.isEmpty) onAdProofGenerated(ADProofs(fb.header.id, proofBytes)) + + if (fb.adProofs.isEmpty) { + val ta0 = System.currentTimeMillis() + val adProofs = ADProofs(fb.header.id, proofBytes) + generate(LocallyGeneratedModifier(adProofs)) + val ta = System.currentTimeMillis() + log.debug(s"UTXO set transformation proofs at height $height dumped in ${ta-ta0} ms.") + } if (!store.get(scorex.core.idToBytes(fb.id)).exists(w => java.util.Arrays.equals(w, fb.header.stateRoot))) { throw new Error("Storage kept roothash is not equal to the declared one") diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala index 0fa18020ff..c241df7cb5 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala @@ -7,11 +7,11 @@ import org.ergoplatform.{DataInput, ErgoAddress, ErgoAddressEncoder, ErgoBox, Er import org.ergoplatform.modifiers.mempool.UnsignedErgoTransaction import org.ergoplatform.nodeView.wallet.ErgoWalletService.DeriveNextKeyResult import org.ergoplatform.nodeView.wallet.persistence.WalletStorage -import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, PaymentRequest, TransactionGenerationRequest} +import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, BurnTokensRequest, PaymentRequest, TransactionGenerationRequest} import org.ergoplatform.settings.Parameters import org.ergoplatform.utils.BoxUtils +import org.ergoplatform.wallet.{AssetUtils, Constants} import org.ergoplatform.wallet.interface4j.SecretString -import org.ergoplatform.wallet.Constants import org.ergoplatform.wallet.Constants.PaymentsScanId import org.ergoplatform.wallet.boxes.BoxSelector.BoxSelectionResult import org.ergoplatform.wallet.boxes.{BoxSelector, TrackedBox} @@ -50,7 +50,7 @@ trait ErgoWalletSupport extends ScorexLogging { protected def deriveNextKeyForMasterKey(state: ErgoWalletState, masterKey: ExtendedSecretKey, usePreEip3Derivation: Boolean) - (implicit addrEncoder: ErgoAddressEncoder): Try[(DeriveNextKeyResult, ErgoWalletState)] = { + (implicit addrEncoder: ErgoAddressEncoder): Try[(DeriveNextKeyResult, ErgoWalletState)] = { val secrets = state.walletVars.proverOpt.toIndexedSeq.flatMap(_.hdKeys) val derivationResult = DerivationPath.nextPath(secrets, usePreEip3Derivation).map { path => val secret = masterKey.derive(path) @@ -58,7 +58,7 @@ trait ErgoWalletSupport extends ScorexLogging { } derivationResult.map(_._3) .flatMap(secret => addSecretToStorage(state, secret)) - .map( newState => DeriveNextKeyResult(derivationResult) -> newState ) + .map(newState => DeriveNextKeyResult(derivationResult) -> newState) } protected def updatePublicKeys(state: ErgoWalletState, @@ -77,7 +77,7 @@ trait ErgoWalletSupport extends ScorexLogging { (masterKey +: sks, masterKey.publicKey +: pks) } val prover = new ErgoProvingInterpreter(secrets, state.parameters, Some(pubKeys))(new RuntimeIRContext) - log.info(s"Wallet unlock: ${prover.hdPubKeys.length} keys read" ) + log.info(s"Wallet unlock: ${prover.hdPubKeys.length} keys read") state.copy(walletVars = state.walletVars.withProver(prover)) } @@ -134,8 +134,8 @@ trait ErgoWalletSupport extends ScorexLogging { } } else { if (pubKeys.size == 1 && - pubKeys.head.path == Constants.eip3DerivationPath.toPublicBranch && - state.storage.readChangeAddress.isEmpty) { + pubKeys.head.path == Constants.eip3DerivationPath.toPublicBranch && + state.storage.readChangeAddress.isEmpty) { val changeAddress = P2PKAddress(pubKeys.head.key) log.info(s"Update change address to $changeAddress") state.storage.updateChangeAddress(changeAddress) @@ -268,8 +268,17 @@ trait ErgoWalletSupport extends ScorexLogging { require(inputBoxes.nonEmpty, "There must be at least one input box") + //filter burnTokens requests + val (requestsWithBurnTokens, requestsWithoutBurnTokens) = requests.partition(_.isInstanceOf[BurnTokensRequest]) + val burnTokensMap = TransactionBuilder.collTokensToMap( + requestsWithBurnTokens + .map(_.asInstanceOf[BurnTokensRequest]) + .flatMap(_.assetsToBurn) + .toColl + ) + //We're getting id of the first input, it will be used in case of asset issuance (asset id == first input id) - requestsToBoxCandidates(requests, inputBoxes.head.box.id, state.fullHeight, state.parameters, state.walletVars.publicKeyAddresses) + requestsToBoxCandidates(requestsWithoutBurnTokens, inputBoxes.head.box.id, state.fullHeight, state.parameters, state.walletVars.publicKeyAddresses) .flatMap { outputs => require(outputs.forall(c => c.value >= BoxUtils.minimalErgoAmountSimulated(c, state.parameters)), "Minimal ERG value not met") require(outputs.forall(_.additionalTokens.forall(_._2 > 0)), "Non-positive asset value") @@ -283,7 +292,11 @@ trait ErgoWalletSupport extends ScorexLogging { val targetBalance = outputs.map(_.value).sum val targetAssets = TransactionBuilder.collectOutputTokens(outputs.filterNot(bx => assetIssueBox.contains(bx))) - val selectionOpt = boxSelector.select(inputBoxes.iterator, targetBalance, targetAssets) + //add burnTokens to target assets so that they are excluded from the change outputs + //thus total outputs assets will be reduced which is interpreted as _token burning_ + val targetAssetsWithBurn = AssetUtils.mergeAssets(targetAssets, burnTokensMap) + + val selectionOpt = boxSelector.select(inputBoxes.iterator, targetBalance, targetAssetsWithBurn) val dataInputs = ErgoWalletService.stringsToBoxes(dataInputsRaw).toIndexedSeq selectionOpt.map { selectionResult => val changeAddressOpt: Option[ProveDlog] = state.getChangeAddress.map(_.pubkey) @@ -294,7 +307,7 @@ trait ErgoWalletSupport extends ScorexLogging { new Exception(s"Failed to find boxes to assemble a transaction for $outputs, \nreason: $e") ) } - } + } }.flatten } diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/BurnTokensRequest.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/BurnTokensRequest.scala new file mode 100644 index 0000000000..4766626eb3 --- /dev/null +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/BurnTokensRequest.scala @@ -0,0 +1,33 @@ +package org.ergoplatform.nodeView.wallet.requests + +import io.circe.syntax._ +import io.circe.{Decoder, Encoder, HCursor, Json} +import org.ergoplatform.modifiers.mempool.ErgoTransaction._ +import org.ergoplatform.ErgoBox + +/** + * Request for asset burning. + * + * @param assetsToBurn sequence of token id's and amount to burn + * + */ +case class BurnTokensRequest(assetsToBurn: Seq[(ErgoBox.TokenId, Long)]) + extends TransactionGenerationRequest + +class BurnTokensRequestEncoder extends Encoder[BurnTokensRequest] { + def apply(request: BurnTokensRequest): Json = { + Json.obj( + "assetsToBurn" -> request.assetsToBurn.asJson + ) + } + +} + +class BurnTokensRequestDecoder extends Decoder[BurnTokensRequest] { + def apply(cursor: HCursor): Decoder.Result[BurnTokensRequest] = { + for { + assetsToBurn <- cursor.downField("assetsToBurn").as[Option[Seq[(ErgoBox.TokenId, Long)]]] + } yield BurnTokensRequest(assetsToBurn.toSeq.flatten) + } + +} diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/TransactionGenerationRequest.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/TransactionGenerationRequest.scala index f40bc7e722..545fa2f013 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/TransactionGenerationRequest.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/TransactionGenerationRequest.scala @@ -15,6 +15,7 @@ class TransactionRequestEncoder(settings: ErgoSettings) extends Encoder[Transact def apply(request: TransactionGenerationRequest): Json = request match { case pr: PaymentRequest => new PaymentRequestEncoder(settings)(pr) case ar: AssetIssueRequest => new AssetIssueRequestEncoder(settings)(ar) + case br: BurnTokensRequest => new BurnTokensRequestEncoder()(br) case other => throw new Exception(s"Unknown TransactionRequest type: $other") } @@ -24,14 +25,19 @@ class TransactionRequestDecoder(settings: ErgoSettings) extends Decoder[Transact val paymentRequestDecoder: PaymentRequestDecoder = new PaymentRequestDecoder(settings) val assetIssueRequestDecoder: AssetIssueRequestDecoder = new AssetIssueRequestDecoder(settings) + val burnTokensRequestDecoder: BurnTokensRequestDecoder = new BurnTokensRequestDecoder() def apply(cursor: HCursor): Decoder.Result[TransactionGenerationRequest] = { val paymentRequestDecoderResult = paymentRequestDecoder.apply(cursor) val assetIssueRequestDecoderResult = assetIssueRequestDecoder.apply(cursor) - if(paymentRequestDecoderResult.isLeft && assetIssueRequestDecoderResult.isLeft) { + val burnTokensRequestDecoderResult = burnTokensRequestDecoder.apply(cursor) + if (paymentRequestDecoderResult.isLeft && + assetIssueRequestDecoderResult.isLeft && + burnTokensRequestDecoderResult.isLeft + ) { paymentRequestDecoderResult } else { - Seq(paymentRequestDecoderResult,assetIssueRequestDecoderResult) + Seq(paymentRequestDecoderResult, assetIssueRequestDecoderResult, burnTokensRequestDecoderResult) .find(_.isRight) .get } diff --git a/src/main/scala/scorex/core/app/Version.scala b/src/main/scala/scorex/core/app/Version.scala index 9c8e8494a9..201adb1f13 100644 --- a/src/main/scala/scorex/core/app/Version.scala +++ b/src/main/scala/scorex/core/app/Version.scala @@ -33,6 +33,10 @@ object Version { val initial: Version = Version(0, 0, 1) + val v4017: Version = Version(4, 0, 17) + + val v4018: Version = Version(4, 0, 18) + } object ApplicationVersionSerializer extends ScorexSerializer[Version] { diff --git a/src/main/scala/scorex/core/network/DeliveryTracker.scala b/src/main/scala/scorex/core/network/DeliveryTracker.scala index 3a482e42a4..4410852087 100644 --- a/src/main/scala/scorex/core/network/DeliveryTracker.scala +++ b/src/main/scala/scorex/core/network/DeliveryTracker.scala @@ -120,7 +120,7 @@ class DeliveryTracker(system: ActorSystem, val checks = requested(modifierTypeId)(modifierId).checks + 1 setUnknown(modifierId, modifierTypeId) if (checks < maxDeliveryChecks) setRequested(modifierId, modifierTypeId, Some(cp), checks) - else throw new StopExpectingError(modifierId, checks) + else throw new StopExpectingError(modifierId, modifierTypeId, checks) } /** @@ -284,8 +284,8 @@ class DeliveryTracker(system: ActorSystem, () } - class StopExpectingError(mid: ModifierId, checks: Int) - extends Error(s"Stop expecting ${encoder.encodeId(mid)} due to exceeded number of retries $checks") + class StopExpectingError(mid: ModifierId, mType: ModifierTypeId, checks: Int) + extends Error(s"Stop expecting ${encoder.encodeId(mid)} of type $mType due to exceeded number of retries $checks") private def tryWithLogging[T](fn: => T): Try[T] = Try(fn).recoverWith { diff --git a/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala index 002a1d7d14..14ed8d3cea 100644 --- a/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/ScriptApiRouteSpec.scala @@ -56,7 +56,7 @@ class ScriptApiRouteSpec extends AnyFlatSpec val cost = json.hcursor.downField("cost").as[Int].right.get value shouldEqual -45 condition shouldEqual true - cost shouldEqual 40 + cost shouldEqual 50 } val json = io.circe.parser.parse(req) Post(prefix + suffix, json) ~> route ~> check(assertion(responseAs[Json])) diff --git a/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala b/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala index 942c672b37..bbded6e013 100644 --- a/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala +++ b/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala @@ -43,9 +43,12 @@ class MempoolAuditorSpec extends AnyFlatSpec with NodeViewTestOps with ErgoTestH val testProbe = new TestProbe(actorSystem) actorSystem.eventStream.subscribe(testProbe.ref, newTx) - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = + WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis) { mod => + nodeViewHolderRef ! mod + } .get applyBlock(genesis) shouldBe 'success getRootHash shouldBe Algos.encode(wusAfterGenesis.rootHash) @@ -88,11 +91,11 @@ class MempoolAuditorSpec extends AnyFlatSpec with NodeViewTestOps with ErgoTestH it should "rebroadcast transactions correctly" in { - val (us0, bh0) = createUtxoState(parameters, None) + val (us0, bh0) = createUtxoState(parameters) val (txs0, bh1) = validTransactionsFromBoxHolder(bh0) val b1 = validFullBlock(None, us0, txs0) - val us = us0.applyModifier(b1).get + val us = us0.applyModifier(b1)(_ => ()).get val bxs = bh1.boxes.values.toList.filter(_.proposition != genesisEmissionBox.proposition) val txs = validTransactionsFromBoxes(200000, bxs, new RandomWrapper)._1 diff --git a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala index d2522367cb..18eae2d466 100644 --- a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala @@ -67,8 +67,8 @@ class CandidateGeneratorPropSpec extends ErgoPropertyTest { defaultMinerPk ) - us.applyModifier(validFullBlock(None, us, incorrectTxs)) shouldBe 'failure - us.applyModifier(validFullBlock(None, us, txs)) shouldBe 'success + us.applyModifier(validFullBlock(None, us, incorrectTxs))(_ => ()) shouldBe 'failure + us.applyModifier(validFullBlock(None, us, txs))(_ => ()) shouldBe 'success } property("collect reward from transaction fees only") { @@ -93,8 +93,8 @@ class CandidateGeneratorPropSpec extends ErgoPropertyTest { defaultMinerPk ) - us.applyModifier(validFullBlock(None, us, blockTx +: incorrect)) shouldBe 'failure - us.applyModifier(validFullBlock(None, us, blockTx +: txs)) shouldBe 'success + us.applyModifier(validFullBlock(None, us, blockTx +: incorrect))(_ => ()) shouldBe 'failure + us.applyModifier(validFullBlock(None, us, blockTx +: txs))(_ => ()) shouldBe 'success } property("filter out double spend txs") { @@ -215,7 +215,7 @@ class CandidateGeneratorPropSpec extends ErgoPropertyTest { .toSeq val block = validFullBlock(None, us, blockTx +: txs) - us = us.applyModifier(block).get + us = us.applyModifier(block)(_ => ()).get val blockTx2 = validTransactionFromBoxes(txBoxes(1), outputsProposition = feeProposition) @@ -227,9 +227,9 @@ class CandidateGeneratorPropSpec extends ErgoPropertyTest { val invalidBlock2 = validFullBlock(Some(block), us, IndexedSeq(earlySpendingTx, blockTx2)) - us.applyModifier(invalidBlock2) shouldBe 'failure + us.applyModifier(invalidBlock2)(_ => ()) shouldBe 'failure - us = us.applyModifier(block2).get + us = us.applyModifier(block2)(_ => ()).get val earlySpendingTx2 = validTransactionFromBoxes(txs.head.outputs, stateCtxOpt = Some(us.stateContext)) @@ -238,7 +238,7 @@ class CandidateGeneratorPropSpec extends ErgoPropertyTest { validTransactionFromBoxes(txBoxes(2), outputsProposition = feeProposition) val block3 = validFullBlock(Some(block2), us, IndexedSeq(earlySpendingTx2, blockTx3)) - us.applyModifier(block3) shouldBe 'success + us.applyModifier(block3)(_ => ()) shouldBe 'success } property("collect reward from both emission box and fees") { diff --git a/src/test/scala/org/ergoplatform/network/FixedSizeApproximateCacheQueueSpec.scala b/src/test/scala/org/ergoplatform/network/FixedSizeApproximateCacheQueueSpec.scala new file mode 100644 index 0000000000..de06f3d9c7 --- /dev/null +++ b/src/test/scala/org/ergoplatform/network/FixedSizeApproximateCacheQueueSpec.scala @@ -0,0 +1,51 @@ +package org.ergoplatform.network + +import org.ergoplatform.network.FixedSizeApproximateCacheQueue.{ApproxCache, ConciseCache, elemCountApproxThreshold} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class FixedSizeApproximateCacheQueueSpec extends AnyFlatSpec with Matchers { + + it should "create fixed size amount of caches" in { + val queue = FixedSizeApproximateCacheQueue.empty(cacheQueueSize = 5) + val queue1 = + queue + .putAll((1 to 100).map(_.toString)) + .putAll((101 to 200).map(_.toString)) + .putAll((201 to 300).map(_.toString)) + + (1 to 300).foreach { n => + assert(queue1.mightContain(n.toString), s"$n should be in cache") + } + + queue1.cacheQueue.size shouldBe 3 + + val queue2 = + queue1 + .putAll(Vector("301")) + .putAll(Vector("302")) + .putAll(Vector("303")) + .putAll(Vector("304")) + .putAll(Vector("305")) + queue2.cacheQueue.size shouldBe 5 + + (301 to 305).foreach { n => + assert(queue2.mightContain(n.toString), s"$n should be in cache") + } + } + + it should "create different types of caches based on reaching approx threshold" in { + val queue = FixedSizeApproximateCacheQueue.empty(cacheQueueSize = 2) + + val fewElemQueue = queue.putAll((1 to 10).map(_.toString)) + assert(fewElemQueue.cacheQueue.head.isInstanceOf[ConciseCache], "Few elements should go to concise cache") + + val manyElemQueue = queue.putAll((1 to (elemCountApproxThreshold + 10)).map(_.toString)) + assert(manyElemQueue.cacheQueue.head.isInstanceOf[ApproxCache], "Many elements should go to approximate cache") + + val combinedQueue = manyElemQueue.putAll((1 to 10).map(_.toString)) + assert(combinedQueue.cacheQueue.head.isInstanceOf[ConciseCache], "Few elements should go to concise cache") + assert(combinedQueue.cacheQueue.last.isInstanceOf[ApproxCache], "Many elements should go to approximate cache") + } + +} diff --git a/src/test/scala/org/ergoplatform/network/FixedSizeBloomFilterQueueSpec.scala b/src/test/scala/org/ergoplatform/network/FixedSizeBloomFilterQueueSpec.scala deleted file mode 100644 index e235737c33..0000000000 --- a/src/test/scala/org/ergoplatform/network/FixedSizeBloomFilterQueueSpec.scala +++ /dev/null @@ -1,37 +0,0 @@ -package org.ergoplatform.network - -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class FixedSizeBloomFilterQueueSpec extends AnyFlatSpec with Matchers { - - it should "create fixed size amount of bloom filters" in { - val queue = FixedSizeBloomFilterQueue.empty(bloomFilterQueueSize = 5) - val queue1 = - queue - .putAll((1 to 100).map(_.toString)) - .putAll((101 to 200).map(_.toString)) - .putAll((201 to 300).map(_.toString)) - - (1 to 300).foreach { n => - assert(queue1.mightContain(n.toString), s"$n should be in bloom filter") - } - - queue1.bloomFilterQueue.size shouldBe 3 - - val queue2 = - queue1 - .putAll(Vector("301")) - .putAll(Vector("302")) - .putAll(Vector("303")) - .putAll(Vector("304")) - .putAll(Vector("305")) - queue2.bloomFilterQueue.size shouldBe 5 - - (301 to 305).foreach { n => - assert(queue2.mightContain(n.toString), s"$n should be in bloom filter") - } - - } - -} diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index b026244bfc..cb80ef1260 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -19,7 +19,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "accept valid transaction" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val txs = validTransactionsFromUtxoState(wus) val pool0 = ErgoMemPool.empty(settings) val poolAfter = txs.foldLeft(pool0) { case (pool, tx) => @@ -41,7 +41,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "decline already contained transaction" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val txs = validTransactionsFromUtxoState(wus) var pool = ErgoMemPool.empty(settings) txs.foreach { tx => @@ -57,7 +57,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec whenever(n1 != n2) { val (us, bh) = createUtxoState(extendedParameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, extendedParameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, extendedParameters).applyModifier(genesis)(_ => ()).get val feeProp = settings.chainSettings.monetary.feeProposition val inputBox = wus.takeBoxes(1).head @@ -113,7 +113,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "decline transactions not meeting min fee" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val txs = validTransactionsFromUtxoState(wus) val maxSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(minimalFeeAmount = Long.MaxValue)) @@ -174,7 +174,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "Accept output of pooled transactions" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val txs = validTransactionsFromUtxoState(wus) var pool = ErgoMemPool.empty(settings) txs.foreach { tx => @@ -192,7 +192,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "consider families for replacement policy" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get var txs = validTransactionsFromUtxoState(wus) val family_depth = 10 val limitedPoolSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(mempoolCapacity = (family_depth + 1) * txs.size)) @@ -222,7 +222,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "correctly remove transaction from pool and rebuild families" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get var txs = validTransactionsFromUtxoState(wus) var allTxs = txs val family_depth = 10 @@ -255,7 +255,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get var txs = validTransactionsFromUtxoState(wus) val family_depth = 10 val limitedPoolSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(mempoolCapacity = (family_depth + 1) * txs.size)) @@ -296,7 +296,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "add removed transaction to mempool statistics" in { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get var txs = validTransactionsFromUtxoState(wus) var allTxs = txs val family_depth = 10 diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala index d2312324ec..724a94af03 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala @@ -76,6 +76,6 @@ class ScriptsSpec extends ErgoPropertyTest { assert(us.boxById(boxId).isDefined, s"Box ${Algos.encode(boxId)} missed") } val block = validFullBlock(None, us, tx, Some(1234L)) - us.applyModifier(block) + us.applyModifier(block)(_ => ()) } } diff --git a/src/test/scala/org/ergoplatform/nodeView/state/DigestStateSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/state/DigestStateSpecification.scala index 9b96120b6d..0ff799e9bc 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/DigestStateSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/DigestStateSpecification.scala @@ -24,7 +24,7 @@ class DigestStateSpecification extends ErgoPropertyTest { val fb = validFullBlock(parentOpt = None, us, bh) val dir2 = createTempDir val ds = DigestState.create(Some(us.version), Some(us.rootHash), dir2, stateConstants, parameters) - ds.applyModifier(fb) shouldBe 'success + ds.applyModifier(fb)(_ => ()) shouldBe 'success ds.close() val state = DigestState.create(None, None, dir2, stateConstants, parameters) @@ -42,8 +42,8 @@ class DigestStateSpecification extends ErgoPropertyTest { val blBh = validFullBlockWithBoxHolder(parentOpt, us, bh, new RandomWrapper(Some(seed))) val block = blBh._1 bh = blBh._2 - ds = ds.applyModifier(block).get - us = us.applyModifier(block).get + ds = ds.applyModifier(block)(_ => ()).get + us = us.applyModifier(block)(_ => ()).get parentOpt = Some(block) } } @@ -64,14 +64,14 @@ class DigestStateSpecification extends ErgoPropertyTest { block.blockTransactions.transactions.exists(_.dataInputs.nonEmpty) shouldBe true val ds = createDigestState(us.version, us.rootHash, parameters) - ds.applyModifier(block) shouldBe 'success + ds.applyModifier(block)(_ => ()) shouldBe 'success } } property("applyModifier() - invalid block") { forAll(invalidErgoFullBlockGen) { b => val state = createDigestState(emptyVersion, emptyAdDigest, parameters) - state.applyModifier(b).isFailure shouldBe true + state.applyModifier(b)(_ => ()).isFailure shouldBe true } } @@ -86,7 +86,7 @@ class DigestStateSpecification extends ErgoPropertyTest { ds.rollbackVersions.size shouldEqual 1 - val ds2 = ds.applyModifier(block).get + val ds2 = ds.applyModifier(block)(_ => ()).get ds2.rollbackVersions.size shouldEqual 2 @@ -99,7 +99,7 @@ class DigestStateSpecification extends ErgoPropertyTest { ds3.stateContext.lastHeaders.size shouldEqual 0 - ds3.applyModifier(block).get.rootHash shouldBe ds2.rootHash + ds3.applyModifier(block)(_ => ()).get.rootHash shouldBe ds2.rootHash } } diff --git a/src/test/scala/org/ergoplatform/nodeView/state/ErgoStateSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/state/ErgoStateSpecification.scala index 5a79bbe3a2..d2ee805dc8 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/ErgoStateSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/ErgoStateSpecification.scala @@ -32,11 +32,11 @@ class ErgoStateSpecification extends ErgoPropertyTest { val bt = BlockTransactions(dsHeader.id, version, dsTxs) val doubleSpendBlock = ErgoFullBlock(dsHeader, bt, validBlock.extension, validBlock.adProofs) - us.applyModifier(doubleSpendBlock) shouldBe 'failure - us.applyModifier(validBlock) shouldBe 'success + us.applyModifier(doubleSpendBlock)(_ => ()) shouldBe 'failure + us.applyModifier(validBlock)(_ => ()) shouldBe 'success - ds.applyModifier(doubleSpendBlock) shouldBe 'failure - ds.applyModifier(validBlock) shouldBe 'success + ds.applyModifier(doubleSpendBlock)(_ => ()) shouldBe 'failure + ds.applyModifier(validBlock)(_ => ()) shouldBe 'success } } @@ -59,8 +59,8 @@ class ErgoStateSpecification extends ErgoPropertyTest { val blBh = validFullBlockWithBoxHolder(lastBlocks.headOption, us, bh, new RandomWrapper(Some(seed))) val block = blBh._1 bh = blBh._2 - ds = ds.applyModifier(block).get - us = us.applyModifier(block).get + ds = ds.applyModifier(block)(_ => ()).get + us = us.applyModifier(block)(_ => ()).get lastBlocks = block +: lastBlocks requireEqualStateContexts(us.stateContext, ds.stateContext, lastBlocks.map(_.header)) } @@ -83,7 +83,7 @@ class ErgoStateSpecification extends ErgoPropertyTest { val block = blBh._1 parentOpt = Some(block) bh = blBh._2 - us = us.applyModifier(block).get + us = us.applyModifier(block)(_ => ()).get val changes1 = ErgoState.boxChanges(block.transactions) val changes2 = ErgoState.boxChanges(block.transactions) diff --git a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala index 0800bf91dd..2ca4cd0c07 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala @@ -36,7 +36,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera var (us, bh) = createUtxoState(parameters) var foundersBox = genesisBoxes.last var lastBlock = validFullBlock(parentOpt = None, us, bh) - us = us.applyModifier(lastBlock).get + us = us.applyModifier(lastBlock)(_ => ()).get // spent founders box, leaving the same proposition (0 until 10) foreach { _ => @@ -51,7 +51,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val txCostLimit = initSettings.nodeSettings.maxTransactionCost us.validateWithCost(tx, None, txCostLimit, None).get should be <= 100000L val block1 = validFullBlock(Some(lastBlock), us, Seq(ErgoTransaction(tx))) - us = us.applyModifier(block1).get + us = us.applyModifier(block1)(_ => ()).get foundersBox = tx.outputs.head lastBlock = block1 } @@ -81,7 +81,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val adProofs = ADProofs(realHeader.id, adProofBytes) val bt = BlockTransactions(realHeader.id, Header.InitialVersion, txs) val fb = ErgoFullBlock(realHeader, bt, genExtension(realHeader, us.stateContext), Some(adProofs)) - us = us.applyModifier(fb).get + us = us.applyModifier(fb)(_ => ()).get val remaining = emission.remainingFoundationRewardAtHeight(height) // check validity of transaction, spending founders box @@ -142,7 +142,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera us.extractEmissionBox(block) should not be None lastBlockOpt = Some(block) bh = blBh._2 - us = us.applyModifier(block).get + us = us.applyModifier(block)(_ => ()).get } } @@ -170,7 +170,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val adProofs = ADProofs(realHeader.id, adProofBytes) val bt = BlockTransactions(realHeader.id, 1: Byte, txs) val fb = ErgoFullBlock(realHeader, bt, genExtension(realHeader, us.stateContext), Some(adProofs)) - us = us.applyModifier(fb).get + us = us.applyModifier(fb)(_ => ()).get height = height + 1 } } @@ -197,7 +197,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera height = height + 1 val bt = BlockTransactions(realHeader.id, Header.InitialVersion, txs) val fb = ErgoFullBlock(realHeader, bt, genExtension(realHeader, us.stateContext), Some(adProofs)) - us = us.applyModifier(fb).get + us = us.applyModifier(fb)(_ => ()).get fb } // create new genesis state @@ -215,7 +215,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera } // apply chain of headers full block to state chain.foreach { fb => - us2 = us2.applyModifier(fb).get + us2 = us2.applyModifier(fb)(_ => ()).get } Await.result(f, Duration.Inf) } @@ -349,7 +349,6 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera IndexedSeq(), IndexedSeq(new ErgoBoxCandidate(boxToSpend.value, Constants.TrueLeaf, creationHeight = startHeight)) ) - val txs = txsFromHolder :+ spendingTx val us = createUtxoState(bh, parameters) @@ -363,6 +362,46 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera } } + property("applyTransactions() - no double-spend of an output created in a block is possible") { + forAll(boxesHolderGen) { bh => + val txsFromHolder = validTransactionsFromBoxHolder(bh)._1 + + val boxToSpend = txsFromHolder.last.outputs.head + + val spendingTxInput = Input(boxToSpend.id, emptyProverResult) + val spendingTx = ErgoTransaction( + IndexedSeq(spendingTxInput), + IndexedSeq(), + IndexedSeq(new ErgoBoxCandidate(boxToSpend.value, Constants.TrueLeaf, creationHeight = startHeight)) + ) + + val spending2Tx = ErgoTransaction( + IndexedSeq(Input(spendingTx.outputs.head.id, emptyProverResult)), + IndexedSeq(), + IndexedSeq(new ErgoBoxCandidate(boxToSpend.value, Constants.TrueLeaf, creationHeight = startHeight)) + ) + + val spending3Tx = ErgoTransaction( + IndexedSeq(Input(spending2Tx.outputs.head.id, emptyProverResult)), + IndexedSeq(), + IndexedSeq(new ErgoBoxCandidate(boxToSpend.value, Constants.TrueLeaf, creationHeight = startHeight)) + ) + + val spending4Tx = ErgoTransaction( + IndexedSeq(Input(spending2Tx.outputs.head.id, emptyProverResult)), + IndexedSeq(), + IndexedSeq(new ErgoBoxCandidate(boxToSpend.value, Constants.FalseLeaf, creationHeight = startHeight)) + ) + + val txs = txsFromHolder ++ Seq(spendingTx, spending2Tx, spending3Tx, spending4Tx) + + val us = createUtxoState(bh, parameters) + + // Fails on generating state root digest for the block + us.proofsForTransactions(txs).isSuccess shouldBe false + } + } + property("proofsForTransactions() fails if a transaction is spending an output created by a follow-up transaction") { forAll(boxesHolderGen) { bh => val txsFromHolder = validTransactionsFromBoxHolder(bh)._1 @@ -388,14 +427,14 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera bh.sortedBoxes.foreach(box => us.boxById(box.id) should not be None) val block = validFullBlock(parentOpt = None, us, bh) - us.applyModifier(block).get + us.applyModifier(block)(_ => ()).get } } property("applyModifier() - invalid block") { forAll(invalidErgoFullBlockGen) { b => val state = createUtxoState(parameters)._1 - state.applyModifier(b).isFailure shouldBe true + state.applyModifier(b)(_ => ()).isFailure shouldBe true } } @@ -413,37 +452,37 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera } val invalidBlock = validFullBlock(parentOpt = None, us2, bh2) - us.applyModifier(invalidBlock).isSuccess shouldBe false - us.applyModifier(validBlock).isSuccess shouldBe true + us.applyModifier(invalidBlock)(_ => ()).isSuccess shouldBe false + us.applyModifier(validBlock)(_ => ()).isSuccess shouldBe true } property("2 forks switching") { val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val chain1block1 = validFullBlock(Some(genesis), wusAfterGenesis) - val wusChain1Block1 = wusAfterGenesis.applyModifier(chain1block1).get + val wusChain1Block1 = wusAfterGenesis.applyModifier(chain1block1)(_ => ()).get val chain1block2 = validFullBlock(Some(chain1block1), wusChain1Block1) val (us2, bh2) = createUtxoState(parameters) - val wus2AfterGenesis = WrappedUtxoState(us2, bh2, stateConstants, parameters).applyModifier(genesis).get + val wus2AfterGenesis = WrappedUtxoState(us2, bh2, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val chain2block1 = validFullBlock(Some(genesis), wus2AfterGenesis) - val wusChain2Block1 = wus2AfterGenesis.applyModifier(chain2block1).get + val wusChain2Block1 = wus2AfterGenesis.applyModifier(chain2block1)(_ => ()).get val chain2block2 = validFullBlock(Some(chain2block1), wusChain2Block1) var (state, _) = createUtxoState(parameters) - state = state.applyModifier(genesis).get + state = state.applyModifier(genesis)(_ => ()).get - state = state.applyModifier(chain1block1).get + state = state.applyModifier(chain1block1)(_ => ()).get state = state.rollbackTo(idToVersion(genesis.id)).get - state = state.applyModifier(chain2block1).get - state = state.applyModifier(chain2block2).get + state = state.applyModifier(chain2block1)(_ => ()).get + state = state.applyModifier(chain2block2)(_ => ()).get state = state.rollbackTo(idToVersion(genesis.id)).get - state = state.applyModifier(chain1block1).get - state = state.applyModifier(chain1block2).get + state = state.applyModifier(chain1block1)(_ => ()).get + state = state.applyModifier(chain1block2)(_ => ()).get } @@ -453,14 +492,14 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val us = createUtxoState(bh, parameters) bh.sortedBoxes.foreach(box => us.boxById(box.id) should not be None) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get wusAfterGenesis.rootHash shouldEqual genesis.header.stateRoot val (finalState: WrappedUtxoState, chain: Seq[ErgoFullBlock]) = (0 until depth) .foldLeft((wusAfterGenesis, Seq(genesis))) { (sb, _) => val state = sb._1 val block = validFullBlock(parentOpt = Some(sb._2.last), state) - (state.applyModifier(block).get, sb._2 ++ Seq(block)) + (state.applyModifier(block)(_ => ()).get, sb._2 ++ Seq(block)) } val finalRoot = finalState.rootHash finalRoot shouldEqual chain.last.header.stateRoot @@ -469,7 +508,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera rollbackedState.rootHash shouldEqual genesis.header.stateRoot val finalState2: WrappedUtxoState = chain.tail.foldLeft(rollbackedState) { (state, block) => - state.applyModifier(block).get + state.applyModifier(block)(_ => ()).get } finalState2.rootHash shouldEqual finalRoot diff --git a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala index 11355e4c98..26d2949c0d 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala @@ -1,6 +1,7 @@ package org.ergoplatform.nodeView.state.wrapped import org.ergoplatform.modifiers.ErgoPersistentModifier +import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedModifier import org.ergoplatform.nodeView.state.DigestState import org.ergoplatform.settings.{ErgoSettings, Parameters} import scorex.core.VersionTag @@ -13,8 +14,9 @@ class WrappedDigestState(val digestState: DigestState, override val parameters: Parameters) extends DigestState(digestState.version, digestState.rootHash, digestState.store, parameters, settings) { - override def applyModifier(mod: ErgoPersistentModifier): Try[WrappedDigestState] = { - wrapped(super.applyModifier(mod), wrappedUtxoState.applyModifier(mod)) + override def applyModifier(mod: ErgoPersistentModifier) + (generate: LocallyGeneratedModifier => Unit): Try[WrappedDigestState] = { + wrapped(super.applyModifier(mod)(_ => ()), wrappedUtxoState.applyModifier(mod)(_ => ())) } override def rollbackTo(version: VersionTag): Try[WrappedDigestState] = { diff --git a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala index 548b28b613..1209ba979c 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala @@ -5,6 +5,7 @@ import java.io.File import akka.actor.ActorRef import org.ergoplatform.ErgoBox import org.ergoplatform.modifiers.ErgoPersistentModifier +import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedModifier import org.ergoplatform.nodeView.state._ import org.ergoplatform.settings.{ErgoSettings, Parameters} import org.ergoplatform.settings.Algos.HF @@ -34,24 +35,25 @@ class WrappedUtxoState(prover: PersistentBatchAVLProver[Digest32, HF], case Failure(e) => Failure(e) } - override def applyModifier(mod: ErgoPersistentModifier): Try[WrappedUtxoState] = super.applyModifier(mod) match { - case Success(us) => - mod match { - case ct: TransactionsCarryingPersistentNodeViewModifier => - // You can not get block with transactions not being of ErgoTransaction type so no type checks here. + override def applyModifier(mod: ErgoPersistentModifier)(generate: LocallyGeneratedModifier => Unit): Try[WrappedUtxoState] = + super.applyModifier(mod)(generate) match { + case Success(us) => + mod match { + case ct: TransactionsCarryingPersistentNodeViewModifier => + // You can not get block with transactions not being of ErgoTransaction type so no type checks here. - val changes = ErgoState.stateChanges(ct.transactions) - val updHolder = versionedBoxHolder.applyChanges( - us.version, - changes.toRemove.map(_.boxId).map(ByteArrayWrapper.apply), - changes.toAppend.map(_.box)) - Success(new WrappedUtxoState(us.persistentProver, idToVersion(mod.id), us.store, updHolder, constants, parameters)) - case _ => - val updHolder = versionedBoxHolder.applyChanges(us.version, Seq(), Seq()) - Success(new WrappedUtxoState(us.persistentProver, idToVersion(mod.id), us.store, updHolder, constants, parameters)) - } - case Failure(e) => Failure(e) - } + val changes = ErgoState.stateChanges(ct.transactions) + val updHolder = versionedBoxHolder.applyChanges( + us.version, + changes.toRemove.map(_.boxId).map(ByteArrayWrapper.apply), + changes.toAppend.map(_.box)) + Success(new WrappedUtxoState(us.persistentProver, idToVersion(mod.id), us.store, updHolder, constants, parameters)) + case _ => + val updHolder = versionedBoxHolder.applyChanges(us.version, Seq(), Seq()) + Success(new WrappedUtxoState(us.persistentProver, idToVersion(mod.id), us.store, updHolder, constants, parameters)) + } + case Failure(e) => Failure(e) + } } object WrappedUtxoState { @@ -61,7 +63,7 @@ object WrappedUtxoState { nodeViewHolderRef: Option[ActorRef], parameters: Parameters, settings: ErgoSettings): WrappedUtxoState = { - val constants = StateConstants(nodeViewHolderRef, settings) + val constants = StateConstants(settings) val emissionBox = ErgoState.genesisBoxes(constants.settings.chainSettings).headOption val us = UtxoState.fromBoxHolder(boxHolder, emissionBox, dir, constants, parameters) WrappedUtxoState(us, boxHolder, constants, parameters) diff --git a/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala b/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala index 5b2f859312..270b22e6f4 100644 --- a/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala @@ -20,8 +20,7 @@ import scorex.util.{ModifierId, bytesToId} class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers with NodeViewTestOps with NoShrink { private val t0 = TestCase("check chain is healthy") { fixture => - import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val block = validFullBlock(None, us, bh) val history = generateHistory(true, StateType.Utxo, false, 2) @@ -29,12 +28,12 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi // too big chain update delay val notAcceptableDelay = System.currentTimeMillis() - (initSettings.nodeSettings.acceptableChainUpdateDelay.toMillis + 100) val invalidProgress = ChainProgress(block, 2, 3, notAcceptableDelay) - ErgoNodeViewHolder.checkChainIsHealthy(invalidProgress, history, initSettings).isInstanceOf[ChainIsStuck] shouldBe true + ErgoNodeViewHolder.checkChainIsHealthy(invalidProgress, history, timeProvider, initSettings).isInstanceOf[ChainIsStuck] shouldBe true // acceptable chain update delay val acceptableDelay = System.currentTimeMillis() - 5 val validProgress = ChainProgress(block, 2, 3, acceptableDelay) - ErgoNodeViewHolder.checkChainIsHealthy(validProgress, history, initSettings) shouldBe ChainIsHealthy + ErgoNodeViewHolder.checkChainIsHealthy(validProgress, history, timeProvider, initSettings) shouldBe ChainIsHealthy } @@ -50,7 +49,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t3 = TestCase("apply valid block header") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val block = validFullBlock(None, us, bh) getBestHeaderOpt shouldBe None @@ -70,7 +69,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t4 = TestCase("apply valid block as genesis") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) subscribeEvents(classOf[SyntacticallySuccessfulModifier]) @@ -90,9 +89,12 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t5 = TestCase("apply full blocks after genesis") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = + WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis) { mod => + nodeViewHolderRef ! mod + }.get applyBlock(genesis) shouldBe 'success val block = validFullBlock(Some(genesis), wusAfterGenesis) @@ -109,7 +111,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t6 = TestCase("add transaction to memory pool") { fixture => import fixture._ if (stateType == Utxo) { - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) applyBlock(genesis) shouldBe 'success @@ -126,15 +128,18 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t7 = TestCase("apply statefully invalid full block") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = + WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis) { mod => + nodeViewHolderRef ! mod + }.get // TODO looks like another bug is still present here, see https://github.com/ergoplatform/ergo/issues/309 if (verifyTransactions) { applyBlock(genesis) shouldBe 'success val block = validFullBlock(Some(genesis), wusAfterGenesis) - val wusAfterBlock = wusAfterGenesis.applyModifier(block).get + val wusAfterBlock = wusAfterGenesis.applyModifier(block)(mod => nodeViewHolderRef ! mod).get applyBlock(block) shouldBe 'success getBestHeaderOpt shouldBe Some(block.header) @@ -178,9 +183,12 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t8 = TestCase("switching for a better chain") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = + WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis) { mod => + nodeViewHolderRef ! mod + }.get applyBlock(genesis) shouldBe 'success getRootHash shouldBe Algos.encode(wusAfterGenesis.rootHash) @@ -196,7 +204,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi getBestFullBlockOpt shouldBe expectedBestFullBlockOpt getBestHeaderOpt shouldBe Some(chain1block1.header) - val wusChain2Block1 = wusAfterGenesis.applyModifier(chain2block1).get + val wusChain2Block1 = wusAfterGenesis.applyModifier(chain2block1)(mod => nodeViewHolderRef ! mod).get val chain2block2 = validFullBlock(Some(chain2block1), wusChain2Block1) chain2block1.header.stateRoot shouldEqual wusChain2Block1.rootHash @@ -212,7 +220,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t9 = TestCase("UTXO state should generate adProofs and put them in history") { fixture => import fixture._ if (stateType == StateType.Utxo) { - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) nodeViewHolderRef ! LocallyGeneratedModifier(genesis.header) @@ -226,9 +234,12 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t10 = TestCase("NodeViewHolder start from inconsistent state") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = + WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis) { mod => + nodeViewHolderRef ! mod + }.get applyBlock(genesis) shouldBe 'success val block1 = validFullBlock(Some(genesis), wusAfterGenesis) @@ -246,15 +257,18 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t11 = TestCase("apply payload in incorrect order (excluding extension)") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(parentOpt = None, us, bh) - val wusAfterGenesis = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis).get + val wusAfterGenesis = + WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis) { mod => + nodeViewHolderRef ! mod + }.get applyBlock(genesis) shouldBe 'success getRootHash shouldBe Algos.encode(wusAfterGenesis.rootHash) val chain2block1 = validFullBlock(Some(genesis), wusAfterGenesis) - val wusChain2Block1 = wusAfterGenesis.applyModifier(chain2block1).get + val wusChain2Block1 = wusAfterGenesis.applyModifier(chain2block1)(mod => nodeViewHolderRef ! mod).get val chain2block2 = validFullBlock(Some(chain2block1), wusChain2Block1) subscribeEvents(classOf[SyntacticallySuccessfulModifier]) @@ -272,7 +286,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t12 = TestCase("Do not apply txs with wrong header id") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val block = validFullBlock(None, us, bh) getBestHeaderOpt shouldBe None getHistoryHeight shouldBe ErgoHistory.EmptyHistoryHeight @@ -324,7 +338,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t13 = TestCase("Do not apply wrong adProofs") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val block = validFullBlock(None, us, bh) getBestHeaderOpt shouldBe None @@ -355,7 +369,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi "it's not equal to genesisId from config") { fixture => import fixture._ updateConfig(genesisIdConfig(modifierIdGen.sample)) - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val block = validFullBlock(None, us, bh) getBestHeaderOpt shouldBe None @@ -373,7 +387,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t15 = TestCase("apply genesis block header if it's equal to genesisId from config") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val block = validFullBlock(None, us, bh) updateConfig(genesisIdConfig(Some(block.header.id))) @@ -392,7 +406,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t16 = TestCase("apply forks that include genesis block") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val wusGenesis = WrappedUtxoState(us, bh, stateConstants, parameters) @@ -407,7 +421,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi getBestFullBlockOpt shouldBe expectedBestFullBlockOpt getBestHeaderOpt shouldBe Some(chain1block1.header) - val wusChain2Block1 = wusGenesis.applyModifier(chain2block1).get + val wusChain2Block1 = wusGenesis.applyModifier(chain2block1)(mod => nodeViewHolderRef ! mod).get val chain2block2 = validFullBlock(Some(chain2block1), wusChain2Block1) chain2block1.header.stateRoot shouldEqual wusChain2Block1.rootHash @@ -422,7 +436,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t17 = TestCase("apply invalid genesis header") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val header = validFullBlock(None, us, bh).header.copy(parentId = bytesToId(Array.fill(32)(9: Byte))) getBestHeaderOpt shouldBe None @@ -440,7 +454,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t18 = TestCase("apply syntactically invalid genesis block") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val validBlock = validFullBlock(parentOpt = None, us, bh) val invalidBlock = validBlock.copy(header = validBlock.header.copy(parentId = bytesToId(Array.fill(32)(9: Byte)))) @@ -453,7 +467,7 @@ class ErgoNodeViewHolderSpec extends ErgoPropertyTest with HistoryTestHelpers wi private val t19 = TestCase("apply semantically invalid genesis block") { fixture => import fixture._ - val (us, bh) = createUtxoState(parameters, Some(nodeViewHolderRef)) + val (us, bh) = createUtxoState(parameters) val wusGenesis = WrappedUtxoState(us, bh, stateConstants, parameters) val invalidBlock = generateInvalidFullBlock(None, wusGenesis) diff --git a/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala b/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala index 55fcb8826a..7087481567 100644 --- a/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala @@ -1,5 +1,6 @@ package org.ergoplatform.nodeView.viewholder +import akka.actor.ActorRef import org.ergoplatform.mining.DefaultFakePowScheme import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.nodeView.state.wrapped.WrappedUtxoState @@ -35,11 +36,11 @@ class PrunedNodeViewHolderSpec extends ErgoPropertyTest with NodeViewTestOps wit ) } - def genFullChain(genesisState: WrappedUtxoState, howMany: Int): Seq[ErgoFullBlock] = { + def genFullChain(genesisState: WrappedUtxoState, howMany: Int, nodeViewHolderRef: ActorRef): Seq[ErgoFullBlock] = { (1 to howMany).foldLeft((Seq[ErgoFullBlock](), genesisState, None: Option[ErgoFullBlock])) { case ((chain, wus, parentOpt), h) => val time = System.currentTimeMillis() - (howMany - h) * (BlockInterval.toMillis * 20) val block = validFullBlock(parentOpt, wus, time) - val newState = wus.applyModifier(block).get + val newState = wus.applyModifier(block)(mod => nodeViewHolderRef ! mod).get (chain :+ block, newState, Some(block)) }._1 } @@ -50,7 +51,7 @@ class PrunedNodeViewHolderSpec extends ErgoPropertyTest with NodeViewTestOps wit val (us, bh) = createUtxoState(stateConstants, parameters) val wus = WrappedUtxoState(us, bh, stateConstants, parameters) - val fullChain = genFullChain(wus, totalBlocks) + val fullChain = genFullChain(wus, totalBlocks, nodeViewHolderRef) fullChain.foreach { block => applyHeader(block.header).isSuccess shouldBe true diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala index ae22d611c9..08aa7bfe73 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala @@ -5,7 +5,7 @@ import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransact import org.ergoplatform.nodeView.state.{ErgoStateContext, VotingData} import org.ergoplatform.nodeView.wallet.IdUtils._ import org.ergoplatform.nodeView.wallet.persistence.{WalletDigest, WalletDigestSerializer} -import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, ExternalSecret, PaymentRequest} +import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, BurnTokensRequest, ExternalSecret, PaymentRequest} import org.ergoplatform.settings.{Algos, Constants} import org.ergoplatform.utils._ import org.ergoplatform.wallet.interpreter.{ErgoInterpreter, TransactionHintsBag} @@ -160,6 +160,84 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually } } + property("Generate transaction with BurnTokensRequest") { + withFixture { implicit w => + val pubKey = getPublicKeys.head.pubkey + val genesisBlock = makeGenesisBlock(pubKey, randomNewAsset) + val initialBoxes = boxesAvailable(genesisBlock, pubKey) + + val boxesToUseEncoded = initialBoxes.map { box => + Base16.encode(ErgoBoxSerializer.toBytes(box)) + } + + applyBlock(genesisBlock) shouldBe 'success + implicit val patienceConfig: PatienceConfig = PatienceConfig(5.second, 300.millis) + eventually { + val confirmedBalance = getConfirmedBalances.walletBalance + + //pay out all the wallet balance: + val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq + assetToSpend should not be empty + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance, assetToSpend, Map.empty) + + val tx1 = await(wallet.generateTransaction(Seq(req1), boxesToUseEncoded)).get + tx1.outputs.size shouldBe 1 + tx1.outputs.head.value shouldBe confirmedBalance + toAssetMap(tx1.outputs.head.additionalTokens.toArray) shouldBe toAssetMap(assetToSpend) + + //change == 1: + val assetToSpend2 = assetToSpend.map { case (tokenId, tokenValue) => (tokenId, tokenValue - 1) } + val assetToReturn = assetToSpend.map { case (tokenId, _) => (tokenId, 1L) } + val req2 = Seq(BurnTokensRequest(assetToSpend2)) + + val tx2 = await(wallet.generateTransaction(req2)).get + tx2.outputs.size shouldBe 1 + tx2.outputs.head.value shouldBe confirmedBalance + toAssetMap(tx2.outputs.head.additionalTokens.toArray) shouldBe toAssetMap(assetToReturn) + } + } + } + + property("Generate transaction with PaymentRequest (no tokens) and BurnTokensRequest") { + withFixture { implicit w => + val pubKey = getPublicKeys.head.pubkey + val genesisBlock = makeGenesisBlock(pubKey, randomNewAsset) + val initialBoxes = boxesAvailable(genesisBlock, pubKey) + + val boxesToUseEncoded = initialBoxes.map { box => + Base16.encode(ErgoBoxSerializer.toBytes(box)) + } + + applyBlock(genesisBlock) shouldBe 'success + implicit val patienceConfig: PatienceConfig = PatienceConfig(5.second, 300.millis) + eventually { + val confirmedBalance = getConfirmedBalances.walletBalance + + //pay out all the wallet balance: + val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq + assetToSpend should not be empty + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance, assetToSpend, Map.empty) + + val tx1 = await(wallet.generateTransaction(Seq(req1), boxesToUseEncoded)).get + tx1.outputs.size shouldBe 1 + tx1.outputs.head.value shouldBe confirmedBalance + toAssetMap(tx1.outputs.head.additionalTokens.toArray) shouldBe toAssetMap(assetToSpend) + + //change == 1: + val assetToSpend2 = assetToSpend.map { case (tokenId, tokenValue) => (tokenId, tokenValue - 1) } + val assetToReturn = assetToSpend.map { case (tokenId, _) => (tokenId, 1L) } + val req2 = Seq(BurnTokensRequest(assetToSpend2), PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance - MinBoxValue, Seq.empty, Map.empty)) + + val tx2 = await(wallet.generateTransaction(req2)).get + tx2.outputs.size shouldBe 2 + tx2.outputs.head.value shouldBe confirmedBalance - MinBoxValue + toAssetMap(tx2.outputs.head.additionalTokens.toArray) shouldBe toAssetMap(Seq.empty) + tx2.outputs(1).value shouldBe MinBoxValue + toAssetMap(tx2.outputs(1).additionalTokens.toArray) shouldBe toAssetMap(assetToReturn) + } + } + } + property("Generate transaction with multiple inputs") { withFixture { implicit w => val addresses = getPublicKeys diff --git a/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala b/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala index 124d61adb7..cd0f542861 100644 --- a/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala +++ b/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala @@ -71,6 +71,22 @@ class JsonSerializationSpec extends ErgoPropertyTest with WalletGenerators with } } + property("BurnTokensRequest should be serialized to json") { + implicit val requestEncoder: Encoder[BurnTokensRequest] = new BurnTokensRequestEncoder() + implicit val requestDecoder: Decoder[BurnTokensRequest] = new BurnTokensRequestDecoder() + forAll(burnTokensRequestGen) { request => + val json = request.asJson + val parsingResult = json.as[BurnTokensRequest] + parsingResult.isRight shouldBe true + val restored = parsingResult.value + Inspectors.forAll(restored.assetsToBurn.zip(request.assetsToBurn)) { + case ((restoredToken, restoredValue), (requestToken, requestValue)) => + restoredToken shouldEqual requestToken + restoredValue shouldEqual requestValue + } + } + } + property("AssetIssueRequest should be serialized to json") { val ergoSettings = ErgoSettings.read() implicit val requestEncoder: Encoder[AssetIssueRequest] = new AssetIssueRequestEncoder(ergoSettings) diff --git a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala index 5ec3400024..e5185e5cbb 100644 --- a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala +++ b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala @@ -30,7 +30,7 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { miningPubKeyHex = None, offlineGeneration = false, keepVersions = 200, - acceptableChainUpdateDelay = 15.minutes, + acceptableChainUpdateDelay = 30.minutes, mempoolCapacity = 100000, mempoolCleanupDuration = 10.seconds, rebroadcastCount = 3, @@ -72,7 +72,7 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { miningPubKeyHex = None, offlineGeneration = false, keepVersions = 200, - acceptableChainUpdateDelay = 15.minutes, + acceptableChainUpdateDelay = 30.minutes, mempoolCapacity = 100000, mempoolCleanupDuration = 10.seconds, rebroadcastCount = 3, @@ -114,7 +114,7 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { miningPubKeyHex = None, offlineGeneration = false, keepVersions = 200, - acceptableChainUpdateDelay = 15.minutes, + acceptableChainUpdateDelay = 30.minutes, mempoolCapacity = 100000, mempoolCleanupDuration = 10.seconds, rebroadcastCount = 3, diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index b234909bc9..126c1c7749 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -75,7 +75,7 @@ object ChainGenerator extends App with ErgoTestHelpers { val history = ErgoHistory.readOrGenerate(fullHistorySettings, timeProvider) HistoryTestHelpers.allowToApplyOldBlocks(history) - val (state, _) = ErgoState.generateGenesisUtxoState(stateDir, StateConstants(None, fullHistorySettings), parameters) + val (state, _) = ErgoState.generateGenesisUtxoState(stateDir, StateConstants(fullHistorySettings), parameters) log.info(s"Going to generate a chain at ${dir.getAbsoluteFile} starting from ${history.bestFullBlockOpt}") val chain = loop(state, None, None, Seq()) @@ -111,7 +111,7 @@ object ChainGenerator extends App with ErgoTestHelpers { log.info( s"Block ${block.id} with ${block.transactions.size} transactions at height ${block.header.height} generated") - loop(state.applyModifier(block).get, outToPassNext, Some(block.header), acc :+ block.id) + loop(state.applyModifier(block)(_ => ()).get, outToPassNext, Some(block.header), acc :+ block.id) } else { acc } diff --git a/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala b/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala index c99da156e5..8a83016c9e 100644 --- a/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala +++ b/src/test/scala/org/ergoplatform/utils/ErgoTestConstants.scala @@ -64,7 +64,7 @@ trait ErgoTestConstants extends ScorexLogging { val emission: EmissionRules = settings.chainSettings.emissionRules val coinsTotal: Long = emission.coinsTotal - val stateConstants: StateConstants = StateConstants(None, settings) + val stateConstants: StateConstants = StateConstants(settings) val genesisStateDigest: ADDigest = settings.chainSettings.genesisStateDigest val feeProp: ErgoTree = ErgoScriptPredef.feeProposition(emission.settings.minerRewardDelay) diff --git a/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala index db942ddf0a..3e6d639bd1 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala @@ -1,6 +1,5 @@ package org.ergoplatform.utils.generators -import akka.actor.ActorRef import org.ergoplatform.ErgoBox import org.ergoplatform.mining.CandidateGenerator import org.ergoplatform.modifiers.ErgoFullBlock @@ -26,9 +25,8 @@ import scala.util.{Failure, Random, Success} trait ValidBlocksGenerators extends TestkitHelpers with TestFileUtils with Matchers with ChainGenerator with ErgoTransactionGenerators { - def createUtxoState(parameters: Parameters, nodeViewHolderRef: Option[ActorRef] = None): (UtxoState, BoxHolder) = { - val constants = StateConstants(nodeViewHolderRef, settings) - createUtxoState(constants, parameters) + def createUtxoState(parameters: Parameters): (UtxoState, BoxHolder) = { + createUtxoState(StateConstants(settings), parameters) } def createUtxoState(constants: StateConstants, parameters: Parameters): (UtxoState, BoxHolder) = { diff --git a/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala index aa2fc0b2d2..5ec1522c19 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala @@ -4,7 +4,7 @@ import org.ergoplatform._ import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.wallet.IdUtils._ import org.ergoplatform.nodeView.wallet.persistence.WalletDigest -import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, PaymentRequest} +import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, BurnTokensRequest, PaymentRequest} import org.ergoplatform.nodeView.wallet.scanning._ import org.ergoplatform.settings.Constants import org.ergoplatform.wallet.Constants.ScanId @@ -86,6 +86,12 @@ trait WalletGenerators extends ErgoTransactionGenerators with Generators { } yield PaymentRequest(Pay2SAddress(Constants.FalseLeaf), value, assets, registers) } + def burnTokensRequestGen: Gen[BurnTokensRequest] = { + for { + assets <- additionalTokensGen + } yield BurnTokensRequest(assets) + } + def assetIssueRequestGen: Gen[AssetIssueRequest] = { for { amount <- Gen.choose(1L, 100000L) diff --git a/src/test/scala/scorex/testkit/properties/state/StateApplicationTest.scala b/src/test/scala/scorex/testkit/properties/state/StateApplicationTest.scala index 4a59394421..dc67dd387c 100644 --- a/src/test/scala/scorex/testkit/properties/state/StateApplicationTest.scala +++ b/src/test/scala/scorex/testkit/properties/state/StateApplicationTest.scala @@ -21,7 +21,7 @@ trait StateApplicationTest[ST <: ErgoState[ST]] extends StateTests[ST] { property(propertyNameGenerator("apply modifier")) { forAll(stateGenWithValidModifier) { case (s, m) => val ver = s.version - val sTry = s.applyModifier(m) + val sTry = s.applyModifier(m)(_ => ()) sTry.isSuccess shouldBe true sTry.get.version == ver shouldBe false } @@ -30,17 +30,17 @@ trait StateApplicationTest[ST <: ErgoState[ST]] extends StateTests[ST] { property(propertyNameGenerator("do not apply same valid modifier twice")) { forAll(stateGenWithValidModifier) { case (s, m) => val ver = s.version - val sTry = s.applyModifier(m) + val sTry = s.applyModifier(m)(_ => ()) sTry.isSuccess shouldBe true val s2 = sTry.get s2.version == ver shouldBe false - s2.applyModifier(m).isSuccess shouldBe false + s2.applyModifier(m)(_ => ()).isSuccess shouldBe false } } property(propertyNameGenerator("do not apply invalid modifier")) { forAll(stateGenWithInvalidModifier) { case (s, m) => - val sTry = s.applyModifier(m) + val sTry = s.applyModifier(m)(_ => ()) sTry.isSuccess shouldBe false } } @@ -48,7 +48,7 @@ trait StateApplicationTest[ST <: ErgoState[ST]] extends StateTests[ST] { property(propertyNameGenerator("apply valid modifier after rollback")) { forAll(stateGenWithValidModifier) { case (s, m) => val ver = s.version - val sTry = s.applyModifier(m) + val sTry = s.applyModifier(m)(_ => ()) sTry.isSuccess shouldBe true val s2 = sTry.get s2.version == ver shouldBe false @@ -57,7 +57,7 @@ trait StateApplicationTest[ST <: ErgoState[ST]] extends StateTests[ST] { val s3 = s2.rollbackTo(ver).get s3.version == ver shouldBe true - val sTry2 = s3.applyModifier(m) + val sTry2 = s3.applyModifier(m)(_ => ()) sTry2.isSuccess shouldBe true val s4 = sTry2.get s4.version == ver shouldBe false @@ -82,7 +82,7 @@ trait StateApplicationTest[ST <: ErgoState[ST]] extends StateTests[ST] { val s2 = (0 until rollbackDepth).foldLeft(s) { case (state, _) => val modifier = semanticallyValidModifier(state) buf += modifier - val sTry = state.applyModifier(modifier) + val sTry = state.applyModifier(modifier)(_ => ()) sTry shouldBe 'success sTry.get } @@ -94,7 +94,7 @@ trait StateApplicationTest[ST <: ErgoState[ST]] extends StateTests[ST] { s3.version == ver shouldBe true val s4 = buf.foldLeft(s3) { case (state, m) => - val sTry = state.applyModifier(m) + val sTry = state.applyModifier(m)(_ => ()) sTry shouldBe 'success sTry.get }