├── .env-template ├── .gitignore ├── README.md ├── assets ├── chain-alert.png └── web3modal.png ├── contracts └── Calc.sol ├── frontend ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.js │ │ └── dashboard.css │ ├── favicon.ico │ └── index.html └── src │ ├── App.vue │ ├── assets │ └── logo.png │ ├── components │ ├── Navbar.vue │ └── Sidebar.vue │ ├── contracts │ └── .gitkeep │ ├── main.js │ ├── pages │ ├── Home.vue │ ├── Profile.vue │ └── SetValue.vue │ ├── router.js │ └── store │ ├── index.js │ └── modules │ ├── accounts.js │ └── contracts.js ├── hardhat.config.js ├── package-lock.json ├── package.json ├── scripts └── deploy.js └── test └── Calc.test.js /.env-template: -------------------------------------------------------------------------------- 1 | ALCHEMY_API_KEY="" 2 | ALCHEMY_DEPLOYMENT_KEY="" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | /frontend/src/contracts/* 4 | 5 | #Hardhat files 6 | /cache 7 | /artifacts 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hardhat Vue.js Starter Template (with web3js) 2 | 3 | A starter template for Ethereum dApps that uses the following tools: 4 | 5 | - Hardhat 6 | - Truffle testing suite 7 | - Web3js 8 | - Vue & Vuex 9 | - Web3Modal 10 | - Bootstrap 5 11 | - Vue Toasted 12 | - Vue Gravatar 13 | 14 | ## Features 15 | 16 | ### Seamless connect/disconnect wallet experience (using Web3Modal) 17 | 18 | Web3Modal is used to support various different Ethereum wallets. When user switches between accounts and even chains, the UI quickly notices that and adapts to the change (account and ETH balance data are refreshed). 19 | 20 | ![](assets/web3modal.png) 21 | 22 | ### Alert when not on mainnet 23 | 24 | If user's wallet is not set to Mainnet, an unobtrusive yellow alert band shows up just above the navigation bar. The alert notifies the user which (testnet) chain they are currently using. 25 | 26 | ![](assets/chain-alert.png) 27 | 28 | ### Storing contract addresses and ABIs on front-end 29 | 30 | The deploy.js script automatically stores all contract ABIs and their respective addresses in the /frontend/src/contracts folder. 31 | 32 | Addresses are separated from one another per contract name and also per chain ID. 33 | 34 | Example (`addresses.json`): 35 | 36 | ```json 37 | { 38 | "Token":{ 39 | "1337":"0x78afecb367f032d93eDf865Ada339AFf6ef2621b", 40 | "3":"0x5FbDB2315678afecb367f032d93F642f64180aa3", 41 | "1":"0xE2Df865998BD3f20117e037d1293367f032d93F6" 42 | }, 43 | "Farm":{ 44 | "1337":"0x1Cf865998BD3f20eB6BCdAda339aa8BD3f2e26eb", 45 | "3":"0x998BD3f20eB6Bafecb3673f201ca17e037d10aa3", 46 | "1":"0xBCdAda33b67815678afecb365998BD3f2e26BCdA" 47 | } 48 | } 49 | ``` 50 | 51 | ## npm install 52 | 53 | Run installations in both root and in the frontend folder: 54 | 55 | ```bash 56 | npm install 57 | cd frontend && npm install 58 | ``` 59 | 60 | ## Run Vue app 61 | 62 | ```bash 63 | cd frontend && npm run serve 64 | ``` 65 | 66 | ## Tests 67 | 68 | ### Solidity/Hardhat 69 | 70 | ```bash 71 | npx hardhat test 72 | ``` 73 | 74 | ## Deployment to ganache 75 | 76 | ```bash 77 | npx hardhat run scripts/deploy.js --network ganache 78 | ``` 79 | 80 | ## Deployment to a remote blockchain 81 | 82 | ```bash 83 | npx hardhat run scripts/deploy.js --network goerli 84 | ``` 85 | 86 | ## Verify on Etherscan 87 | 88 | ```bash 89 | npx hardhat --network mainnet etherscan-verify --api-key 90 | ``` 91 | -------------------------------------------------------------------------------- /assets/chain-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remote-gildor/hardhat-web3-vue-starter/39ac171c0f42cf1a3d3ef61084da852a0ee28d11/assets/chain-alert.png -------------------------------------------------------------------------------- /assets/web3modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remote-gildor/hardhat-web3-vue-starter/39ac171c0f42cf1a3d3ef61084da852a0ee28d11/assets/web3modal.png -------------------------------------------------------------------------------- /contracts/Calc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity =0.7.3; 3 | 4 | contract Calc { 5 | uint private num = 0; 6 | 7 | event NumberSet(address _from, uint value); 8 | 9 | function getNum() public view returns(uint) { 10 | return num; 11 | } 12 | 13 | function setNum(uint _num) public { 14 | num = _num; 15 | 16 | emit NumberSet(msg.sender, _num); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@burner-wallet/burner-connect-provider": "^0.1.1", 12 | "authereum": "^0.1.0", 13 | "core-js": "^3.6.5", 14 | "vue": "^2.6.11", 15 | "vue-gravatar": "^1.3.1", 16 | "vue-router": "^3.4.9", 17 | "vue-toasted": "^1.1.28", 18 | "vuex": "^3.6.0", 19 | "web3": "^1.3.1", 20 | "web3modal": "^1.9.2" 21 | }, 22 | "devDependencies": { 23 | "@vue/cli-plugin-babel": "~4.5.0", 24 | "@vue/cli-plugin-eslint": "~4.5.0", 25 | "@vue/cli-service": "~4.5.0", 26 | "babel-eslint": "^10.1.0", 27 | "eslint": "^6.7.2", 28 | "eslint-plugin-vue": "^6.2.2", 29 | "vue-template-compiler": "^2.6.11" 30 | }, 31 | "eslintConfig": { 32 | "root": true, 33 | "env": { 34 | "node": true 35 | }, 36 | "extends": [ 37 | "plugin:vue/essential", 38 | "eslint:recommended" 39 | ], 40 | "parserOptions": { 41 | "parser": "babel-eslint" 42 | }, 43 | "rules": {} 44 | }, 45 | "browserslist": [ 46 | "> 1%", 47 | "last 2 versions", 48 | "not dead" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /frontend/public/bootstrap/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v5.0.0-beta1 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach((function(n){if("default"!==n){var i=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,i.get?i:{enumerable:!0,get:function(){return t[n]}})}})),e.default=t,Object.freeze(e)}var n=e(t);function i(t,e){for(var n=0;n0,i._pointerEvent=Boolean(window.PointerEvent),i._addEventListeners(),i}r(e,t);var n=e.prototype;return n.next=function(){this._isSliding||this._slide("next")},n.nextWhenVisible=function(){!document.hidden&&v(this._element)&&this.next()},n.prev=function(){this._isSliding||this._slide("prev")},n.pause=function(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(p(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},n.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},n.to=function(t){var e=this;this._activeElement=V.findOne(".active.carousel-item",this._element);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)Q.one(this._element,"slid.bs.carousel",(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?"next":"prev";this._slide(i,this._items[t])}},n.dispose=function(){t.prototype.dispose.call(this),Q.off(this._element,G),this._items=null,this._config=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},n._getConfig=function(t){return t=s({},Z,t),_($,t,J),t},n._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},n._addEventListeners=function(){var t=this;this._config.keyboard&&Q.on(this._element,"keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&(Q.on(this._element,"mouseenter.bs.carousel",(function(e){return t.pause(e)})),Q.on(this._element,"mouseleave.bs.carousel",(function(e){return t.cycle(e)}))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()},n._addTouchEventListeners=function(){var t=this,e=function(e){t._pointerEvent&&tt[e.pointerType.toUpperCase()]?t.touchStartX=e.clientX:t._pointerEvent||(t.touchStartX=e.touches[0].clientX)},n=function(e){t._pointerEvent&&tt[e.pointerType.toUpperCase()]&&(t.touchDeltaX=e.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};V.find(".carousel-item img",this._element).forEach((function(t){Q.on(t,"dragstart.bs.carousel",(function(t){return t.preventDefault()}))})),this._pointerEvent?(Q.on(this._element,"pointerdown.bs.carousel",(function(t){return e(t)})),Q.on(this._element,"pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(Q.on(this._element,"touchstart.bs.carousel",(function(t){return e(t)})),Q.on(this._element,"touchmove.bs.carousel",(function(e){return function(e){e.touches&&e.touches.length>1?t.touchDeltaX=0:t.touchDeltaX=e.touches[0].clientX-t.touchStartX}(e)})),Q.on(this._element,"touchend.bs.carousel",(function(t){return n(t)})))},n._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.key){case"ArrowLeft":t.preventDefault(),this.prev();break;case"ArrowRight":t.preventDefault(),this.next()}},n._getItemIndex=function(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)},n._getItemByDirection=function(t,e){var n="next"===t,i="prev"===t,o=this._getItemIndex(e),s=this._items.length-1;if((i&&0===o||n&&o===s)&&!this._config.wrap)return e;var r=(o+("prev"===t?-1:1))%this._items.length;return-1===r?this._items[this._items.length-1]:this._items[r]},n._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(V.findOne(".active.carousel-item",this._element));return Q.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n})},n._setActiveIndicatorElement=function(t){if(this._indicatorsElement){for(var e=V.find(".active",this._indicatorsElement),n=0;n0)for(var i=0;i0&&s--,"ArrowDown"===t.key&&sdocument.documentElement.clientHeight;e||(this._element.style.overflowY="hidden"),this._element.classList.add("modal-static");var n=h(this._dialog);Q.off(this._element,"transitionend"),Q.one(this._element,"transitionend",(function(){t._element.classList.remove("modal-static"),e||(Q.one(t._element,"transitionend",(function(){t._element.style.overflowY=""})),m(t._element,n))})),m(this._element,n),this._element.focus()}},n._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;(!this._isBodyOverflowing&&t&&!T||this._isBodyOverflowing&&!t&&T)&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),(this._isBodyOverflowing&&!t&&!T||!this._isBodyOverflowing&&t&&T)&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},n._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},n._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",container:!1,fallbackPlacements:null,boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:Tt,popperConfig:null},Ot={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},It=function(e){function i(t,i){var o;if(void 0===n)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");return(o=e.call(this,t)||this)._isEnabled=!0,o._timeout=0,o._hoverState="",o._activeTrigger={},o._popper=null,o.config=o._getConfig(i),o.tip=null,o._setListeners(),o}r(i,e);var a=i.prototype;return a.enable=function(){this._isEnabled=!0},a.disable=function(){this._isEnabled=!1},a.toggleEnabled=function(){this._isEnabled=!this._isEnabled},a.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=L(t.delegateTarget,e);n||(n=new this.constructor(t.delegateTarget,this._getDelegateConfig()),A(t.delegateTarget,e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}},a.dispose=function(){clearTimeout(this._timeout),Q.off(this._element,this.constructor.EVENT_KEY),Q.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.parentNode.removeChild(this.tip),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.config=null,this.tip=null,e.prototype.dispose.call(this)},a.show=function(){var e=this;if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(this.isWithContent()&&this._isEnabled){var n=Q.trigger(this._element,this.constructor.Event.SHOW),i=function t(e){if(!document.documentElement.attachShadow)return null;if("function"==typeof e.getRootNode){var n=e.getRootNode();return n instanceof ShadowRoot?n:null}return e instanceof ShadowRoot?e:e.parentNode?t(e.parentNode):null}(this._element),o=null===i?this._element.ownerDocument.documentElement.contains(this._element):i.contains(this._element);if(n.defaultPrevented||!o)return;var s=this.getTipElement(),r=c(this.constructor.NAME);s.setAttribute("id",r),this._element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&s.classList.add("fade");var a="function"==typeof this.config.placement?this.config.placement.call(this,s,this._element):this.config.placement,l=this._getAttachment(a);this._addAttachmentClass(l);var u=this._getContainer();A(s,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||u.appendChild(s),Q.trigger(this._element,this.constructor.Event.INSERTED),this._popper=t.createPopper(this._element,s,this._getPopperConfig(l)),s.classList.add("show");var d,f,p="function"==typeof this.config.customClass?this.config.customClass():this.config.customClass;if(p)(d=s.classList).add.apply(d,p.split(" "));if("ontouchstart"in document.documentElement)(f=[]).concat.apply(f,document.body.children).forEach((function(t){Q.on(t,"mouseover",(function(){}))}));var g=function(){var t=e._hoverState;e._hoverState=null,Q.trigger(e._element,e.constructor.Event.SHOWN),"out"===t&&e._leave(null,e)};if(this.tip.classList.contains("fade")){var _=h(this.tip);Q.one(this.tip,"transitionend",g),m(this.tip,_)}else g()}},a.hide=function(){var t=this;if(this._popper){var e=this.getTipElement(),n=function(){"show"!==t._hoverState&&e.parentNode&&e.parentNode.removeChild(e),t._cleanTipClass(),t._element.removeAttribute("aria-describedby"),Q.trigger(t._element,t.constructor.Event.HIDDEN),t._popper&&(t._popper.destroy(),t._popper=null)};if(!Q.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented){var i;if(e.classList.remove("show"),"ontouchstart"in document.documentElement)(i=[]).concat.apply(i,document.body.children).forEach((function(t){return Q.off(t,"mouseover",b)}));if(this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this.tip.classList.contains("fade")){var o=h(e);Q.one(e,"transitionend",n),m(e,o)}else n();this._hoverState=""}}},a.update=function(){null!==this._popper&&this._popper.update()},a.isWithContent=function(){return Boolean(this.getTitle())},a.getTipElement=function(){if(this.tip)return this.tip;var t=document.createElement("div");return t.innerHTML=this.config.template,this.tip=t.children[0],this.tip},a.setContent=function(){var t=this.getTipElement();this.setElementContent(V.findOne(".tooltip-inner",t),this.getTitle()),t.classList.remove("fade","show")},a.setElementContent=function(t,e){if(null!==t)return"object"==typeof e&&g(e)?(e.jquery&&(e=e[0]),void(this.config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this.config.html?(this.config.sanitize&&(e=kt(e,this.config.allowList,this.config.sanitizeFn)),t.innerHTML=e):t.textContent=e)},a.getTitle=function(){var t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this._element):this.config.title),t},a.updateAttachment=function(t){return"right"===t?"end":"left"===t?"start":t},a._getPopperConfig=function(t){var e=this,n={name:"flip",options:{altBoundary:!0}};return this.config.fallbackPlacements&&(n.options.fallbackPlacements=this.config.fallbackPlacements),s({},{placement:t,modifiers:[n,{name:"preventOverflow",options:{rootBoundary:this.config.boundary}},{name:"arrow",options:{element:"."+this.constructor.NAME+"-arrow"}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:function(t){return e._handlePopperPlacementChange(t)}}],onFirstUpdate:function(t){t.options.placement!==t.placement&&e._handlePopperPlacementChange(t)}},this.config.popperConfig)},a._addAttachmentClass=function(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))},a._getContainer=function(){return!1===this.config.container?document.body:g(this.config.container)?this.config.container:V.findOne(this.config.container)},a._getAttachment=function(t){return St[t.toUpperCase()]},a._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)Q.on(t._element,t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n="hover"===e?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i="hover"===e?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;Q.on(t._element,n,t.config.selector,(function(e){return t._enter(e)})),Q.on(t._element,i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t._element&&t.hide()},Q.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=s({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},a._fixTitle=function(){var t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))},a._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||L(t.delegateTarget,n))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),A(t.delegateTarget,n,e)),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){"show"===e._hoverState&&e.show()}),e.config.delay.show):e.show())},a._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||L(t.delegateTarget,n))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),A(t.delegateTarget,n,e)),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){"out"===e._hoverState&&e.hide()}),e.config.delay.hide):e.hide())},a._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},a._getConfig=function(t){var e=q.getDataAttributes(this._element);return Object.keys(e).forEach((function(t){Ct.has(t)&&delete e[t]})),t&&"object"==typeof t.container&&t.container.jquery&&(t.container=t.container[0]),"number"==typeof(t=s({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_(At,t,this.constructor.DefaultType),t.sanitize&&(t.template=kt(t.template,t.allowList,t.sanitizeFn)),t},a._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},a._cleanTipClass=function(){var t=this.getTipElement(),e=t.getAttribute("class").match(Lt);null!==e&&e.length>0&&e.map((function(t){return t.trim()})).forEach((function(e){return t.classList.remove(e)}))},a._handlePopperPlacementChange=function(t){var e=t.state;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))},i.jQueryInterface=function(t){return this.each((function(){var e=L(this,"bs.tooltip"),n="object"==typeof t&&t;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n)),"string"==typeof t)){if(void 0===e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},o(i,null,[{key:"Default",get:function(){return Nt}},{key:"NAME",get:function(){return At}},{key:"DATA_KEY",get:function(){return"bs.tooltip"}},{key:"Event",get:function(){return Ot}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Dt}}]),i}(U);E((function(){var t=w();if(t){var e=t.fn[At];t.fn[At]=It.jQueryInterface,t.fn[At].Constructor=It,t.fn[At].noConflict=function(){return t.fn[At]=e,It.jQueryInterface}}}));var jt="popover",Pt=new RegExp("(^|\\s)bs-popover\\S+","g"),xt=s({},It.Default,{placement:"right",trigger:"click",content:"",template:''}),Ht=s({},It.DefaultType,{content:"(string|element|function)"}),Bt={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Mt=function(t){function e(){return t.apply(this,arguments)||this}r(e,t);var n=e.prototype;return n.isWithContent=function(){return this.getTitle()||this._getContent()},n.setContent=function(){var t=this.getTipElement();this.setElementContent(V.findOne(".popover-header",t),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this._element)),this.setElementContent(V.findOne(".popover-body",t),e),t.classList.remove("fade","show")},n._addAttachmentClass=function(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))},n._getContent=function(){return this._element.getAttribute("data-bs-content")||this.config.content},n._cleanTipClass=function(){var t=this.getTipElement(),e=t.getAttribute("class").match(Pt);null!==e&&e.length>0&&e.map((function(t){return t.trim()})).forEach((function(e){return t.classList.remove(e)}))},e.jQueryInterface=function(t){return this.each((function(){var n=L(this,"bs.popover"),i="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new e(this,i),A(this,"bs.popover",n)),"string"==typeof t)){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},o(e,null,[{key:"Default",get:function(){return xt}},{key:"NAME",get:function(){return jt}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return Bt}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Ht}}]),e}(It);E((function(){var t=w();if(t){var e=t.fn[jt];t.fn[jt]=Mt.jQueryInterface,t.fn[jt].Constructor=Mt,t.fn[jt].noConflict=function(){return t.fn[jt]=e,Mt.jQueryInterface}}}));var Rt="scrollspy",Kt={offset:10,method:"auto",target:""},Qt={offset:"number",method:"string",target:"(string|element)"},Ut=function(t){function e(e,n){var i;return(i=t.call(this,e)||this)._scrollElement="BODY"===e.tagName?window:e,i._config=i._getConfig(n),i._selector=i._config.target+" .nav-link, "+i._config.target+" .list-group-item, "+i._config.target+" .dropdown-item",i._offsets=[],i._targets=[],i._activeTarget=null,i._scrollHeight=0,Q.on(i._scrollElement,"scroll.bs.scrollspy",(function(t){return i._process(t)})),i.refresh(),i._process(),i}r(e,t);var n=e.prototype;return n.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":"position",n="auto"===this._config.method?e:this._config.method,i="position"===n?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(this._selector).map((function(t){var e=d(t),o=e?V.findOne(e):null;if(o){var s=o.getBoundingClientRect();if(s.width||s.height)return[q[n](o).top+i,e]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},n.dispose=function(){t.prototype.dispose.call(this),Q.off(this._scrollElement,".bs.scrollspy"),this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},n._getConfig=function(t){if("string"!=typeof(t=s({},Kt,"object"==typeof t&&t?t:{})).target&&g(t.target)){var e=t.target.id;e||(e=c(Rt),t.target.id=e),t.target="#"+e}return _(Rt,t,Qt),t},n._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},n._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},n._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},n._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&(void 0===this._offsets[o+1]||t li > .active":".active";e=(e=V.find(o,i))[e.length-1]}var s=null;if(e&&(s=Q.trigger(e,"hide.bs.tab",{relatedTarget:this._element})),!(Q.trigger(this._element,"show.bs.tab",{relatedTarget:e}).defaultPrevented||null!==s&&s.defaultPrevented)){this._activate(this._element,i);var r=function(){Q.trigger(e,"hidden.bs.tab",{relatedTarget:t._element}),Q.trigger(t._element,"shown.bs.tab",{relatedTarget:e})};n?this._activate(n,n.parentNode,r):r()}}},n._activate=function(t,e,n){var i=this,o=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,".active"):V.find(":scope > li > .active",e))[0],s=n&&o&&o.classList.contains("fade"),r=function(){return i._transitionComplete(t,o,n)};if(o&&s){var a=h(o);o.classList.remove("show"),Q.one(o,"transitionend",r),m(o,a)}else r()},n._transitionComplete=function(t,e,n){if(e){e.classList.remove("active");var i=V.findOne(":scope > .dropdown-menu .active",e.parentNode);i&&i.classList.remove("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}(t.classList.add("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),y(t),t.classList.contains("fade")&&t.classList.add("show"),t.parentNode&&t.parentNode.classList.contains("dropdown-menu"))&&(t.closest(".dropdown")&&V.find(".dropdown-toggle").forEach((function(t){return t.classList.add("active")})),t.setAttribute("aria-expanded",!0));n&&n()},e.jQueryInterface=function(t){return this.each((function(){var n=L(this,"bs.tab")||new e(this);if("string"==typeof t){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},o(e,null,[{key:"DATA_KEY",get:function(){return"bs.tab"}}]),e}(U);Q.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){t.preventDefault(),(L(this,"bs.tab")||new Wt(this)).show()})),E((function(){var t=w();if(t){var e=t.fn.tab;t.fn.tab=Wt.jQueryInterface,t.fn.tab.Constructor=Wt,t.fn.tab.noConflict=function(){return t.fn.tab=e,Wt.jQueryInterface}}}));var Ft={animation:"boolean",autohide:"boolean",delay:"number"},Yt={animation:!0,autohide:!0,delay:5e3},zt=function(t){function e(e,n){var i;return(i=t.call(this,e)||this)._config=i._getConfig(n),i._timeout=null,i._setListeners(),i}r(e,t);var n=e.prototype;return n.show=function(){var t=this;if(!Q.trigger(this._element,"show.bs.toast").defaultPrevented){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var e=function(){t._element.classList.remove("showing"),t._element.classList.add("show"),Q.trigger(t._element,"shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove("hide"),y(this._element),this._element.classList.add("showing"),this._config.animation){var n=h(this._element);Q.one(this._element,"transitionend",e),m(this._element,n)}else e()}},n.hide=function(){var t=this;if(this._element.classList.contains("show")&&!Q.trigger(this._element,"hide.bs.toast").defaultPrevented){var e=function(){t._element.classList.add("hide"),Q.trigger(t._element,"hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){var n=h(this._element);Q.one(this._element,"transitionend",e),m(this._element,n)}else e()}},n.dispose=function(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),Q.off(this._element,"click.dismiss.bs.toast"),t.prototype.dispose.call(this),this._config=null},n._getConfig=function(t){return t=s({},Yt,q.getDataAttributes(this._element),"object"==typeof t&&t?t:{}),_("toast",t,this.constructor.DefaultType),t},n._setListeners=function(){var t=this;Q.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',(function(){return t.hide()}))},n._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},e.jQueryInterface=function(t){return this.each((function(){var n=L(this,"bs.toast");if(n||(n=new e(this,"object"==typeof t&&t)),"string"==typeof t){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t](this)}}))},o(e,null,[{key:"DefaultType",get:function(){return Ft}},{key:"Default",get:function(){return Yt}},{key:"DATA_KEY",get:function(){return"bs.toast"}}]),e}(U);return E((function(){var t=w();if(t){var e=t.fn.toast;t.fn.toast=zt.jQueryInterface,t.fn.toast.Constructor=zt,t.fn.toast.noConflict=function(){return t.fn.toast=e,zt.jQueryInterface}}})),{Alert:F,Button:Y,Carousel:et,Collapse:st,Dropdown:mt,Modal:bt,Popover:Mt,ScrollSpy:Ut,Tab:Wt,Toast:zt,Tooltip:It}})); 7 | //# sourceMappingURL=bootstrap.min.js.map -------------------------------------------------------------------------------- /frontend/public/bootstrap/dashboard.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: .875rem; 3 | } 4 | 5 | .feather { 6 | width: 16px; 7 | height: 16px; 8 | vertical-align: text-bottom; 9 | } 10 | 11 | /* 12 | * Sidebar 13 | */ 14 | 15 | .sidebar { 16 | position: fixed; 17 | top: 0; 18 | /* rtl:raw: 19 | right: 0; 20 | */ 21 | bottom: 0; 22 | /* rtl:remove */ 23 | left: 0; 24 | z-index: 100; /* Behind the navbar */ 25 | padding: 48px 0 0; /* Height of navbar */ 26 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); 27 | } 28 | 29 | @media (max-width: 767.98px) { 30 | .sidebar { 31 | top: 5rem; 32 | } 33 | } 34 | 35 | .sidebar-sticky { 36 | position: relative; 37 | top: 0; 38 | height: calc(100vh - 48px); 39 | padding-top: .5rem; 40 | overflow-x: hidden; 41 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 42 | } 43 | 44 | .sidebar .nav-link { 45 | font-weight: 500; 46 | color: #333; 47 | } 48 | 49 | .sidebar .nav-link .feather { 50 | margin-right: 4px; 51 | color: #727272; 52 | } 53 | 54 | .sidebar .nav-link.active { 55 | color: #007bff; 56 | } 57 | 58 | .sidebar .nav-link:hover .feather, 59 | .sidebar .nav-link.active .feather { 60 | color: inherit; 61 | } 62 | 63 | .sidebar-heading { 64 | font-size: .75rem; 65 | text-transform: uppercase; 66 | } 67 | 68 | /* 69 | * Navbar 70 | */ 71 | 72 | .navbar-brand { 73 | padding-top: .75rem; 74 | padding-bottom: .75rem; 75 | font-size: 1rem; 76 | background-color: rgba(0, 0, 0, .25); 77 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); 78 | } 79 | 80 | .navbar .navbar-toggler { 81 | top: .25rem; 82 | right: 1rem; 83 | } 84 | 85 | .navbar .form-control { 86 | padding: .75rem 1rem; 87 | border-width: 0; 88 | border-radius: 0; 89 | } 90 | 91 | .form-control-dark { 92 | color: #fff; 93 | background-color: rgba(255, 255, 255, .1); 94 | border-color: rgba(255, 255, 255, .1); 95 | } 96 | 97 | .form-control-dark:focus { 98 | border-color: transparent; 99 | box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); 100 | } 101 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remote-gildor/hardhat-web3-vue-starter/39ac171c0f42cf1a3d3ef61084da852a0ee28d11/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Hardhat Vue Starter by Gildor 11 | 12 | 13 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remote-gildor/hardhat-web3-vue-starter/39ac171c0f42cf1a3d3ef61084da852a0ee28d11/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 75 | -------------------------------------------------------------------------------- /frontend/src/contracts/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import store from "./store/index.js"; 4 | import router from "./router.js"; 5 | import Toasted from 'vue-toasted'; 6 | 7 | Vue.use(Toasted); 8 | Vue.config.productionTip = false; 9 | 10 | new Vue({ 11 | router, 12 | store, 13 | render: h => h(App), 14 | }).$mount('#app'); 15 | -------------------------------------------------------------------------------- /frontend/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /frontend/src/pages/Profile.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 37 | -------------------------------------------------------------------------------- /frontend/src/pages/SetValue.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | -------------------------------------------------------------------------------- /frontend/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | import Home from "./pages/Home"; 4 | import Profile from "./pages/Profile"; 5 | import SetValue from "./pages/SetValue"; 6 | 7 | Vue.use(Router); 8 | 9 | export default new Router({ 10 | // Make sure the server can handle the history mode 11 | // If not, set it to hash (or delete the mode) 12 | // More info here: https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations 13 | mode: "history", 14 | routes: [ 15 | { 16 | path: "/", 17 | name: "home", 18 | component: Home 19 | }, 20 | { 21 | path: "/set-value", 22 | name: "setValue", 23 | component: SetValue 24 | }, 25 | { 26 | path: "/profile", 27 | name: "profile", 28 | component: Profile 29 | } 30 | ], 31 | linkActiveClass: "active" 32 | }); -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import accounts from "./modules/accounts"; 4 | import contracts from "./modules/contracts"; 5 | 6 | Vue.use(Vuex); 7 | 8 | export default new Vuex.Store({ 9 | modules: { 10 | accounts, 11 | contracts 12 | } 13 | }); -------------------------------------------------------------------------------- /frontend/src/store/modules/accounts.js: -------------------------------------------------------------------------------- 1 | import Web3Modal from "web3modal"; 2 | import Web3 from "web3"; 3 | import BurnerConnectProvider from "@burner-wallet/burner-connect-provider"; 4 | import Authereum from "authereum"; 5 | 6 | const state = { 7 | activeAccount: null, 8 | activeBalance: 0, 9 | chainId: null, 10 | chainName: null, 11 | web3: null, 12 | isConnected: false, 13 | providerW3m: null, // this is "provider" from Web3Modal 14 | web3Modal: null 15 | }; 16 | 17 | const getters = { 18 | getActiveAccount(state) { 19 | if (!state.activeAccount) { 20 | return window.ethereum.selectedAddress; 21 | } 22 | 23 | return state.activeAccount; 24 | }, 25 | getActiveBalanceWei(state) { 26 | return state.activeBalance; 27 | }, 28 | getActiveBalanceEth(state) { 29 | return state.web3.utils.fromWei(state.activeBalance, "ether"); 30 | }, 31 | getChainId(state) { 32 | return state.chainId; 33 | }, 34 | getChainName(state) { 35 | return state.chainName; 36 | }, 37 | getWeb3(state) { 38 | if (state.web3) { 39 | return state.web3; 40 | } else { 41 | return new Web3(Web3.givenProvider); 42 | } 43 | }, 44 | getWeb3Modal(state) { 45 | return state.web3Modal; 46 | }, 47 | isUserConnected(state) { 48 | return state.isConnected; 49 | } 50 | }; 51 | 52 | const actions = { 53 | 54 | async initWeb3Modal({ commit }) { 55 | const providerOptions = { 56 | // MetaMask is enabled by default 57 | // Find other providers here: https://github.com/Web3Modal/web3modal/tree/master/docs/providers 58 | burnerconnect: { 59 | package: BurnerConnectProvider // required 60 | }, 61 | authereum: { 62 | package: Authereum // required 63 | } 64 | }; 65 | 66 | const w3mObject = new Web3Modal({ 67 | cacheProvider: true, // optional 68 | providerOptions // required 69 | }); 70 | 71 | // This will get deprecated soon. Setting it to false removes a warning from the console. 72 | window.ethereum.autoRefreshOnNetworkChange = false; 73 | 74 | // if the user is flagged as already connected, automatically connect back to Web3Modal 75 | if (localStorage.getItem('isConnected') === "true") { 76 | let providerW3m = await w3mObject.connect(); 77 | commit("setIsConnected", true); 78 | 79 | commit("setActiveAccount", window.ethereum.selectedAddress); 80 | commit("setChainData", window.ethereum.chainId); 81 | commit("setWeb3Provider", providerW3m); 82 | actions.fetchActiveBalance({ commit }); 83 | } 84 | 85 | commit("setWeb3ModalInstance", w3mObject); 86 | }, 87 | 88 | async connectWeb3Modal({ commit }) { 89 | let providerW3m = await state.web3Modal.connect(); 90 | commit("setIsConnected", true); 91 | 92 | commit("setActiveAccount", window.ethereum.selectedAddress); 93 | commit("setChainData", window.ethereum.chainId); 94 | commit("setWeb3Provider", providerW3m); 95 | actions.fetchActiveBalance({ commit }); 96 | }, 97 | 98 | async disconnectWeb3Modal({ commit }) { 99 | commit("disconnectWallet"); 100 | commit("setIsConnected", false); 101 | }, 102 | 103 | async ethereumListener({ commit }) { 104 | 105 | window.ethereum.on('accountsChanged', (accounts) => { 106 | if (state.isConnected) { 107 | commit("setActiveAccount", accounts[0]); 108 | commit("setWeb3Provider", state.providerW3m); 109 | actions.fetchActiveBalance({ commit }); 110 | } 111 | }); 112 | 113 | window.ethereum.on('chainChanged', (chainId) => { 114 | commit("setChainData", chainId); 115 | commit("setWeb3Provider", state.providerW3m); 116 | actions.fetchActiveBalance({ commit }); 117 | }); 118 | 119 | }, 120 | 121 | async fetchActiveBalance({ commit }) { 122 | let balance = await state.web3.eth.getBalance(state.activeAccount); 123 | commit("setActiveBalance", balance); 124 | } 125 | 126 | }; 127 | 128 | const mutations = { 129 | 130 | async disconnectWallet(state) { 131 | state.activeAccount = null; 132 | state.activeBalance = 0; 133 | state.web3 = null; 134 | if (state.providerW3m.close && state.providerW3m !== null) { 135 | await state.providerW3m.close(); 136 | } 137 | state.providerW3m = null; 138 | await state.web3Modal.clearCachedProvider(); 139 | 140 | window.location.href = '../'; // redirect to the Main page 141 | }, 142 | 143 | setActiveAccount(state, selectedAddress) { 144 | state.activeAccount = selectedAddress; 145 | }, 146 | 147 | setActiveBalance(state, balance) { 148 | state.activeBalance = balance; 149 | }, 150 | 151 | setChainData(state, chainId) { 152 | state.chainId = chainId; 153 | 154 | switch(chainId) { 155 | case "0x1": 156 | state.chainName = "Mainnet"; 157 | break; 158 | case "0x2a": 159 | state.chainName = "Kovan"; 160 | break; 161 | case "0x3": 162 | state.chainName = "Ropsten"; 163 | break; 164 | case "0x4": 165 | state.chainName = "Rinkeby"; 166 | break; 167 | case "0x5": 168 | state.chainName = "Goerli"; 169 | break; 170 | case "0x539": // 1337 (often used on localhost) 171 | case "0x1691": // 5777 (default in Ganache) 172 | default: 173 | state.chainName = "Localhost"; 174 | break; 175 | } 176 | }, 177 | 178 | async setWeb3Provider(state, providerW3m) { 179 | state.providerW3m = providerW3m; 180 | state.web3 = new Web3(providerW3m); 181 | }, 182 | 183 | setIsConnected(state, isConnected) { 184 | state.isConnected = isConnected; 185 | // add to persistent storage so that the user can be logged back in when revisiting website 186 | localStorage.setItem('isConnected', isConnected); 187 | }, 188 | 189 | setWeb3ModalInstance(state, w3mObject) { 190 | state.web3Modal = w3mObject; 191 | } 192 | 193 | }; 194 | 195 | export default { 196 | namespaced: true, 197 | state, 198 | getters, 199 | actions, 200 | mutations 201 | }; 202 | -------------------------------------------------------------------------------- /frontend/src/store/modules/contracts.js: -------------------------------------------------------------------------------- 1 | import Calc from "../../contracts/Calc.json"; 2 | import addresses from "../../contracts/addresses.json"; 3 | 4 | const state = { 5 | num: 0, 6 | calcAbi: null, 7 | calcAddress: null, 8 | calcContract: null 9 | }; 10 | 11 | const getters = { 12 | getNum(state) { 13 | return state.num; 14 | }, 15 | getCalcAbi(state) { 16 | return state.calcAbi; 17 | }, 18 | getCalcAddress(state) { 19 | return state.calcAddress; 20 | }, 21 | getCalcContract(state) { 22 | return state.calcContract; 23 | } 24 | }; 25 | 26 | const actions = { 27 | async fetchCalcContract({ commit, rootState }) { 28 | let web3 = rootState.accounts.web3; 29 | let chainIdDec = parseInt(rootState.accounts.chainId); 30 | let calcAddress = addresses.Calc[chainIdDec]; 31 | 32 | let contract = new web3.eth.Contract(Calc.abi, calcAddress); 33 | commit("setCalcContract", contract); 34 | }, 35 | async fetchNum({ commit, state }) { 36 | if (!state.calcContract) { 37 | this.fetchCalcContract(); 38 | } 39 | 40 | let num = await state.calcContract.methods.getNum().call(); 41 | 42 | commit("setNum", num); 43 | }, 44 | storeCalcAbi({commit}) { 45 | commit("setCalcAbi", Calc.abi); 46 | }, 47 | storeCalcAddress({ commit, rootState }) { 48 | let chainIdDec = parseInt(rootState.accounts.chainId); 49 | let calcAddress = addresses.Calc[chainIdDec]; 50 | 51 | commit("setCalcAddress", calcAddress); 52 | } 53 | }; 54 | 55 | const mutations = { 56 | setNum(state, _num) { 57 | state.num = _num; 58 | }, 59 | setCalcAbi(state, abi) { 60 | state.calcAbi = abi; 61 | }, 62 | setCalcAddress(state, address) { 63 | state.calcAddress = address; 64 | }, 65 | setCalcContract(state, _contract) { 66 | state.calcContract = _contract; 67 | } 68 | }; 69 | 70 | export default { 71 | namespaced: true, 72 | state, 73 | getters, 74 | actions, 75 | mutations 76 | }; 77 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-truffle5"); 2 | require('dotenv').config(); 3 | 4 | /** 5 | * @type import('hardhat/config').HardhatUserConfig 6 | */ 7 | module.exports = { 8 | defaultNetwork: "hardhat", 9 | 10 | networks: { 11 | hardhat: {}, 12 | ganache: { 13 | url: "http://127.0.0.1:7545/", 14 | saveDeployments: true 15 | } 16 | //goerli: { 17 | // url: "https://eth-goerli.alchemyapi.io/v2/" + process.env.ALCHEMY_API_KEY, 18 | // accounts: [process.env.ALCHEMY_DEPLOYMENT_KEY] 19 | //} 20 | }, 21 | 22 | paths: { 23 | sources: "./contracts", 24 | tests: "./test", 25 | cache: "./cache", 26 | artifacts: "./artifacts" 27 | }, 28 | 29 | solidity: { 30 | version: "0.7.3", 31 | settings: { 32 | optimizer: { 33 | enabled: true, 34 | runs: 200 35 | } 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-web3-vue-starter", 3 | "version": "1.0.0", 4 | "description": "An example hardhat dapp to jumpstart your project.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Gildor", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@nomiclabs/hardhat-truffle5": "^2.0.0", 14 | "@nomiclabs/hardhat-web3": "^2.0.0", 15 | "@openzeppelin/test-helpers": "^0.5.10", 16 | "chai": "^4.2.0", 17 | "hardhat": "^2.0.7", 18 | "hardhat-deploy": "^0.7.0-beta.39", 19 | "web3": "^1.3.1" 20 | }, 21 | "dependencies": { 22 | "dotenv": "^8.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // write down contracts that you wish to deploy one-by-one (names only, no .sol extension) 5 | // after the run, find the ABIs and addresses in frontend/src/contracts 6 | const contracts = ["Calc"]; 7 | 8 | // DO NOT MODIFY CODE BELOW UNLESS ABSOLUTELY NECESSARY 9 | async function publishContract(contractName, chainId) { 10 | // deploy the contract 11 | const contractFactory = artifacts.require(contractName); 12 | const contract = await contractFactory.new(); 13 | 14 | console.log(contractName + " contract address: " + contract.address); 15 | 16 | // copy the contract JSON file to front-end and add the address field in it 17 | fs.copyFileSync( 18 | path.join(__dirname, "../artifacts/contracts/" + contractName + ".sol/" + contractName + ".json"), //source 19 | path.join(__dirname, "../frontend/src/contracts/" + contractName + ".json") // destination 20 | ); 21 | 22 | // check if addresses.json already exists 23 | let exists = fs.existsSync(path.join(__dirname, "../frontend/src/contracts/addresses.json")); 24 | 25 | // if not, created the file 26 | if (!exists) { 27 | fs.writeFileSync( 28 | path.join(__dirname, "../frontend/src/contracts/addresses.json"), 29 | "{}" 30 | ); 31 | } 32 | 33 | // update the addresses.json file with the new contract address 34 | let addressesFile = fs.readFileSync(path.join(__dirname, "../frontend/src/contracts/addresses.json")); 35 | let addressesJson = JSON.parse(addressesFile); 36 | 37 | if (!addressesJson[contractName]) { 38 | addressesJson[contractName] = {}; 39 | } 40 | 41 | addressesJson[contractName][chainId] = contract.address; 42 | 43 | fs.writeFileSync( 44 | path.join(__dirname, "../frontend/src/contracts/addresses.json"), 45 | JSON.stringify(addressesJson) 46 | ); 47 | } 48 | 49 | async function main() { 50 | let accounts = await web3.eth.getAccounts(); 51 | let deployer = accounts[0]; 52 | 53 | let chainId = await web3.eth.getChainId(); 54 | console.log("Chain ID:", chainId); 55 | 56 | console.log( 57 | "Deploying contracts with the account:", 58 | deployer 59 | ); 60 | 61 | for (cont of contracts) { 62 | await publishContract(cont, chainId); 63 | } 64 | } 65 | 66 | main() 67 | .then(() => process.exit(0)) 68 | .catch(error => { 69 | console.error(error); 70 | process.exit(1); 71 | }); 72 | -------------------------------------------------------------------------------- /test/Calc.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | BN, // Big Number support 3 | constants, // Common constants, like the zero address and largest integers 4 | expectEvent, // Assertions for emitted events 5 | expectRevert, // Assertions for transactions that should fail 6 | } = require('@openzeppelin/test-helpers'); 7 | 8 | const { assert, expect } = require("chai"); 9 | 10 | const Calc = artifacts.require("Calc"); 11 | 12 | contract("Calc contract", accounts => { 13 | let calcInstance; 14 | 15 | beforeEach(async () => { 16 | calcInstance = await Calc.new(); 17 | }); 18 | 19 | it("gets the num value before a set function is called", async function() { 20 | const numValue = await calcInstance.getNum(); 21 | assert.equal(numValue, 0); 22 | }); 23 | 24 | it("set the new num value and check if it changed", async function() { 25 | await calcInstance.setNum(5); 26 | 27 | const numValue = await calcInstance.getNum(); 28 | assert.equal(numValue, 5); 29 | }); 30 | }); 31 | --------------------------------------------------------------------------------