├── .nvmrc ├── cypress.json ├── docs ├── 02_installation.md ├── 01_technical-overview.md ├── component-overview │ ├── 01_app.md │ ├── 00_index.md │ ├── 101_setup-proxy.md │ ├── 100_service-worker.md │ ├── search │ │ ├── search-bar.md │ │ └── search-box.md │ ├── landing │ │ ├── 00_landing-page.md │ │ └── 01_popular-locations.md │ ├── navigation │ │ ├── 01_user-info.md │ │ ├── 00_navigation-bar.md │ │ └── 02_user-dropdown.md │ ├── results │ │ ├── 00_results-page.md │ │ ├── property │ │ │ └── property.md │ │ └── 01_results-properties.md │ └── notification │ │ └── notification-bar.md ├── images │ ├── enable-webauthn.png │ ├── liked-property.png │ ├── rented-property.png │ ├── scatter-link-account.png │ ├── webauthn-selection.png │ ├── scatter-like-property.png │ ├── scatter-rent-property.png │ └── scatter-custom-network-config.png └── 00_introduction.md ├── .eslintignore ├── public ├── icon.png ├── eosiologo.png ├── manifest.json ├── app-metadata.json ├── chain-manifests.json └── index.html ├── src ├── components │ ├── navigation │ │ ├── LoginButton.scss │ │ ├── LoginButton.js │ │ ├── UserInfo.scss │ │ ├── UserDropdown.scss │ │ ├── NavigationBar.scss │ │ ├── NavigationBar.js │ │ ├── UserDropdown.js │ │ └── UserInfo.js │ ├── results │ │ ├── ResultsProperties.scss │ │ ├── ResultsHeader.js │ │ ├── ResultsHeader.scss │ │ ├── ResultsPage.scss │ │ ├── property │ │ │ ├── PropertyImage.scss │ │ │ ├── PropertyImage.js │ │ │ ├── Property.scss │ │ │ └── Property.js │ │ ├── ResultsProperties.js │ │ └── ResultsPage.js │ ├── landing │ │ ├── LandingHeader.scss │ │ ├── LandingHeader.js │ │ ├── LandingPage.scss │ │ ├── PopularLocations.js │ │ ├── LandingPage.js │ │ └── PopularLocations.scss │ ├── search │ │ ├── SearchBox.js │ │ ├── SearchBar.js │ │ ├── SearchBar.scss │ │ └── SearchBox.scss │ └── notification │ │ ├── NotificationBar.scss │ │ └── NotificationBar.js ├── utils │ ├── keyPress.js │ ├── transaction.js │ └── webauthn.js ├── App.test.js ├── assets │ ├── images │ │ ├── right-leaf-background.svg │ │ ├── left-leaf-background.svg │ │ ├── prev-arrow.svg │ │ ├── down-arrow.svg │ │ ├── up-arrow.svg │ │ ├── next-arrow.svg │ │ ├── heart │ │ │ ├── heart.svg │ │ │ ├── liked-heart.svg │ │ │ └── loading-heart.svg │ │ ├── preview.svg │ │ ├── renting.svg │ │ ├── privacy.svg │ │ ├── property.svg │ │ ├── leave.svg │ │ ├── logo.svg │ │ ├── money-bag.svg │ │ └── tropical-background.svg │ └── styles │ │ └── constants.scss ├── index.scss ├── setupProxy.js ├── App.scss ├── index.js ├── api │ └── index.js ├── App.js └── serviceWorker.js ├── eosio ├── contracts │ ├── eosio.bios-v1.8.3 │ │ ├── eosio.bios.wasm │ │ ├── eosio.bios.contracts.md │ │ └── eosio.bios.abi │ ├── eosio.token │ │ └── eosio.token.contracts.md │ └── tropical │ │ ├── tropical.contracts.md │ │ └── tropical.cpp ├── scripts │ ├── replaceUrlInAppManifest.sh │ └── deploy_contracts.sh └── Dockerfile ├── default.env ├── .eslintrc ├── scripts └── copyDefaultEnv.js ├── .gitpod.nginx.conf ├── .gitpod.dockerfile ├── .github └── PULL_REQUEST_TEMPLATE.md ├── docker-compose.yml ├── LICENSE ├── cypress └── integration │ └── App.spec.js ├── .gitignore ├── .gitpod.yml ├── package.json ├── IMPORTANT.md ├── CONTRIBUTING.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.15.3 2 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /docs/02_installation.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/01_technical-overview.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/01_app.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/00_index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /src/serviceWorker.js 2 | -------------------------------------------------------------------------------- /docs/component-overview/101_setup-proxy.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/100_service-worker.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/search/search-bar.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/search/search-box.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/landing/00_landing-page.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/navigation/01_user-info.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/results/00_results-page.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/results/property/property.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/landing/01_popular-locations.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/navigation/00_navigation-bar.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/navigation/02_user-dropdown.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/notification/notification-bar.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/component-overview/results/01_results-properties.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/public/icon.png -------------------------------------------------------------------------------- /src/components/navigation/LoginButton.scss: -------------------------------------------------------------------------------- 1 | login-button-container { 2 | cursor: pointer; 3 | } -------------------------------------------------------------------------------- /public/eosiologo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/public/eosiologo.png -------------------------------------------------------------------------------- /docs/images/enable-webauthn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/enable-webauthn.png -------------------------------------------------------------------------------- /docs/images/liked-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/liked-property.png -------------------------------------------------------------------------------- /docs/images/rented-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/rented-property.png -------------------------------------------------------------------------------- /docs/images/scatter-link-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/scatter-link-account.png -------------------------------------------------------------------------------- /docs/images/webauthn-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/webauthn-selection.png -------------------------------------------------------------------------------- /docs/images/scatter-like-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/scatter-like-property.png -------------------------------------------------------------------------------- /docs/images/scatter-rent-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/scatter-rent-property.png -------------------------------------------------------------------------------- /docs/images/scatter-custom-network-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/docs/images/scatter-custom-network-config.png -------------------------------------------------------------------------------- /src/utils/keyPress.js: -------------------------------------------------------------------------------- 1 | export const onKeyUpEnter = (event, func) => { 2 | if (event.which === 13 || event.keyCode === 13) { 3 | func() 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /eosio/contracts/eosio.bios-v1.8.3/eosio.bios.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/tropical-example-web-app/HEAD/eosio/contracts/eosio.bios-v1.8.3/eosio.bios.wasm -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | ReactDOM.unmountComponentAtNode(div) 9 | }) 10 | -------------------------------------------------------------------------------- /src/components/results/ResultsProperties.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .results-properties-container { 4 | display: grid; 5 | grid-gap: 3rem; 6 | 7 | @media screen and (min-width: $max-tablet-width) { 8 | grid-column-gap: 6rem; 9 | grid-template-columns: 1fr 1fr; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/images/right-leaf-background.svg: -------------------------------------------------------------------------------- 1 | Right grey leaf -------------------------------------------------------------------------------- /default.env: -------------------------------------------------------------------------------- 1 | # Common 2 | NODE_PATH=src 3 | 4 | # Chain 5 | REACT_APP_CHAIN_ID=cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f 6 | REACT_APP_RPC_PROTOCOL=http 7 | REACT_APP_RPC_HOST=localhost 8 | REACT_APP_RPC_PORT=8888 9 | 10 | # Server 11 | API_SERVER_PRIVATE_KEY=5Jh6jf9g1UzcWrMMsgqd5GrTCgzeKkh5yT7EUZbiU7wB7k4Ayx1 12 | -------------------------------------------------------------------------------- /src/assets/styles/constants.scss: -------------------------------------------------------------------------------- 1 | // Layout 2 | $max-content-width: 1224px; 3 | $max-tablet-width: 768px; 4 | $max-mobile-width: 480px; 5 | 6 | // Padding 7 | $content-padding: 0 2rem; 8 | 9 | // Colors 10 | $button: #46D564; 11 | $text: #2F3F6A; 12 | 13 | $filler-dark: #A1B3C2; 14 | $filler-medium: #C6CFD7; 15 | $filler-light: #E4E8EC; 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "import/resolver": { 4 | "node": { 5 | "extensions": ".js", 6 | "paths": "src/", 7 | "moduleDirectory": ["node_modules/", "src/", "lib/"] 8 | } 9 | } 10 | }, 11 | "extends": "@blockone/blockone", 12 | "rules": { 13 | "import/extensions": [ "error", "ignorePackages" ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/images/left-leaf-background.svg: -------------------------------------------------------------------------------- 1 | Left grey leaves -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Tropical Example", 3 | "name": "Tropical Example", 4 | "icons": [ 5 | { 6 | "src": "icon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/results/ResultsHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './ResultsHeader.scss' 3 | 4 | import SearchBar from 'components/search/SearchBar' 5 | 6 | const ResultsHeader = () => ( 7 |
8 |
9 | 10 |
11 |
12 | ) 13 | 14 | export default ResultsHeader 15 | -------------------------------------------------------------------------------- /eosio/scripts/replaceUrlInAppManifest.sh: -------------------------------------------------------------------------------- 1 | # Gitpod creates a dynamic URL for browsers. We need to update the tropical app manifest to reflect this dynamic URL as the hostname for the app 2 | GP_URL=$(gp url 8000) 3 | echo "Replacing URLs with: ${GP_URL}" 4 | 5 | # Pull in the manifest the chain will serve and update the URLs 6 | sed -i "s%https://localhost:3000%${GP_URL}%g" $PWD/public/chain-manifests.json 7 | -------------------------------------------------------------------------------- /scripts/copyDefaultEnv.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var root = __dirname.replace('scripts', ''); 4 | 5 | if (!fs.existsSync(path.join(root + '.env'))) { 6 | fs.copyFileSync(path.join(root + 'default.env'), path.join(root + '.env')); 7 | console.info('.env NOT found; copying ./default.env to ./.env'); 8 | } else { 9 | console.info('.env already exists; NOT overwriting...'); 10 | } -------------------------------------------------------------------------------- /src/components/results/ResultsHeader.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .results-header-container { 4 | width: 100%; 5 | padding-top: 8rem; 6 | background: url('../../assets/images/tropical-background.svg'); 7 | background-size: cover; 8 | 9 | .results-header-content { 10 | max-width: $max-content-width; 11 | margin: 0 auto; 12 | padding: $content-padding; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/results/ResultsPage.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .results-page { 4 | .results-content { 5 | max-width: $max-content-width; 6 | margin: 0 auto; 7 | padding: $content-padding; 8 | } 9 | 10 | .results-intro { 11 | background: $filler-dark; 12 | height: 2.5rem; 13 | margin: 2.25rem 0; 14 | width: 44%; 15 | border-radius: .5rem; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | margin: 0; 4 | padding: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 6 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | code { 13 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 14 | monospace; 15 | } 16 | -------------------------------------------------------------------------------- /.gitpod.nginx.conf: -------------------------------------------------------------------------------- 1 | events {} 2 | 3 | http { 4 | server { 5 | listen 8000 default_server; 6 | 7 | server_name _; 8 | 9 | location / { 10 | proxy_pass http://127.0.0.1:3000; 11 | proxy_http_version 1.1; 12 | proxy_set_header Upgrade $http_upgrade; 13 | proxy_set_header Connection "Upgrade"; 14 | } 15 | 16 | location /v1/ { 17 | proxy_pass http://127.0.0.1:8888; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/components/landing/LandingHeader.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .landing-header-container { 4 | width: 100%; 5 | margin-bottom: 8rem; 6 | padding-top: 5rem; 7 | background-image: url('../../assets/images/tropical-background.svg'); 8 | background-size: cover; 9 | 10 | .landing-header-content { 11 | position: relative; 12 | max-width: $max-content-width; 13 | margin: 0 auto; 14 | padding: $content-padding; 15 | top: 3rem; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/results/property/PropertyImage.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .property-image-container { 4 | cursor: pointer; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | width: 100%; 9 | height: 100%; 10 | background: $filler-medium; 11 | 12 | &:focus:not(.focus-visible) { 13 | outline: none; 14 | } 15 | 16 | .heart { 17 | display: none; 18 | } 19 | 20 | .display { 21 | display: block; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/navigation/LoginButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func } from 'prop-types' 3 | 4 | import { onKeyUpEnter } from 'utils/keyPress' 5 | 6 | const LoginButton = ({ login }) => ( 7 | onKeyUpEnter(event, login)} 13 | > 14 | Login 15 | 16 | ) 17 | 18 | LoginButton.propTypes = { 19 | login: func.isRequired, 20 | } 21 | 22 | export default LoginButton 23 | -------------------------------------------------------------------------------- /src/components/landing/LandingHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func } from 'prop-types' 3 | import './LandingHeader.scss' 4 | 5 | import SearchBox from 'components/search/SearchBox' 6 | 7 | const LandingHeader = ({ routeToResults }) => ( 8 |
9 |
10 | 11 |
12 |
13 | ) 14 | 15 | LandingHeader.propTypes = { 16 | routeToResults: func.isRequired, 17 | } 18 | 19 | export default LandingHeader 20 | -------------------------------------------------------------------------------- /public/app-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_version": "0.0.7", 3 | "name": "Tropical Example", 4 | "shortname": "Tropical Example", 5 | "scope": "/", 6 | "apphome": "/", 7 | "icon": "/icon.png#8b7c88882a217876ee4cfd6eaecba21f8083d7f37d757cb7ed3b768ba8590f09", 8 | "description": "Simple Example", 9 | "chains": [ 10 | { 11 | "chainId": "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f", 12 | "chainName": "Local Chain", 13 | "icon": "/eosiologo.png#8ae3ccb19f3a89a8ea21f6c5e18bd2bc8f00c379411a2d9319985dad2db6243e" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | // The API is in ES6 and this is run in the node environment 2 | require('@babel/register')({ 3 | presets: ['@babel/preset-env'], 4 | }) 5 | 6 | const Api = require('./api').default 7 | 8 | // This is a mis-use of the react proxySetup to allow for a simple API layer that the application can 9 | // interact with. In a production setup, this would either proxy to an api service endpoint OR proper 10 | // CORS/access control would be in place for the api service to be directly accessible from the app 11 | // 12 | module.exports = (app) => { 13 | app.use('/api', Api()) 14 | } 15 | -------------------------------------------------------------------------------- /.gitpod.dockerfile: -------------------------------------------------------------------------------- 1 | FROM eosio/eosio-web-ide:v0.1.0 2 | 3 | USER root 4 | 5 | ENV ROOT_DIR="/home/gitpod" 6 | RUN echo "INSTALLING EOSIO AND CDT" \ 7 | && apt-get update -y \ 8 | && apt-get install -y wget sudo curl \ 9 | && wget https://github.com/EOSIO/eosio.cdt/releases/download/v1.7.0/eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb \ 10 | && apt-get update && sudo apt install -y ./eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb \ 11 | && wget https://github.com/EOSIO/eos/releases/download/v2.0.0/eosio_2.0.0-1-ubuntu-18.04_amd64.deb \ 12 | && apt-get update && sudo apt install -y ./eosio_2.0.0-1-ubuntu-18.04_amd64.deb \ 13 | && rm ./eosio_2.0.0-1-ubuntu-18.04_amd64.deb \ 14 | && rm ./eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb 15 | -------------------------------------------------------------------------------- /src/assets/images/prev-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/results/ResultsProperties.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func, bool } from 'prop-types' 3 | import './ResultsProperties.scss' 4 | 5 | import Property from 'components/results/property/Property' 6 | 7 | const NUM_PROPERTIES = 8 8 | 9 | const ResultsProperties = ({ login, displayError, enrolled }) => ( 10 |
11 | { [...Array(NUM_PROPERTIES).keys()].map((e) => ( 12 | 13 | )) 14 | } 15 |
16 | ) 17 | 18 | ResultsProperties.propTypes = { 19 | login: func.isRequired, 20 | displayError: func.isRequired, 21 | enrolled: bool.isRequired, 22 | } 23 | 24 | export default ResultsProperties 25 | -------------------------------------------------------------------------------- /src/assets/images/down-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/up-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/results/ResultsPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func, bool } from 'prop-types' 3 | import './ResultsPage.scss' 4 | 5 | import ResultsHeader from 'components/results/ResultsHeader' 6 | import ResultsProperties from 'components/results/ResultsProperties' 7 | 8 | const ResultsPage = ({ login, displayError, enrolled }) => ( 9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 | ) 17 | 18 | ResultsPage.propTypes = { 19 | login: func.isRequired, 20 | displayError: func.isRequired, 21 | enrolled: bool.isRequired, 22 | } 23 | 24 | export default ResultsPage 25 | -------------------------------------------------------------------------------- /src/components/landing/LandingPage.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .landing-page { 4 | .landing-content { 5 | max-width: $max-content-width; 6 | margin: 0 auto; 7 | padding: $content-padding; 8 | 9 | 10 | .background { 11 | position: fixed; 12 | z-index: -1; 13 | opacity: .5; 14 | 15 | @media screen and (max-width: $max-mobile-width) { 16 | display: none; 17 | } 18 | } 19 | 20 | .background-left { 21 | top: 100%; 22 | left: 0%; 23 | transform: translate(0, -100%); 24 | height: 17rem; 25 | } 26 | 27 | .background-right { 28 | position: fixed; 29 | top: 100%; 30 | left: 100%; 31 | transform: translate(-100%, -100%); 32 | height: 15rem; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/results/property/PropertyImage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { bool, func } from 'prop-types' 3 | import './PropertyImage.scss' 4 | 5 | import propertySvg from 'assets/images/property.svg' 6 | import { onKeyUpEnter } from 'utils/keyPress' 7 | 8 | const PropertyImage = ({ onRent }) => ( 9 |
onKeyUpEnter(event, onRent)} 16 | > 17 |
18 | property 19 |
20 |
21 | ) 22 | 23 | PropertyImage.propTypes = { 24 | loading: bool.isRequired, 25 | liked: bool.isRequired, 26 | onRent: func.isRequired, 27 | } 28 | 29 | export default PropertyImage 30 | -------------------------------------------------------------------------------- /src/assets/images/next-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | * { 4 | box-sizing: border-box; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | -webkit-tap-highlight-color: transparent; 8 | } 9 | 10 | html, 11 | body { 12 | height: 100%; 13 | margin: 0; 14 | padding: 0; 15 | font-family: 'Poppins', sans-serif; 16 | font-size: 16px; 17 | border: none; 18 | 19 | *:focus { 20 | outline: none; 21 | } 22 | 23 | @media screen and (min-width: $max-content-width) { 24 | font-size: 18px; 25 | } 26 | } 27 | 28 | .disabled { 29 | opacity: .5; 30 | cursor: not-allowed !important; 31 | } 32 | 33 | .button { 34 | color: white; 35 | font-weight: 600; 36 | text-align: center; 37 | height: 41px; 38 | border-radius: 6px; 39 | padding: 0.4rem; 40 | img { 41 | width: 22px; 42 | height: 16px; 43 | margin-right: 0.5rem; 44 | } 45 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Change Description 5 | 6 | 7 | 8 | ## API Changes 9 | - [ ] API Changes 10 | 11 | 12 | 13 | 14 | ## Documentation Additions 15 | - [ ] Documentation Additions 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/chain-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_version": "0.0.7", 3 | "manifests": [ 4 | { 5 | "chainId": "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f", 6 | "manifest": { 7 | "account": "tropical", 8 | "domain": "https://localhost:3000", 9 | "appmeta": "https://localhost:3000/app-metadata.json#bc677523fca562e307343296e49596e25cb14aac6b112a9428a42119da9f65fa", 10 | "whitelist": [ 11 | { 12 | "contract": "tropical", 13 | "action": "like" 14 | }, 15 | { 16 | "contract": "tropical", 17 | "action": "rent" 18 | }, 19 | { 20 | "contract": "tropical", 21 | "action": "check2fa" 22 | }, 23 | { 24 | "contract": "eosio.assert", 25 | "action": "require" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/landing/PopularLocations.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './PopularLocations.scss' 3 | 4 | import previous from 'assets/images/prev-arrow.svg' 5 | import next from 'assets/images/next-arrow.svg' 6 | 7 | const PopularLocations = () => ( 8 |
9 |
10 |

Popular Locations

11 |
12 |
13 |
14 | previous 15 | next 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ) 24 | 25 | export default PopularLocations 26 | -------------------------------------------------------------------------------- /src/components/landing/LandingPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func } from 'prop-types' 3 | import './LandingPage.scss' 4 | 5 | import LandingHeader from 'components/landing/LandingHeader' 6 | import PopularLocations from 'components/landing/PopularLocations' 7 | import leftLeaf from 'assets/images/left-leaf-background.svg' 8 | import rightLeaf from 'assets/images/right-leaf-background.svg' 9 | 10 | const LandingPage = ({ routeToResults }) => ( 11 |
12 | 13 |
14 | 15 | 16 | 17 |
18 |
19 | ) 20 | 21 | LandingPage.propTypes = { 22 | routeToResults: func.isRequired, 23 | } 24 | 25 | export default LandingPage 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | eosio: 4 | build: 5 | context: ./eosio 6 | dockerfile: Dockerfile 7 | labels: 8 | - "cleanup" 9 | image: tropical-example/eosio 10 | command: ["nodeos", "--data-dir", "/root/.local/share", "-e", "-p", "eosio", "--hard-replay", "--plugin", "eosio::producer_plugin", "--plugin", "eosio::chain_api_plugin", "--plugin", "eosio::http_plugin", "--http-server-address=0.0.0.0:8888", "--access-control-allow-origin=*", "--contracts-console", "--http-validate-host=false", "--verbose-http-errors", "--max-transaction-time=100"] 11 | labels: 12 | - "cleanup" 13 | ports: 14 | - "8888:8888" 15 | - "9876:9876" 16 | volumes: 17 | - ./eosio/contracts:/opt/eosio/bin/contracts 18 | - ./eosio/scripts:/opt/eosio/bin/scripts 19 | - eosiochaindata:/root/.local/share 20 | 21 | volumes: 22 | eosiochaindata: 23 | labels: 24 | - "cleanup" 25 | -------------------------------------------------------------------------------- /src/components/search/SearchBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func } from 'prop-types' 3 | import './SearchBox.scss' 4 | 5 | const SearchBox = ({ onSearch }) => { 6 | const handleSubmit = (event) => { 7 | event.preventDefault() 8 | onSearch() 9 | } 10 | 11 | return ( 12 |
13 |

Search a Property

14 |
15 |
16 |
17 |
18 | 19 | 20 | ) 21 | } 22 | 23 | SearchBox.propTypes = { 24 | onSearch: func.isRequired, 25 | } 26 | 27 | export default SearchBox 28 | -------------------------------------------------------------------------------- /src/assets/images/heart/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/navigation/UserInfo.scss: -------------------------------------------------------------------------------- 1 | .user-info-container { 2 | display: grid; 3 | align-items: center; 4 | grid-gap: 0 .5rem; 5 | grid-template-columns: 270px 50px; 6 | grid-template-areas: 7 | 'prefix options' 8 | 'name options' 9 | 'dropdown dropdown'; 10 | 11 | &.user-info-hide-dropdown { 12 | grid-template-columns: 1fr; 13 | 14 | grid-template-areas: 15 | 'prefix' 16 | 'name'; 17 | } 18 | 19 | .user-info-prefix { 20 | text-align: right; 21 | grid-area: prefix; 22 | font-size: .75rem; 23 | font-weight: 500; 24 | } 25 | 26 | .user-info-name { 27 | text-align: right; 28 | grid-area: name; 29 | font-size: 0.90rem; 30 | } 31 | 32 | .user-info-dropdown-btn { 33 | grid-area: options; 34 | cursor: pointer; 35 | width: 1.25rem; 36 | justify-self: end; 37 | 38 | &:focus:not(.focus-visible) { 39 | outline: none; 40 | } 41 | } 42 | 43 | .user-info-dropdown-content { 44 | grid-area: dropdown; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/assets/images/heart/liked-heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/search/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './SearchBar.scss' 3 | 4 | import downArrow from 'assets/images/down-arrow.svg' 5 | 6 | const SearchBar = () => ( 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | select 19 |
20 |
21 |
22 |
23 | select 24 |
25 |
26 |
27 | ) 28 | 29 | export default SearchBar 30 | -------------------------------------------------------------------------------- /src/utils/transaction.js: -------------------------------------------------------------------------------- 1 | export const generateLikeTransaction = account => ({ 2 | actions: [{ 3 | account: 'tropical', 4 | name: 'like', 5 | authorization: [{ 6 | actor: account, 7 | permission: 'active', 8 | }], 9 | data: { 10 | user: account, 11 | }, 12 | }], 13 | }) 14 | 15 | export const generateRentTransaction = (accountName, propertyName, serverKey, userKey, serverAuth, userAuth) => ({ 16 | context_free_actions: [{ 17 | account: 'tropical', 18 | name: 'check2fa', 19 | authorization: [], 20 | data: { 21 | user: accountName, 22 | property: propertyName, 23 | server_key: serverKey, 24 | user_key: userKey, 25 | server_auth: serverAuth, 26 | bearer_auth: userAuth, 27 | }, 28 | }], 29 | actions: [{ 30 | account: 'tropical', 31 | name: 'rent', 32 | authorization: [{ 33 | actor: accountName, 34 | permission: 'active', 35 | }], 36 | data: { 37 | user: accountName, 38 | property: propertyName, 39 | }, 40 | }, 41 | ], 42 | }) 43 | 44 | export const transactionConfig = { broadcast: true, expireSeconds: 300 } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2019 block.one and its contributors. All rights reserved. 2 | 3 | The MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /eosio/contracts/eosio.token/eosio.token.contracts.md: -------------------------------------------------------------------------------- 1 |

2 | transfer 3 |

4 | --- 5 | spec_version: 0.2.0 6 | title: Token Transfer 7 | summary: Transfer tokens from one account to another. 8 | icon: https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Attraction_transfer_icon.svg/200px-Attraction_transfer_icon.svg.png#24785AF89B4480048CF951E39D0C3D70EE20C44F66582F0890938378C0025064 9 | --- 10 | 11 | ## Transfer Terms & Conditions 12 | 13 | I, {{from}}, certify the following to be true to the best of my knowledge: 14 | 15 | 1. I certify that {{quantity}} is not the proceeds of fraudulent or violent activities. 16 | 2. I certify that, to the best of my knowledge, {{to}} is not supporting initiation of violence against others. 17 | 3. I have disclosed any contractual terms & conditions with respect to {{quantity}} to {{to}}. 18 | 19 | I understand that funds transfers are not reversible after the {{$transaction.delay_sec}} seconds or other delay as configured by {{from}}'s permissions. 20 | 21 | If this action fails to be irreversibly confirmed after receiving goods or services from '{{to}}', I agree to either return the goods or services or resend {{quantity}} in a timely manner. 22 | -------------------------------------------------------------------------------- /cypress/integration/App.spec.js: -------------------------------------------------------------------------------- 1 | 2 | describe('Tropical Stay', () => { 3 | 4 | it('Rent Action', () => { 5 | cy.visit('https://localhost:3000') 6 | cy.contains('Login').click() 7 | cy.wait(1000) 8 | cy.get('[aria-label="Scatter"]').click() 9 | cy.wait(2000) 10 | cy.get('.user-info-container').should('have.text', ' Signed in as example') 11 | 12 | cy.get('div[role=button].user-info-dropdown-btn').click() 13 | cy.get('[aria-label="Enable WebAuthn 2FA"]').click() 14 | cy.get('[aria-label="Enable WebAuthn 2FA"]').click() 15 | cy.wait(2500) 16 | cy.get('div[role=button].user-info-dropdown-btn').click() 17 | cy.get('.user-dropdown-item.menu-item-with-icon').first().should('have.text', 'WebAuthn 2FA Enabled!') 18 | 19 | cy.get('[aria-label="Search a Property Submit"]').click() 20 | cy.get('[aria-label="Rent Property Button"]').first().click() 21 | cy.wait(2000) 22 | cy.get('[aria-label="Rent Property Button"]').first().should('have.text', 'Renting') 23 | 24 | cy.get('[aria-label="Like Property Button"]').first().click() 25 | cy.wait(2000) 26 | cy.get('[aria-label="Like Property Button"]').first().should('have.text', 'Liked') 27 | }) 28 | }) 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/notification/NotificationBar.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/styles/constants.scss'; 2 | 3 | .notification-bar-container { 4 | padding: .5rem 1.5rem; 5 | 6 | &.notification-error { 7 | background-color: #F7DDDD; 8 | color: #F4452F; 9 | text-align: center; 10 | position: sticky; 11 | top: 0; 12 | z-index: 15; 13 | } 14 | 15 | .notification-bar-content { 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | 20 | .notification-error-text { 21 | padding: 0 1rem; 22 | width: 100%; 23 | overflow-wrap: break-word; 24 | word-wrap: break-word; 25 | } 26 | 27 | .notification-bar-title { 28 | margin: 0 auto; 29 | padding : .75rem; 30 | color: $text; 31 | text-decoration: none; 32 | font-weight: bold; 33 | text-align: center; 34 | 35 | &:focus:not(.focus-visible) { 36 | outline: none; 37 | } 38 | } 39 | 40 | .notification-bar-close { 41 | opacity: .5; 42 | cursor: pointer; 43 | font-size: 1.75rem; 44 | line-height: 1.5rem; 45 | 46 | &:focus:not(.focus-visible) { 47 | outline: none; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/assets/images/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 514A6B0A-EACB-4F83-84E7-AF717BE0720B@1.00x 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editors 2 | # Intellij and idea based editors 3 | .idea 4 | 5 | # VS Code 6 | .vscode/* 7 | !.vscode/settings.json 8 | !.vscode/tasks.json 9 | !.vscode/launch.json 10 | !.vscode/extensions.json 11 | 12 | ## Sublime Text 13 | # Cache files for Sublime Text 14 | *.tmlanguage.cache 15 | *.tmPreferences.cache 16 | *.stTheme.cache 17 | 18 | # Workspace files are user-specific 19 | *.sublime-workspace 20 | 21 | # OS generated files # 22 | ###################### 23 | .DS_Store 24 | .DS_Store? 25 | ._* 26 | .Spotlight-V100 27 | .Trashes 28 | ehthumbs.db 29 | Thumbs.db 30 | 31 | # Logs 32 | logs 33 | *.log 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | *.out 38 | 39 | # Runtime data 40 | pids 41 | *.pid 42 | *.seed 43 | *.pid.lock 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | 61 | # Yarn Integrity file 62 | .yarn-integrity 63 | 64 | # Testing 65 | /coverage 66 | 67 | # Build 68 | /build 69 | 70 | # Misc 71 | .env 72 | 73 | # Smart Contract Artifacts 74 | *.wasm 75 | *.abi 76 | 77 | -------------------------------------------------------------------------------- /src/assets/images/renting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/landing/PopularLocations.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .popular-container { 4 | display: grid; 5 | align-items: center; 6 | grid-template-areas: 7 | 'title nav' 8 | 'locations locations'; 9 | 10 | .popular-title-container { 11 | grid-area: title; 12 | 13 | .popular-title { 14 | margin: 0 0 1.5rem 0; 15 | font-weight: 600; 16 | } 17 | 18 | .popular-blurb { 19 | height: .5rem; 20 | width: 40%; 21 | margin: .5rem 0; 22 | background: $filler-dark; 23 | border-radius: 1rem; 24 | } 25 | } 26 | 27 | .popular-nav-container { 28 | grid-area: nav; 29 | justify-self: end; 30 | 31 | img { 32 | margin-right: 1rem; 33 | cursor: pointer; 34 | height: 1.8rem; 35 | } 36 | 37 | .popular-prev { 38 | margin-right: 2rem; 39 | cursor: not-allowed; 40 | } 41 | } 42 | 43 | .popular-locations { 44 | display: flex; 45 | flex-direction: row; 46 | grid-area: locations; 47 | height: 13rem; 48 | 49 | .popular-location { 50 | width: 31.33%; 51 | height: 100%; 52 | margin: 1rem 2% 1rem 0; 53 | border-radius: .25rem; 54 | background: $filler-medium; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/search/SearchBar.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .search-bar-container { 4 | display: grid; 5 | grid-template-columns: 2fr 2fr 1fr 1fr; 6 | grid-gap: 3rem; 7 | padding-bottom: 3rem; 8 | 9 | @media screen and (max-width: $max-mobile-width) { 10 | grid-template-columns: 1fr 1fr; 11 | } 12 | 13 | .search-item { 14 | .search-item-rectangle { 15 | background: white; 16 | height: 1.25rem; 17 | border-radius: .5rem; 18 | margin-bottom: 1.25rem; 19 | } 20 | 21 | .search-item-line { 22 | background: rgba(255, 255, 255, .85); 23 | height: 3px; 24 | } 25 | 26 | img { 27 | width: 1.25rem; 28 | } 29 | } 30 | 31 | .search-item-large { 32 | @media screen and (max-width: $max-mobile-width) { 33 | grid-column: 1/3; 34 | } 35 | .search-item-rectangle { 36 | width: 66%; 37 | } 38 | } 39 | 40 | .search-item-small { 41 | display: grid; 42 | grid-template-columns: 1fr 1fr; 43 | 44 | .search-item-rectangle { 45 | justify-self: start; 46 | width: 100%; 47 | } 48 | 49 | .search-item-select { 50 | justify-self: end; 51 | } 52 | 53 | .search-item-line { 54 | grid-column: 1/3; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/results/property/Property.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .property-container { 4 | width: 100%; 5 | display: grid; 6 | grid-template-columns: 1fr 2fr; 7 | grid-column-gap: 1rem; 8 | grid-row-gap: 19px; 9 | grid-auto-rows: 25px; 10 | 11 | .property-image { 12 | grid-column: 1/2; 13 | grid-row: 1/2; 14 | height: 113px; 15 | } 16 | 17 | .property-info { 18 | grid-column: 2/3; 19 | border-radius: 6px; 20 | } 21 | 22 | .property-info-1 { 23 | background: $filler-medium; 24 | width: 100%; 25 | } 26 | 27 | .property-info-2 { 28 | background: $filler-light; 29 | width: 70%; 30 | } 31 | 32 | .property-info-3 { 33 | width: 100%; 34 | } 35 | } 36 | 37 | .results-property:nth-child(even) .property-container { 38 | @media screen and (min-width: 1200px) { 39 | float: right; 40 | } 41 | } 42 | 43 | .rent-button { 44 | display: inline-block; 45 | background-color: #2B455B; 46 | width: 150px; 47 | margin-right: 0.5rem; 48 | cursor: pointer; 49 | &.active { 50 | background-color: #46D564; 51 | } 52 | } 53 | 54 | .like-button { 55 | display: inline-block; 56 | background-color: #597084; 57 | width: 110px; 58 | cursor: pointer; 59 | &.active { 60 | background-color: #FD5F82; 61 | } 62 | } -------------------------------------------------------------------------------- /src/assets/images/heart/loading-heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/navigation/UserDropdown.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .user-dropdown-container > ul > li.user-dropdown-item.menu-item-with-icon { 4 | margin: 1rem 0 0 0.2rem; 5 | } 6 | 7 | .user-info-dropdown-content .user-dropdown-container li.user-dropdown-item.menu-item-with-icon { 8 | display: block; 9 | } 10 | 11 | .user-dropdown-container { 12 | position: relative; 13 | background-color: white; 14 | border-radius: 0.3rem; 15 | font-size: 0.9rem; 16 | padding: 0.5rem 1rem; 17 | right: -0.6rem; 18 | margin-top: 1rem; 19 | margin-left: -2.5rem; 20 | z-index: 1; 21 | cursor: pointer; 22 | 23 | ul { 24 | padding-inline-start: 0px; 25 | } 26 | 27 | li { 28 | &.user-dropdown-item { 29 | margin: 0 0 0 0; 30 | font-weight: normal; 31 | color: black; 32 | } 33 | } 34 | 35 | .menu-item-icon-left { 36 | height: 22px; 37 | width: 22px; 38 | margin: 0px 0.5rem; 39 | vertical-align: text-top; 40 | } 41 | 42 | &:after { 43 | content: ''; 44 | position: absolute; 45 | top: 0.1rem; 46 | left: 51%; 47 | width: 0; 48 | height: 0; 49 | border: 0.6rem solid transparent; 50 | border-bottom-color: white; 51 | border-top: 0; 52 | margin-left: 3.3rem; 53 | margin-top: -0.6rem; 54 | 55 | @media screen and (max-width: $max-mobile-width) { 56 | left: 52.5%; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/00_introduction.md: -------------------------------------------------------------------------------- 1 | `Tropical Stay` is a [**React**](https://reactjs.org/) application which provides examples for various **EOSIO** functionality to include: 2 | 3 | * Using the [`ual-reactjs-renderer`](https://github.com/EOSIO/ual-reactjs-renderer) in an application. 4 | * Using various community built authenticators with `ual-reactjs-renderer`. 5 | * Maintaining the user state and signing transactions for a user with `ual-reactjs-renderer`. 6 | * Using `WebAuthn` to sign higher risk transactions when authenticator based authentication is insufficient. 7 | 8 | 9 | The documentation for `Tropical Stay` is structured in the following way: 10 | 11 | * [Installation](02_installation.md) explains how to install dependencies and run the application for local development, or alternatively, use [`Gitpod`](https://www.gitpod.io/). 12 | * [Technical Overview](01_technical-overview.md) provides a high-level overview of the `Tropical Stay` app, the various components it interacts with, and how these interactions are carried out. 13 | * [Component Overview](component-overview/) provides information related to each of the specific **React** components present in `Tropical Stay` and how these components interact with one another. 14 | * [FAQ](faq/) provides answers to frequently asked questions surrounding the `Tropical Stay` app. 15 | * [Troubleshooting](troubleshooting/) provides possible exceptions encountered when using `Tropical Stay` and their most common causes. -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.dockerfile 3 | 4 | github: 5 | prebuilds: 6 | branches: true 7 | 8 | ports: 9 | - port: 3000 10 | onOpen: ignore 11 | # We open this port because it's the port that nginx is serving and which is federating the other services (web app and chain in this case) 12 | - port: 8000 13 | onOpen: open-browser 14 | - port: 8900 15 | onOpen: ignore 16 | - port: 8080 17 | onOpen: ignore 18 | - port: 8888 19 | onOpen: ignore 20 | - port: 9876 21 | onOpen: ignore 22 | 23 | tasks: 24 | - name: WebApp 25 | init: yarn 26 | # We need the $PWD here because this path is not resolved from the current folder but from the path nginx runs from. $PWD is the workspace path where the copy of the repo files is 27 | command: alias ls='ls -al --color'; nginx -c $PWD/.gitpod.nginx.conf; yarn startInGitpod 28 | - name: Blockchain 29 | before: alias ls='ls -al --color' && $PWD/eosio/scripts/replaceUrlInAppManifest.sh && $PWD/eosio/scripts/deploy_contracts.sh "" IS_GITPOD && cleos wallet unlock --password > PLEASE minimally read the section of the README (above) related to Gitpod for important instructions.\n\n\n-----------------------------\n\n\n\n\n\n" 33 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | Tropical Example 19 | 20 | 21 | 24 |
25 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/notification/NotificationBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func, instanceOf } from 'prop-types' 3 | import './NotificationBar.scss' 4 | 5 | import { onKeyUpEnter } from 'utils/keyPress' 6 | 7 | const NotificationBar = ({ hideNotificationBar, error }) => ( 8 |
9 |
10 | { error 11 | ? ( 12 |
13 | Error: 14 | {' '} 15 | { error.message ? error.message : error.reason } 16 |
17 | ) 18 | : ( 19 | 20 | 25 | Developer Demo - View Guide 26 | 27 | 28 | ) 29 | } 30 |
onKeyUpEnter(e, hideNotificationBar)} 36 | > 37 | × 38 |
39 |
40 |
41 | ) 42 | 43 | NotificationBar.propTypes = { 44 | hideNotificationBar: func.isRequired, 45 | error: instanceOf(Error), 46 | } 47 | 48 | NotificationBar.defaultProps = { 49 | error: null, 50 | } 51 | 52 | export default NotificationBar 53 | -------------------------------------------------------------------------------- /src/components/navigation/NavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .navigation-bar-container { 4 | position: absolute; 5 | height: 2rem; 6 | padding: 0.5rem 0 3.5rem 0; 7 | width: 100%; 8 | 9 | .navigation-bar-content { 10 | max-width: $max-content-width; 11 | margin: 0 auto; 12 | padding: $content-padding; 13 | color: white; 14 | 15 | .navigation-bar-title-container { 16 | display: flex; 17 | float: left; 18 | cursor: pointer; 19 | 20 | &:focus:not(.focus-visible) { 21 | outline: none; 22 | } 23 | 24 | .navigation-bar-logo { 25 | width: 10rem; 26 | height: 4rem; 27 | 28 | @media screen and (max-width: $max-mobile-width) { 29 | width: 7.5rem; 30 | } 31 | } 32 | } 33 | 34 | .navigation-bar-list { 35 | float: right; 36 | list-style-type: none; 37 | padding: 0; 38 | margin: 0; 39 | cursor: default; 40 | 41 | li { 42 | display: inline-block; 43 | margin: 1.5rem 0 0 3rem; 44 | font-weight: 600; 45 | 46 | &.post { 47 | margin-left: 0; 48 | cursor: pointer; 49 | 50 | @media screen and (max-width: $max-mobile-width) { 51 | display: none; 52 | } 53 | } 54 | 55 | &.login { 56 | cursor: pointer; 57 | 58 | &:focus:not(.focus-visible) { 59 | outline: none; 60 | } 61 | } 62 | 63 | &.user-info { 64 | vertical-align: top; 65 | margin-top: 1rem; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /eosio/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN echo "INSTALLING EOSIO AND CDT" 4 | RUN apt-get update && apt-get install -y wget sudo curl 5 | RUN wget https://github.com/EOSIO/eosio.cdt/releases/download/v1.7.0/eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb 6 | RUN apt-get update && sudo apt install -y ./eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb 7 | RUN wget https://github.com/EOSIO/eos/releases/download/v2.0.0/eosio_2.0.0-1-ubuntu-18.04_amd64.deb 8 | RUN apt-get update && sudo apt install -y ./eosio_2.0.0-1-ubuntu-18.04_amd64.deb 9 | 10 | RUN echo "INSTALLING CONTRACTS" 11 | RUN mkdir -p "/opt/eosio/bin/contracts" 12 | 13 | RUN echo "INSTALLING EOSIO.CONTRACTS" 14 | RUN wget https://github.com/EOSIO/eosio.contracts/archive/v1.9.0.tar.gz 15 | RUN mkdir -p /eosio.contracts 16 | RUN tar xvzf ./v1.9.0.tar.gz -C /eosio.contracts 17 | RUN mv /eosio.contracts/eosio.contracts-1.9.0 /opt/eosio/bin/contracts 18 | RUN mv /opt/eosio/bin/contracts/eosio.contracts-1.9.0 /opt/eosio/bin/contracts/eosio.contracts 19 | 20 | RUN echo "INSTALLING EOSIO.ASSERT CONTRACT" 21 | RUN wget https://github.com/EOSIO/eosio.assert/archive/v0.1.0.tar.gz 22 | RUN mkdir -p /eosio.assert 23 | RUN tar xvzf ./v0.1.0.tar.gz -C /eosio.assert 24 | RUN mv /eosio.assert/eosio.assert-0.1.0 /opt/eosio/bin/contracts 25 | RUN mv /opt/eosio/bin/contracts/eosio.assert-0.1.0 /opt/eosio/bin/contracts/eosio.assert 26 | 27 | RUN echo "COPYING APP CONTRACTS" 28 | COPY ./ /opt/eosio/bin 29 | 30 | RUN echo "COPYING EOSIO.TOKEN RICARDIAN CONTRACT" 31 | RUN cp /opt/eosio/bin/contracts/eosio.token/eosio.token.contracts.md /opt/eosio/bin/contracts/eosio.contracts/contracts/eosio.token/src 32 | 33 | RUN echo "DEPLOYING CONTRACTS" 34 | RUN mkdir -p "/opt/eosio/bin/config-dir" 35 | RUN /bin/bash /opt/eosio/bin/scripts/deploy_contracts.sh 36 | -------------------------------------------------------------------------------- /src/components/search/SearchBox.scss: -------------------------------------------------------------------------------- 1 | @import "src/assets/styles/constants.scss"; 2 | 3 | .search-box-container { 4 | display: grid; 5 | width: 45%; 6 | background: white; 7 | border-radius: .25rem; 8 | padding: 1.5rem; 9 | margin: $content-padding; 10 | box-shadow: 1px 2px 1rem #AAA; 11 | grid-template-areas: 12 | "title title" 13 | "name name" 14 | "location location" 15 | "left right" 16 | "button button"; 17 | 18 | @media screen and (min-width: $max-content-width) { 19 | width: 36%; 20 | } 21 | 22 | @media screen and (max-width: $max-tablet-width) { 23 | width: 60%; 24 | } 25 | 26 | @media screen and (max-width: $max-mobile-width) { 27 | width: 100%; 28 | margin: 0; 29 | } 30 | 31 | .search-field { 32 | height: 2rem; 33 | border-bottom: 2px solid $filler-light; 34 | margin-bottom: 3rem; 35 | } 36 | 37 | .search-box-title { 38 | grid-area: title; 39 | margin: 0 0 2.5rem 0; 40 | font-weight: 600; 41 | } 42 | 43 | .name { 44 | grid-area: name; 45 | } 46 | 47 | .location { 48 | grid-area: location; 49 | } 50 | 51 | .left { 52 | grid-area: left; 53 | width: 85%; 54 | } 55 | 56 | .right { 57 | grid-area: right; 58 | justify-self: right; 59 | width: 85%; 60 | } 61 | 62 | .search-button { 63 | grid-area: button; 64 | height: 3rem; 65 | border-radius: .5rem; 66 | cursor: pointer; 67 | background-color: $button; 68 | color: white; 69 | font-size: 1rem; 70 | border: none; 71 | font-family: 'Poppins', sans-serif; 72 | font-weight: 700; 73 | 74 | &:focus:not(.focus-visible) { 75 | outline: none; 76 | } 77 | } 78 | 79 | .blurb { 80 | width: 50%; 81 | height: 50%; 82 | border-radius: 5rem; 83 | background: $filler-light; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | // UAL Required Imports 5 | import { UALProvider } from 'ual-reactjs-renderer' 6 | // Authenticator Imports 7 | import { EOSIOAuth } from 'ual-eosio-reference-authenticator' 8 | import { Scatter } from 'ual-scatter' 9 | import { Lynx } from 'ual-lynx' 10 | import { TokenPocket } from 'ual-token-pocket' 11 | 12 | import 'focus-visible/dist/focus-visible.min.js' 13 | import 'index.scss' 14 | import 'assets/styles/constants.scss' 15 | 16 | import App from './App' 17 | import * as serviceWorker from './serviceWorker' 18 | 19 | const appName = 'Tropical-Example' 20 | 21 | const isGitpod = process.env.REACT_APP_IS_GITPOD === 'true' 22 | 23 | // Chains 24 | const chain = { 25 | chainId: process.env.REACT_APP_CHAIN_ID, 26 | rpcEndpoints: [ 27 | isGitpod ? { 28 | protocol: window.location.protocol.replace(/:$/, ''), 29 | host: window.location.host, 30 | port: '', 31 | } : { 32 | protocol: process.env.REACT_APP_RPC_PROTOCOL, 33 | host: process.env.REACT_APP_RPC_HOST, 34 | port: process.env.REACT_APP_RPC_PORT, 35 | }, 36 | ], 37 | } 38 | 39 | // Authenticators 40 | const eosioAuth = new EOSIOAuth([chain], { appName, protocol: 'eosio' }) 41 | const scatter = new Scatter([chain], { appName }) 42 | const lynx = new Lynx([chain]) 43 | const tokenPocket = new TokenPocket([chain]) 44 | 45 | const supportedChains = [chain] 46 | const supportedAuthenticators = [eosioAuth, scatter, lynx, tokenPocket] 47 | 48 | ReactDOM.render( 49 | 50 | 51 | , 52 | document.getElementById('root'), 53 | ) 54 | 55 | // If you want your app to work offline and load faster, you can change 56 | // unregister() to register() below. Note this comes with some pitfalls. 57 | // Learn more about service workers: http://bit.ly/CRA-PWA 58 | serviceWorker.unregister() 59 | -------------------------------------------------------------------------------- /src/components/navigation/NavigationBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { bool, func, instanceOf, oneOfType, shape } from 'prop-types' 3 | import { EOSIOAuthUser } from 'ual-eosio-reference-authenticator' 4 | import { ScatterUser } from 'ual-scatter' 5 | import { LynxUser } from 'ual-lynx' 6 | import { TokenPocketUser } from 'ual-token-pocket' 7 | import { withUAL } from 'ual-reactjs-renderer' 8 | import './NavigationBar.scss' 9 | 10 | import UserInfo from 'components/navigation/UserInfo' 11 | import LoginButton from 'components/navigation/LoginButton' 12 | import { onKeyUpEnter } from 'utils/keyPress' 13 | import logo from 'assets/images/logo.svg' 14 | 15 | const NavigationBar = ({ ual: { activeUser }, routeToLanding, login, enroll, enrolled }) => ( 16 |
17 |
18 |
onKeyUpEnter(event, routeToLanding)} 24 | > 25 | logo 26 |
27 |
    28 |
  • Post a Property
  • 29 | { activeUser 30 | ?
  • 31 | :
  • 32 | } 33 |
34 |
35 |
36 | ) 37 | 38 | NavigationBar.propTypes = { 39 | ual: shape({ 40 | activeUser: oneOfType([ 41 | instanceOf(EOSIOAuthUser), 42 | instanceOf(ScatterUser), 43 | instanceOf(LynxUser), 44 | instanceOf(TokenPocketUser), 45 | ]), 46 | }), 47 | routeToLanding: func.isRequired, 48 | login: func.isRequired, 49 | enroll: func.isRequired, 50 | enrolled: bool.isRequired, 51 | } 52 | 53 | NavigationBar.defaultProps = { 54 | ual: { 55 | activeUser: null, 56 | }, 57 | } 58 | 59 | export default withUAL(NavigationBar) 60 | -------------------------------------------------------------------------------- /src/assets/images/privacy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 98DDC0C7-B6EC-435F-B2BD-E0E1A8452FB7@1.00x 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/images/property.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/navigation/UserDropdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { bool, func } from 'prop-types' 3 | // UAL context object that can be set via the contextType property on a class and can be referenced using this.context 4 | import { UALContext } from 'ual-reactjs-renderer' 5 | import './UserDropdown.scss' 6 | import privacyIcon from 'assets/images/privacy.svg' 7 | import logoutIcon from 'assets/images/leave.svg' 8 | 9 | import { onKeyUpEnter } from 'utils/keyPress' 10 | 11 | class UserDropdown extends React.Component { 12 | static propTypes = { 13 | logout: func.isRequired, 14 | enroll: func.isRequired, 15 | enrolled: bool.isRequired, 16 | } 17 | 18 | static contextType = UALContext 19 | 20 | render() { 21 | const { logout, enrolled, enroll } = this.props 22 | return ( 23 |
28 |
    29 | { !enrolled 30 | ? ( 31 |
  • onKeyUpEnter(event, enroll)} 36 | > 37 | privacy 38 | Enable WebAuthn 2FA 39 |
  • 40 | ) 41 | : ( 42 |
  • 43 | privacy 44 | WebAuthn 2FA Enabled! 45 |
  • 46 | ) 47 | } 48 |
  • onKeyUpEnter(event, logout)} 53 | > 54 | 55 | Logout 56 |
  • 57 |
58 |
59 | ) 60 | } 61 | } 62 | 63 | export default UserDropdown 64 | -------------------------------------------------------------------------------- /src/assets/images/leave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3DE8CDF3-095F-4AA3-A2A5-DD2FC0158FED@1.00x 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | Tropical logo updated -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tropical-example-web-app", 3 | "version": "1.1.1", 4 | "license": "MIT", 5 | "description": "Simple example app demonstrating usage of several EOSIO Labs repositories", 6 | "author": { 7 | "name": "block.one", 8 | "url": "https://block.one" 9 | }, 10 | "contributors": [ 11 | "Justin Brown", 12 | "Cody Douglass", 13 | "Jonathan Pratt", 14 | "Ben Rush", 15 | "Tara Tritt", 16 | "Mike Manfredi" 17 | ], 18 | "private": true, 19 | "dependencies": { 20 | "@babel/core": "7.4.3", 21 | "@babel/preset-env": "7.4.3", 22 | "@babel/register": "7.4.4", 23 | "assert": "2.0.0", 24 | "base64url": "3.0.1", 25 | "bigi": "1.4.2", 26 | "bs58": "4.0.1", 27 | "cbor": "3.0.3", 28 | "create-hash": "1.2.0", 29 | "create-hmac": "1.1.7", 30 | "ecurve": "1.0.6", 31 | "elliptic": "6.5.2", 32 | "eosjs": "21.0.1-rc1", 33 | "express": "4.17.1", 34 | "focus-visible": "4.1.5", 35 | "prop-types": "15.7.2", 36 | "randombytes": "2.1.0", 37 | "react": "16.8.6", 38 | "react-dom": "16.8.6", 39 | "react-scripts": "3.2.0", 40 | "ual-eosio-reference-authenticator": "0.1.3", 41 | "ual-lynx": "0.2.2", 42 | "ual-reactjs-renderer": "0.1.7", 43 | "ual-scatter": "0.1.8", 44 | "ual-token-pocket": "0.1.2" 45 | }, 46 | "resolutions": { 47 | "serialize-javascript": "^2.1.1", 48 | "eosjs": "21.0.1-rc1" 49 | }, 50 | "scripts": { 51 | "start": "node scripts/copyDefaultEnv.js && HTTPS=true react-scripts start", 52 | "startInGitpod": "node scripts/copyDefaultEnv.js && REACT_APP_IS_GITPOD=true react-scripts start", 53 | "build": "react-scripts build", 54 | "up": "docker-compose up", 55 | "lint": "eslint \"src/**/*.js{,?}\"", 56 | "test": "react-scripts test", 57 | "eject": "react-scripts eject" 58 | }, 59 | "eslintConfig": { 60 | "extends": "react-app" 61 | }, 62 | "browserslist": [ 63 | ">0.2%", 64 | "not dead", 65 | "not ie <= 11", 66 | "not op_mini all" 67 | ], 68 | "devDependencies": { 69 | "@blockone/eslint-config-blockone": "^1.1.1", 70 | "@testing-library/react": "^9.4.0", 71 | "@types/elliptic": "^6.4.6", 72 | "eslint-plugin-no-exclusive-tests": "^1.0.0-rc.1", 73 | "node-sass": "^4.10.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /eosio/contracts/tropical/tropical.contracts.md: -------------------------------------------------------------------------------- 1 |

2 | like 3 |

4 | --- 5 | spec_version: 0.2.0 6 | title: Like a Property 7 | summary: Like a Tropical Example property 8 | icon: https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Love_Heart_SVG.svg/265px-Love_Heart_SVG.svg.png#301991F0C25EE8EAA55F4CE940B9E5910560652A8D930C56D65C3C8987FF7DDB 9 | --- 10 | Liking this property will be visible to anyone who views your profile or searches your name. 11 | This like may result in the post owner’s property to be featured on the property owner’s most liked pages. 12 | Any usage of bots, macros, or any autonomous form of liking a specific person’s property would result in the investigation of like legitimacy for a post. 13 |

14 | rent 15 |

16 | --- 17 | spec_version: 0.2.0 18 | title: Rent a Property 19 | summary: Rent a Tropical Example property 20 | icon: https://https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Dollar_sign_in_circle.svg/240px-Dollar_sign_in_circle.svg.png#27F7CC5F628A8FC0B16680C141D58D42A1F49E3E38B1A4F67590C61F5D607CBF 21 | --- 22 | Renting a property requires 2FA and constitutes a binding agreement to pay the listed rental fee(s). 23 | Any usage of bots, macros, or any autonomous form of liking a specific person’s property would result in the investigation of like legitimacy for a post. 24 |

25 | check2fa 26 |

27 | --- 28 | spec_version: 0.2.0 29 | title: Validate the 2FA Token 30 | summary: Validate a WebAuthn second factor 31 | icon: https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4/OOjs_UI_icon_key-ltr.svg/200px-OOjs_UI_icon_key-ltr.svg.png#0A1E2435DCAFDF4B34E60C65C570DCE30FAAC1862D7C5E139D188A6EAAF95014 32 | --- 33 | Any usage of bots, macros, or any autonomous form of liking a specific person’s property would result in the investigation of like legitimacy for a post. 34 |

35 | setsrvkey 36 |

37 | --- 38 | spec_version: 0.2.0 39 | title: Set the Root-of-trust 40 | summary: This is an administrative action that sets the respected "root of trust" key that counter signs 2FA 41 | icon: https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4/OOjs_UI_icon_key-ltr.svg/200px-OOjs_UI_icon_key-ltr.svg.png#0A1E2435DCAFDF4B34E60C65C570DCE30FAAC1862D7C5E139D188A6EAAF95014 42 | --- 43 | Any usage of bots, macros, or any autonomous form of liking a specific person’s property would result in the investigation of like legitimacy for a post. 44 | -------------------------------------------------------------------------------- /src/components/navigation/UserInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { UALContext } from 'ual-reactjs-renderer' 3 | import './UserInfo.scss' 4 | 5 | import UserDropdown from 'components/navigation/UserDropdown' 6 | import downArrow from 'assets/images/down-arrow.svg' 7 | import upArrow from 'assets/images/up-arrow.svg' 8 | import { onKeyUpEnter } from 'utils/keyPress' 9 | 10 | class UserInfo extends React.Component { 11 | _isMounted = false 12 | 13 | state = { 14 | showDropdown: false, 15 | accountName: '', 16 | } 17 | 18 | async componentDidMount() { 19 | this._isMounted = true 20 | const { activeUser } = this.context 21 | if (activeUser) { 22 | const accountName = await activeUser.getAccountName() 23 | if (this._isMounted) { 24 | this.setState({ accountName }) 25 | } 26 | } 27 | } 28 | 29 | componentWillUnmount() { 30 | this._isMounted = false 31 | } 32 | 33 | toggleDropdown = () => { 34 | this.setState(prevState => ({ 35 | showDropdown: !prevState.showDropdown, 36 | })) 37 | } 38 | 39 | static contextType = UALContext 40 | 41 | renderLogout = (enroll, enrolled) => ( 42 | 43 |
onKeyUpEnter(event, this.toggleDropdown)} 49 | > 50 | dropdown 51 |
52 | { this.state.showDropdown && this.renderDropdown(enroll, enrolled) } 53 |
54 | ) 55 | 56 | renderDropdown = (enroll, enrolled) => { 57 | const { logout } = this.context 58 | return ( 59 |
60 | 61 |
62 | ) 63 | } 64 | 65 | render() { 66 | const { logout, isAutoLogin } = this.context 67 | const { accountName } = this.state 68 | const { enroll, enrolled } = this.props 69 | const shouldDisplayLogout = logout && !isAutoLogin 70 | return ( 71 |
72 | Signed in as 73 |
{accountName}
74 | { shouldDisplayLogout && this.renderLogout(enroll, enrolled) } 75 |
76 | ) 77 | } 78 | } 79 | 80 | export default UserInfo 81 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import { Router, json } from 'express' 2 | import { ec as EC } from 'elliptic' 3 | import { Serialize, Numeric } from 'eosjs' 4 | import { PrivateKey, PublicKey, Signature } from 'eosjs/dist/eosjs-jssig' 5 | import { KeyType } from 'eosjs/dist/eosjs-numeric' 6 | import base64url from 'base64url' 7 | import cbor from 'cbor' 8 | import util from 'util' 9 | 10 | export default () => { 11 | const ec = new EC('secp256k1') 12 | const privateKeyWif = process.env.API_SERVER_PRIVATE_KEY 13 | const api = Router() 14 | 15 | const decodeWebauthnPublicKey = (webauthnPublicKey, hostname) => { 16 | const attestationBuffer = base64url.toBuffer(webauthnPublicKey.attestationObject) 17 | const attestation = cbor.decodeFirstSync(attestationBuffer) 18 | const authdata = attestation.authData 19 | const flags = authdata.readUInt8(32) 20 | const credentialIDLength = authdata.readUInt16BE(53) 21 | const credentialID = authdata.slice(55, 55 + credentialIDLength) 22 | const COSEPublicKeyBuffer = authdata.slice(55 + credentialIDLength) 23 | const COSEPublicKey = cbor.decodeFirstSync(COSEPublicKeyBuffer) 24 | 25 | const x = COSEPublicKey.get(-2) 26 | const y = COSEPublicKey.get(-3) 27 | 28 | const rpId = hostname 29 | const presence = ((flags) => { 30 | if (flags & 0x04) return 2 31 | if (flags & 0x01) return 1 32 | return 0 33 | })(flags) 34 | 35 | const ser = new Serialize.SerialBuffer({ textEncoder: new util.TextEncoder(), textDecoder: new util.TextDecoder() }) 36 | ser.push(2) 37 | ser.push((y[31] & 1) ? 3 : 2) 38 | ser.pushArray(x) 39 | ser.push(presence) 40 | ser.pushString(rpId) 41 | const eosioPubkey = ser.asUint8Array() 42 | 43 | return { eosioPubkey, credentialID } 44 | } 45 | 46 | const users = {} 47 | 48 | api.post('/generateRentChallenge', json(), (req, resp) => { 49 | const name = req.body.accountName 50 | const { propertyName } = req.body 51 | const namePairBuffer = 52 | new Serialize.SerialBuffer({ textEncoder: new util.TextEncoder(), textDecoder: new util.TextDecoder() }) 53 | namePairBuffer.pushName(name) 54 | namePairBuffer.pushName(propertyName) 55 | 56 | const sigData = Buffer.concat([namePairBuffer.asUint8Array(), users[name].eosioPubkey]) 57 | const sigDigest = Buffer.from(ec.hash().update(sigData).digest()) 58 | 59 | const kPrivElliptic = PrivateKey.fromString(privateKeyWif).toElliptic(ec) 60 | const ellipticSignature = kPrivElliptic.sign(sigDigest) 61 | const signature = Signature.fromElliptic(ellipticSignature, KeyType.k1).toString() 62 | 63 | const userKey = Numeric.publicKeyToString({ 64 | type: Numeric.KeyType.wa, 65 | data: users[name].eosioPubkey.slice(1), 66 | }) 67 | const serverKey = PublicKey.fromElliptic(kPrivElliptic, KeyType.k1).toString() 68 | const credentialIDStr = base64url.encode(users[name].credentialID) 69 | 70 | resp.json({ 71 | status: 'ok', 72 | userKey, 73 | serverKey, 74 | serverAuth: signature, 75 | credentialID: credentialIDStr, 76 | }) 77 | }) 78 | 79 | api.post('/enroll', json(), (req, resp) => { 80 | const { accountName, hostname, webauthnPublicKey } = req.body 81 | users[accountName] = decodeWebauthnPublicKey(webauthnPublicKey, hostname) 82 | resp.json({ status: 'ok' }) 83 | }) 84 | 85 | return api 86 | } 87 | -------------------------------------------------------------------------------- /src/assets/images/money-bag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14536D85-CEDC-4C6C-8B03-BC3107F83C9F@1.00x 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func, shape, instanceOf } from 'prop-types' 3 | // HOC (Higher Order Component) to pass the UALProvider context to the component 4 | import { withUAL } from 'ual-reactjs-renderer' 5 | import 'App.scss' 6 | 7 | import NavigationBar from 'components/navigation/NavigationBar' 8 | import NotificationBar from 'components/notification/NotificationBar' 9 | import ResultsPage from 'components/results/ResultsPage' 10 | import LandingPage from 'components/landing/LandingPage' 11 | 12 | import { generateWebauthnPubkey, enrollWebauthnPubkey } from 'utils/webauthn' 13 | 14 | class App extends React.Component { 15 | static propTypes = { 16 | ual: shape({ 17 | error: instanceOf(Error), 18 | logout: func, 19 | showModal: func.isRequired, 20 | hideModal: func.isRequired, 21 | }), 22 | } 23 | 24 | static defaultProps = { 25 | ual: { 26 | error: null, 27 | logout: () => {}, 28 | }, 29 | } 30 | 31 | state = { 32 | showResults: false, 33 | showNotificationBar: true, 34 | error: null, 35 | enrolled: false, 36 | } 37 | 38 | componentDidUpdate(prevProps) { 39 | // Via withUAL() below, access to the error object is now available 40 | // This error object will be set in the event of an error during any UAL execution 41 | const { ual: { error } } = this.props 42 | const { ual: { error: prevError } } = prevProps 43 | if (error && (prevError ? error.message !== prevError.message : true)) { 44 | // UAL modal will display the error message to the user, so no need to render this error in the app 45 | console.error('UAL Error', JSON.parse(JSON.stringify(error))) 46 | } 47 | } 48 | 49 | displayResults = display => this.setState({ showResults: display }) 50 | 51 | displayNotificationBar = display => this.setState({ showNotificationBar: display }) 52 | 53 | displayLoginModal = (display) => { 54 | // Via withUAL() below, access to the showModal & hideModal functions are now available 55 | const { ual: { showModal, hideModal } } = this.props 56 | if (display) { 57 | showModal() 58 | } else { 59 | hideModal() 60 | } 61 | } 62 | 63 | displayError = (error) => { 64 | if (error.source) { 65 | console.error('UAL Error', JSON.parse(JSON.stringify(error))) 66 | } 67 | this.setState({ error }) 68 | this.displayNotificationBar(true) 69 | } 70 | 71 | clearError = () => { 72 | this.setState({ error: null }) 73 | this.displayNotificationBar(false) 74 | } 75 | 76 | enroll = async () => { 77 | console.info('enroll().top') 78 | // Via static contextType = UALContext, access to the activeUser object on this.context is now available 79 | const { ual: { activeUser } } = this.props 80 | if (activeUser) { 81 | try { 82 | const accountName = await activeUser.getAccountName() 83 | const pubkey = await generateWebauthnPubkey(accountName) 84 | console.info('accountName:', accountName) 85 | console.info('pubkey:', pubkey) 86 | await enrollWebauthnPubkey(accountName, pubkey) 87 | this.setState({ enrolled: true }) 88 | } catch (err) { 89 | this.displayError(err) 90 | } 91 | } else { 92 | this.displayError(new Error('Not Logged In!')) 93 | } 94 | } 95 | 96 | render() { 97 | const login = () => this.displayLoginModal(true) 98 | const routeToResults = () => this.displayResults(true) 99 | const routeToLanding = () => this.displayResults(false) 100 | const hideNotificationBar = () => this.clearError() 101 | const { showResults, showNotificationBar, error, enrolled } = this.state 102 | 103 | return ( 104 |
105 | { showNotificationBar && } 106 | 107 | { showResults 108 | ? ( 109 | 115 | ) 116 | : 117 | } 118 |
119 | ) 120 | } 121 | } 122 | 123 | // Passes down the context via props to the wrapped component 124 | export default withUAL(App) 125 | -------------------------------------------------------------------------------- /src/components/results/property/Property.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { func, bool } from 'prop-types' 3 | // UAL context object that can be set via the contextType property on a class and can be referenced using this.context 4 | import { UALContext } from 'ual-reactjs-renderer' 5 | import './Property.scss' 6 | 7 | import PropertyImage from 'components/results/property/PropertyImage' 8 | import { generateLikeTransaction, generateRentTransaction, transactionConfig } from 'utils/transaction' 9 | import { generateRentChallenge, signRentChallenge, canUseWebAuthN } from 'utils/webauthn' 10 | import { onKeyUpEnter } from 'utils/keyPress' 11 | 12 | import likeSvg from 'assets/images/heart/heart.svg' 13 | import rentSvg from 'assets/images/money-bag.svg' 14 | import rentingSvg from 'assets/images/renting.svg' 15 | 16 | class Property extends React.Component { 17 | static propTypes = { 18 | login: func.isRequired, 19 | displayError: func.isRequired, 20 | enrolled: bool.isRequired, 21 | } 22 | 23 | state = { 24 | loading: false, 25 | liked: false, 26 | rented: false, 27 | canRent: canUseWebAuthN(), 28 | } 29 | 30 | onLike = async () => { 31 | const { login, displayError } = this.props 32 | // Via static contextType = UALContext, access to the activeUser object on this.context is now available 33 | const { activeUser } = this.context 34 | if (activeUser) { 35 | this.setState({ loading: true }) 36 | try { 37 | const accountName = await activeUser.getAccountName() 38 | const transaction = generateLikeTransaction(accountName) 39 | // The activeUser.signTransaction will propose the passed in transaction to the logged in Authenticator 40 | await activeUser.signTransaction(transaction, transactionConfig) 41 | this.setState({ liked: true }) 42 | } catch (err) { 43 | displayError(err) 44 | } 45 | this.setState({ loading: false }) 46 | } else { 47 | login() 48 | } 49 | } 50 | 51 | onRent = async () => { 52 | const { login, displayError } = this.props 53 | const { activeUser } = this.context 54 | if (activeUser) { 55 | if (!this.state.canRent) { 56 | displayError(new Error('HTTPS is required to use 2FA.')) 57 | return 58 | } 59 | if (!this.props.enrolled) { 60 | displayError( 61 | new Error('No 2FA enrolled 2FA: Please enroll in 2FA (under Login/Profile menu at the top right) to Rent.'), 62 | ) 63 | return 64 | } 65 | this.setState({ loading: true }) 66 | try { 67 | const accountName = await activeUser.getAccountName() 68 | const rentChallenge = await generateRentChallenge(accountName, 'aproperty') 69 | const userAuth = await signRentChallenge(accountName, 'aproperty', rentChallenge) 70 | const transaction = generateRentTransaction( 71 | accountName, 72 | 'aproperty', 73 | rentChallenge.serverKey, 74 | rentChallenge.userKey, 75 | rentChallenge.serverAuth, 76 | userAuth, 77 | ) 78 | // The activeUser.signTransaction will propose the passed in transaction to the logged in Authenticator 79 | await activeUser.signTransaction(transaction, transactionConfig) 80 | this.setState({ rented: true }) 81 | } catch (err) { 82 | displayError(err) 83 | } 84 | this.setState({ loading: false }) 85 | } else { 86 | login() 87 | } 88 | } 89 | 90 | static contextType = UALContext 91 | 92 | render() { 93 | const { loading, liked, rented } = this.state 94 | 95 | return ( 96 |
97 | 98 |
99 |
100 |
101 | onKeyUpEnter(event, this.onRent)} 108 | > 109 | rent 110 | {rented && !loading ? 'Renting' : 'Rent'} 111 | 112 | onKeyUpEnter(event, this.onLike)} 119 | > 120 | like 121 | {liked && !loading ? 'Liked' : 'Like'} 122 | 123 |
124 |
125 | ) 126 | } 127 | } 128 | 129 | export default Property 130 | -------------------------------------------------------------------------------- /eosio/contracts/tropical/tropical.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace eosio; 7 | 8 | CONTRACT tropical : public contract { 9 | public: 10 | tropical(name self, name first_receiver, datastream ds) 11 | :contract(self, first_receiver, ds) 12 | ,configuration_singleton(get_self(), get_self().value) 13 | {} 14 | 15 | ACTION like( name user ) { 16 | print_f("You've liked a property on chain, %!\n", user); 17 | } 18 | 19 | /** 20 | * Global singleton that holds the current "root of trust" 21 | */ 22 | TABLE config { 23 | public_key srvkey; 24 | 25 | EOSLIB_SERIALIZE( config, (srvkey) ) 26 | }; 27 | 28 | eosio::singleton< "config"_n, config > configuration_singleton; 29 | 30 | /** 31 | * 32 | * @param user 33 | * @param property 34 | * @return 35 | */ 36 | ACTION rent( name user, name property ) { 37 | // enforce that the check2fa action is the first CFA 38 | // 39 | auto check2fa_action = get_action(0, 0); 40 | 41 | // unpack the first two parameters 42 | // 43 | auto second_factor_params = unpack>(check2fa_action.data); 44 | 45 | // validate that the 2FA was properly sent to this contract 46 | // 47 | check(check2fa_action.account == _self, "Malformed 2FA action, wrong account"); 48 | 49 | // validate that the 2FA was propertly sent to the `check2fa` context-free action handler 50 | // 51 | check(check2fa_action.name == "check2fa"_n, "Malfomed 2FA action, wrong name"); 52 | 53 | // validate that the 2FA was for this user and property 54 | // 55 | check(std::get<0>(second_factor_params) == user, "Malformed 2FA action, wrong user"); 56 | check(std::get<1>(second_factor_params) == property, "Malfomed 2FA action, wrong property"); 57 | 58 | // finally validate that the root of trust, the server_key, matches the chain state 59 | // this was not possible in a context free action 60 | // 61 | auto server_key = configuration_singleton.get().srvkey; 62 | check(std::get<2>(second_factor_params) == server_key, "Malfomed 2FA action, wrong root of trust"); 63 | 64 | print_f("You've rented a % on chain, %!\n", property, user); 65 | } 66 | 67 | /** 68 | * Validate that a provided pair of signatures represents the provided user and property names as well as 69 | * a chain of trust for a user_key that terminates in a server_key 70 | * 71 | * This is a context-free action. This means it cannot access any chain state. It can only enforce the 72 | * consistency of the parameters passed to it. This implies that this action will succeed as long as 73 | * the user, property, and user_key are attested to by the server_key via various signatures. 74 | * 75 | * it *does not* validate the server_key 76 | * 77 | * @param user - the name of the user present in this 2fa assertion 78 | * @param property - the name of the property present in this 2fa assertion 79 | * @param server_key - the public key that is the root of trust for this assertion 80 | * @param user_key - a public key, trusted by `server_key`, to be in possessed by `user` 81 | * @param server_auth - a signature from the `server_key` 82 | * @param bearer_auth - a signature from the `user_key` 83 | */ 84 | ACTION check2fa( name user, name property, public_key server_key, public_key user_key, signature server_auth, signature bearer_auth ) { 85 | // concatenate the serialized user name, property name, and user public key 86 | // as the "challenge" that the server would have signed 87 | // 88 | auto challenge = pack(std::forward_as_tuple(user, property, user_key)); 89 | 90 | // hash the "challenge" into a signature digest that both the server and the user's WebAuthn authenticator 91 | // will sign in order to prove to the chain that there was a valid second factor ceremony 92 | // 93 | auto signature_digest = sha256(challenge.data(), challenge.size()); 94 | 95 | // verify the provided signature from the server, this is something only an entity in possession of the 96 | // private `server_key` can have properly generated 97 | // 98 | assert_recover_key(signature_digest, server_auth, server_key); 99 | 100 | // verify the provided signature from the bearer, this is something only an entity in possession of the 101 | // private `user_key` can have properly generated and the `user_key` is attested to by the `server_key` 102 | // via the challenge digest 103 | // 104 | assert_recover_key(signature_digest, bearer_auth, user_key); 105 | } 106 | 107 | /** 108 | * Administrative action to set the root of trust, aka server key 109 | * 110 | * @param server_key - the public key that is the root of trust for this contract 111 | */ 112 | ACTION setsrvkey(public_key server_key) { 113 | require_auth(_self); 114 | configuration_singleton.set({server_key}, _self); 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /IMPORTANT.md: -------------------------------------------------------------------------------- 1 | # Important Notice 2 | 3 | We (block.one and its affiliates) make available EOSIO and other software, updates, patches and documentation (collectively, Software) on a voluntary basis as a member of the EOSIO community. A condition of you accessing any Software, websites, articles, media, publications, documents or other material (collectively, Material) is your acceptance of the terms of this important notice. 4 | 5 | ## Software 6 | We are not responsible for ensuring the overall performance of Software or any related applications. Any test results or performance figures are indicative and will not reflect performance under all conditions. Software may contain components that are open sourced and subject to their own licenses; you are responsible for ensuring your compliance with those licenses. 7 | 8 | We make no representation, warranty, guarantee or undertaking in respect of Software, whether expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall we be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. 9 | 10 | Wallets and related components are complex software that require the highest levels of security. If incorrectly built or used, they may compromise users’ private keys and digital assets. Wallet applications and related components should undergo thorough security evaluations before being used. Only experienced developers should work with such Software. 11 | 12 | Material is not made available to any person or entity that is the subject of sanctions administered or enforced by any country or government or otherwise designated on any list of prohibited or restricted parties (including but not limited to the lists maintained by the United Nations Security Council, the U.S. Government, the European Union or its Member States, or other applicable government authority) or organized or resident in a country or territory that is the subject of country-wide or territory-wide sanctions. You represent and warrant that neither you nor any party having a direct or indirect beneficial interest in you or on whose behalf you are acting as agent or nominee is such a person or entity and you will comply with all applicable import, re-import, sanctions, anti-boycott, export, and re-export control laws and regulations. If this is not accurate or you do not agree, then you must immediately cease accessing our Material and delete all copies of Software. 13 | 14 | Any person using or offering Software in connection with providing software, goods or services to third parties shall advise such third parties of this important notice, including all limitations, restrictions and exclusions of liability. 15 | 16 | ## Trademarks 17 | Block.one, EOSIO, EOS, the heptahedron and associated logos and related marks are our trademarks. Other trademarks referenced in Material are the property of their respective owners. 18 | 19 | ## Third parties 20 | Any reference in Material to any third party or third-party product, resource or service is not an endorsement or recommendation by Block.one. We are not responsible for, and disclaim any and all responsibility and liability for, your use of or reliance on any of these resources. Third-party resources may be updated, changed or terminated at any time, so information in Material may be out of date or inaccurate. 21 | 22 | ## Forward-looking statements 23 | Please note that in making statements expressing Block.one’s vision, we do not guarantee anything, and all aspects of our vision are subject to change at any time and in all respects at Block.one’s sole discretion, with or without notice. We call these “forward-looking statements”, which includes statements on our website and in other Material, other than statements of historical facts, such as statements regarding EOSIO’s development, expected performance, and future features, or our business strategy, plans, prospects, developments and objectives. These statements are only predictions and reflect Block.one’s current beliefs and expectations with respect to future events; they are based on assumptions and are subject to risk, uncertainties and change at any time. 24 | 25 | We operate in a rapidly changing environment and new risks emerge from time to time. Given these risks and uncertainties, you are cautioned not to rely on these forward-looking statements. Actual results, performance or events may differ materially from what is predicted in the forward-looking statements. Some of the factors that could cause actual results, performance or events to differ materially from the forward-looking statements include, without limitation: technical feasibility and barriers; market trends and volatility; continued availability of capital, financing and personnel; product acceptance; the commercial success of any new products or technologies; competition; government regulation and laws; and general economic, market or business conditions. 26 | 27 | All statements are valid only as of the date of first posting and Block.one is under no obligation to, and expressly disclaims any obligation to, update or alter any statements, whether as a result of new information, subsequent events or otherwise. Nothing in any Material constitutes technological, financial, investment, legal or other advice, either in general or with regard to any particular situation or implementation. Please consult with experts in appropriate areas before implementing or utilizing anything contained in Material. 28 | -------------------------------------------------------------------------------- /src/utils/webauthn.js: -------------------------------------------------------------------------------- 1 | import base64url from 'base64url' 2 | import { Serialize, Numeric } from 'eosjs' 3 | import { ec as EC } from 'elliptic' 4 | 5 | // taken from the 6 | const formatWebauthnPubkey = (pubkey) => { 7 | const clientDataStr = String.fromCharCode.apply(null, new Uint8Array(pubkey.clientDataJSON)) 8 | return { 9 | attestationObject: base64url.encode(pubkey.attestationObject), 10 | clientData: JSON.parse(clientDataStr), 11 | } 12 | } 13 | 14 | const decodeWebauthnSignature = (assertion, key) => { 15 | const e = new EC('p256') 16 | const fixup = (x) => { 17 | const a = Array.from(x) 18 | while (a.length < 32) a.unshift(0) 19 | while (a.length > 32) if (a.shift() !== 0) throw new Error('Signature has an r or s that is too big') 20 | return new Uint8Array(a) 21 | } 22 | 23 | const der = new Serialize.SerialBuffer({ array: new Uint8Array(assertion.signature) }) 24 | if (der.get() !== 0x30) throw new Error('Signature missing DER prefix') 25 | if (der.get() !== der.array.length - 2) throw new Error('Signature has bad length') 26 | if (der.get() !== 0x02) throw new Error('Signature has bad r marker') 27 | const r = fixup(der.getUint8Array(der.get())) 28 | if (der.get() !== 0x02) throw new Error('Signature has bad s marker') 29 | const s = fixup(der.getUint8Array(der.get())) 30 | 31 | const pubkeyData = Numeric.stringToPublicKey(key).data.subarray(0, 33) 32 | const pubKey = e.keyFromPublic(pubkeyData).getPublic() 33 | const signedData = Buffer.concat([ 34 | Buffer.from(assertion.authenticatorData), 35 | Buffer.from(e.hash().update(Buffer.from(assertion.clientDataJSON)).digest()), 36 | ]) 37 | const hash = Buffer.from(e.hash().update(signedData).digest()) 38 | const recid = e.getKeyRecoveryParam(hash, Buffer.from(assertion.signature), pubKey) 39 | 40 | const sigData = new Serialize.SerialBuffer() 41 | sigData.push(recid + 27 + 4) 42 | sigData.pushArray(r) 43 | sigData.pushArray(s) 44 | sigData.pushBytes(new Uint8Array(assertion.authenticatorData)) 45 | sigData.pushBytes(new Uint8Array(assertion.clientDataJSON)) 46 | 47 | const sig = Numeric.signatureToString({ 48 | type: Numeric.KeyType.wa, 49 | data: sigData.asUint8Array().slice(), 50 | }) 51 | 52 | return sig 53 | } 54 | 55 | export const generateWebauthnPubkey = async (accountName) => { 56 | const createCredentialOptions = { 57 | // Format of new credentials is publicKey 58 | publicKey: { 59 | // Relying Party 60 | rp: { 61 | name: 'Tropical Stay', 62 | id: window.location.hostname, 63 | }, 64 | // Cryptographic challenge from the server 65 | challenge: new Uint8Array(26), 66 | // User 67 | user: { 68 | id: new Uint8Array(16), 69 | name: accountName, 70 | displayName: accountName, 71 | }, 72 | // Requested format of new keypair 73 | pubKeyCredParams: [{ 74 | type: 'public-key', 75 | alg: -7, 76 | }], 77 | timeout: 60000, 78 | attestation: 'direct', 79 | }, 80 | } 81 | 82 | const webauthnResp = await navigator.credentials.create(createCredentialOptions) 83 | return formatWebauthnPubkey(webauthnResp.response) 84 | } 85 | 86 | export const enrollWebauthnPubkey = async (accountName, webauthnPublicKey) => { 87 | const payload = { 88 | accountName, 89 | webauthnPublicKey, 90 | hostname: window.location.hostname, 91 | } 92 | 93 | const enrollResponse = await fetch('/api/enroll', { 94 | method: 'POST', 95 | headers: { 96 | 'Content-Type': 'application/json', 97 | }, 98 | body: JSON.stringify(payload), 99 | }) 100 | 101 | const enrollResult = await enrollResponse.json() 102 | if (!enrollResult.status || enrollResult.status !== 'ok') { 103 | throw new Error('Enrollment failed') 104 | } 105 | } 106 | 107 | export const generateRentChallenge = async (accountName, propertyName) => { 108 | const payload = { 109 | accountName, 110 | propertyName, 111 | } 112 | 113 | const resp = await fetch('/api/generateRentChallenge', { 114 | method: 'POST', 115 | headers: { 116 | 'Content-Type': 'application/json', 117 | }, 118 | body: JSON.stringify(payload), 119 | }) 120 | 121 | const result = await resp.json() 122 | if (!result.status || result.status !== 'ok') { 123 | throw new Error('Enrollment failed') 124 | } 125 | 126 | return result 127 | } 128 | 129 | export const signRentChallenge = async (accountName, propertyName, challenge) => { 130 | const e = new EC('p256') 131 | const challengeBuffer = new Serialize.SerialBuffer() 132 | challengeBuffer.pushName(accountName) 133 | challengeBuffer.pushName(propertyName) 134 | challengeBuffer.pushPublicKey(challenge.userKey) 135 | const sigData = challengeBuffer.asUint8Array() 136 | // const sigDigest = Buffer.from(ecc.sha256(sigData), 'hex') 137 | const sigDigest = Buffer.from(e.hash().update(sigData).digest()) 138 | const getCredentialOptions = { 139 | publicKey: { 140 | timeout: 60000, 141 | allowCredentials: [{ 142 | id: base64url.toBuffer(challenge.credentialID), 143 | type: 'public-key', 144 | }], 145 | challenge: sigDigest, 146 | }, 147 | } 148 | 149 | const webauthnResp = await navigator.credentials.get(getCredentialOptions) 150 | return decodeWebauthnSignature(webauthnResp.response, challenge.userKey) 151 | } 152 | 153 | export const canUseWebAuthN = () => window.location.protocol.replace(/:$/, '') === 'https' 154 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /eosio/contracts/eosio.bios-v1.8.3/eosio.bios.contracts.md: -------------------------------------------------------------------------------- 1 |

activate

2 | 3 | --- 4 | spec_version: "0.2.0" 5 | title: Activate Protocol Feature 6 | summary: 'Activate protocol feature {{nowrap feature_digest}}' 7 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e 8 | --- 9 | 10 | {{$action.account}} activates the protocol feature with a digest of {{feature_digest}}. 11 | 12 |

canceldelay

13 | 14 | --- 15 | spec_version: "0.2.0" 16 | title: Cancel Delayed Transaction 17 | summary: '{{nowrap canceling_auth.actor}} cancels a delayed transaction' 18 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 19 | --- 20 | 21 | {{canceling_auth.actor}} cancels the delayed transaction with id {{trx_id}}. 22 | 23 |

deleteauth

24 | 25 | --- 26 | spec_version: "0.2.0" 27 | title: Delete Account Permission 28 | summary: 'Delete the {{nowrap permission}} permission of {{nowrap account}}' 29 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 30 | --- 31 | 32 | Delete the {{permission}} permission of {{account}}. 33 | 34 |

linkauth

35 | 36 | --- 37 | spec_version: "0.2.0" 38 | title: Link Action to Permission 39 | summary: '{{nowrap account}} sets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract to {{nowrap requirement}}' 40 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 41 | --- 42 | 43 | {{account}} sets the minimum required permission for the {{#if type}}{{type}} action of the{{/if}} {{code}} contract to {{requirement}}. 44 | 45 | {{#if type}}{{else}}Any links explicitly associated to specific actions of {{code}} will take precedence.{{/if}} 46 | 47 |

newaccount

48 | 49 | --- 50 | spec_version: "0.2.0" 51 | title: Create New Account 52 | summary: '{{nowrap creator}} creates a new account with the name {{nowrap name}}' 53 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 54 | --- 55 | 56 | {{creator}} creates a new account with the name {{name}} and the following permissions: 57 | 58 | owner permission with authority: 59 | {{to_json owner}} 60 | 61 | active permission with authority: 62 | {{to_json active}} 63 | 64 |

reqactivated

65 | 66 | --- 67 | spec_version: "0.2.0" 68 | title: Assert Protocol Feature Activation 69 | summary: 'Assert that protocol feature {{nowrap feature_digest}} has been activated' 70 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e 71 | --- 72 | 73 | Assert that the protocol feature with a digest of {{feature_digest}} has been activated. 74 | 75 |

reqauth

76 | 77 | --- 78 | spec_version: "0.2.0" 79 | title: Assert Authorization 80 | summary: 'Assert that authorization by {{nowrap from}} is provided' 81 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 82 | --- 83 | 84 | Assert that authorization by {{from}} is provided. 85 | 86 |

setabi

87 | 88 | --- 89 | spec_version: "0.2.0" 90 | title: Deploy Contract ABI 91 | summary: 'Deploy contract ABI on account {{nowrap account}}' 92 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 93 | --- 94 | 95 | Deploy the ABI file associated with the contract on account {{account}}. 96 | 97 |

setalimits

98 | 99 | --- 100 | spec_version: "0.2.0" 101 | title: Adjust Resource Limits of Account 102 | summary: 'Adjust resource limits of account {{nowrap account}}' 103 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e 104 | --- 105 | 106 | {{$action.account}} updates {{account}}’s resource limits to have a RAM quota of {{ram_bytes}} bytes, a NET bandwidth quota of {{net_weight}} and a CPU bandwidth quota of {{cpu_weight}}. 107 | 108 |

setcode

109 | 110 | --- 111 | spec_version: "0.2.0" 112 | title: Deploy Contract Code 113 | summary: 'Deploy contract code on account {{nowrap account}}' 114 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 115 | --- 116 | 117 | Deploy compiled contract code to the account {{account}}. 118 | 119 |

setparams

120 | 121 | --- 122 | spec_version: "0.2.0" 123 | title: Set System Parameters 124 | summary: 'Set system parameters' 125 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e 126 | --- 127 | 128 | {{$action.account}} sets system parameters to: 129 | {{to_json params}} 130 | 131 |

setpriv

132 | 133 | --- 134 | spec_version: "0.2.0" 135 | title: Make an Account Privileged or Unprivileged 136 | summary: '{{#if is_priv}}Make {{nowrap account}} privileged{{else}}Remove privileged status of {{nowrap account}}{{/if}}' 137 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e 138 | --- 139 | 140 | {{#if is_priv}} 141 | {{$action.account}} makes {{account}} privileged. 142 | {{else}} 143 | {{$action.account}} removes privileged status of {{account}}. 144 | {{/if}} 145 | 146 |

setprods

147 | 148 | --- 149 | spec_version: "0.2.0" 150 | title: Set Block Producers 151 | summary: 'Set block producer schedule' 152 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e 153 | --- 154 | 155 | {{$action.account}} proposes a block producer schedule of: 156 | {{#each schedule}} 157 | 1. {{this.producer_name}} with a block signing key of {{this.block_signing_key}} 158 | {{/each}} 159 | 160 |

unlinkauth

161 | 162 | --- 163 | spec_version: "0.2.0" 164 | title: Unlink Action from Permission 165 | summary: '{{nowrap account}} unsets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract' 166 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 167 | --- 168 | 169 | {{account}} removes the association between the {{#if type}}{{type}} action of the{{/if}} {{code}} contract and its minimum required permission. 170 | 171 | {{#if type}}{{else}}This will not remove any links explicitly associated to specific actions of {{code}}.{{/if}} 172 | 173 |

updateauth

174 | 175 | --- 176 | spec_version: "0.2.0" 177 | title: Modify Account Permission 178 | summary: 'Add or update the {{nowrap permission}} permission of {{nowrap account}}' 179 | icon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f 180 | --- 181 | 182 | Modify, and create if necessary, the {{permission}} permission of {{account}} to have a parent permission of {{parent}} and the following authority: 183 | {{to_json auth}} 184 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Tropical Example 2 | 3 | Interested in contributing? That's awesome! Here are some guidelines to get started quickly and easily: 4 | 5 | - [Contributing to Tropical Example](#contributing-to-tropical-example) 6 | - [Reporting An Issue](#reporting-an-issue) 7 | - [Bug Reports](#bug-reports) 8 | - [Feature Requests](#feature-requests) 9 | - [Change Requests](#change-requests) 10 | - [Working on Tropical Example](#working-on-tropical-example) 11 | - [Feature Branches](#feature-branches) 12 | - [Submitting Pull Requests](#submitting-pull-requests) 13 | - [Testing and Quality Assurance](#testing-and-quality-assurance) 14 | - [Conduct](#conduct) 15 | - [Contributor License & Acknowledgments](#contributor-license--acknowledgments) 16 | - [References](#references) 17 | 18 | ## Reporting An Issue 19 | 20 | If you're about to raise an issue because you think you've found a problem with Tropical Example, or you'd like to make a request for a new feature in the codebase, or any other reason… please read this first. 21 | 22 | The GitHub issue tracker is the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests), and [submitting pull requests](#submitting-pull-requests), but please respect the following restrictions: 23 | 24 | * Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone has already reported your problem or requested your idea. 25 | 26 | * Please **be civil**. Keep the discussion on topic and respect the opinions of others. See also our [Contributor Code of Conduct](#conduct). 27 | 28 | ### Bug Reports 29 | 30 | A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you! 31 | 32 | Guidelines for bug reports: 33 | 34 | 1. **Use the GitHub issue search** — check if the issue has already been 35 | reported. 36 | 37 | 1. **Check if the issue has been fixed** — look for [closed issues in the 38 | current milestone](https://github.com/EOSIO/tropical-example-web-app/issues?q=is%3Aissue+is%3Aclosed) or try to reproduce it 39 | using the latest `develop` branch. 40 | 41 | A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the details of your environment and relevant tests that demonstrate the failure. 42 | 43 | [Report a bug](https://github.com/EOSIO/tropical-example-web-app/issues/new?title=Bug%3A) 44 | 45 | ### Feature Requests 46 | 47 | Feature requests are welcome. Before you submit one be sure to have: 48 | 49 | 1. **Use the GitHub search** and check the feature hasn't already been requested. 50 | 1. Take a moment to think about whether your idea fits with the scope and aims of the project. 51 | 1. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this feature. Please provide as much detail and context as possible, this means explaining the use case and why it is likely to be common. 52 | 53 | ### Change Requests 54 | 55 | Change requests cover both architectural and functional changes to how Tropical Example works. If you have an idea for a new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to: 56 | 57 | 1. **Use the GitHub search** and check someone else didn't get there first 58 | 1. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure this shouldn't really be 59 | a [bug report](#bug-reports) or a [feature request](#feature-requests)? Is it really one idea or is it many? What's the context? What problem are you solving? Why is what you are suggesting better than what's already there? 60 | 61 | ## Working on Tropical Example 62 | 63 | Code contributions are welcome and encouraged! If you are looking for a good place to start, check out the [good first issue](https://github.com/EOSIO/tropical-example-web-app/labels/good%20first%20issue) label in GitHub issues. 64 | 65 | Also, please follow these guidelines when submitting code: 66 | 67 | ### Feature Branches 68 | 69 | To get it out of the way: 70 | 71 | - **[develop](https://github.com/EOSIO/tropical-example-web-app/tree/develop)** is the development branch. All work on the next release happens here so you should generally branch off `develop`. Do **NOT** use this branch for a production site. 72 | - **[master](https://github.com/EOSIO/tropical-example-web-app)** contains the latest release of Tropical Example. This branch may be used in production. Do **NOT** use this branch to work on Tropical Example's source. 73 | 74 | ### Submitting Pull Requests 75 | 76 | Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#reporting-an-issue) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged. 77 | 78 | ### Testing and Quality Assurance 79 | 80 | Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and don't know where to start, checking out and testing a pull request is one of the most useful things you could do. 81 | 82 | Essentially, [check out the latest develop branch](#working-on-tropical-example), take it for a spin, and if you find anything odd, please follow the [bug report guidelines](#bug-reports) and let us know! 83 | 84 | ## Conduct 85 | 86 | While contributing, please be respectful and constructive, so that participation in our project is a positive experience for everyone. 87 | 88 | Examples of behavior that contributes to creating a positive environment include: 89 | - Using welcoming and inclusive language 90 | - Being respectful of differing viewpoints and experiences 91 | - Gracefully accepting constructive criticism 92 | - Focusing on what is best for the community 93 | - Showing empathy towards other community members 94 | 95 | Examples of unacceptable behavior include: 96 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 97 | - Trolling, insulting/derogatory comments, and personal or political attacks 98 | - Public or private harassment 99 | - Publishing others’ private information, such as a physical or electronic address, without explicit permission 100 | - Other conduct which could reasonably be considered inappropriate in a professional setting 101 | 102 | 103 | 104 | ## Contributor License & Acknowledgments 105 | 106 | Whenever you make a contribution to this project, you license your contribution under the same terms as set out in LICENSE, and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below: 107 | 108 | ``` 109 | Developer Certificate of Origin 110 | Version 1.1 111 | 112 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 113 | 1 Letterman Drive 114 | Suite D4700 115 | San Francisco, CA, 94129 116 | 117 | Everyone is permitted to copy and distribute verbatim copies of this 118 | license document, but changing it is not allowed. 119 | 120 | 121 | Developer's Certificate of Origin 1.1 122 | 123 | By making a contribution to this project, I certify that: 124 | 125 | (a) The contribution was created in whole or in part by me and I 126 | have the right to submit it under the open source license 127 | indicated in the file; or 128 | 129 | (b) The contribution is based upon previous work that, to the best 130 | of my knowledge, is covered under an appropriate open source 131 | license and I have the right under that license to submit that 132 | work with modifications, whether created in whole or in part 133 | by me, under the same open source license (unless I am 134 | permitted to submit under a different license), as indicated 135 | in the file; or 136 | 137 | (c) The contribution was provided directly to me by some other 138 | person who certified (a), (b) or (c) and I have not modified 139 | it. 140 | 141 | (d) I understand and agree that this project and the contribution 142 | are public and that a record of the contribution (including all 143 | personal information I submit with it, including my sign-off) is 144 | maintained indefinitely and may be redistributed consistent with 145 | this project or the open source license(s) involved. 146 | ``` 147 | 148 | ## References 149 | 150 | * Overall CONTRIB adapted from https://github.com/mathjax/MathJax/blob/master/CONTRIBUTING.md 151 | * Conduct section adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 152 | -------------------------------------------------------------------------------- /eosio/scripts/deploy_contracts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | NODEOS_RUNNING=$1 3 | RUNNING_IN_GITPOD=$2 4 | 5 | set -m 6 | 7 | # CAUTION: Never use these development keys for a production account! 8 | # Doing so will most certainly result in the loss of access to your account, these private keys are publicly known. 9 | SYSTEM_ACCOUNT_PRIVATE_KEY="5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" 10 | SYSTEM_ACCOUNT_PUBLIC_KEY="EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" 11 | 12 | TROPICAL_EXAMPLE_ACCOUNT_PRIVATE_KEY="5Jh6jf9g1UzcWrMMsgqd5GrTCgzeKkh5yT7EUZbiU7wB7k4Ayx1" 13 | TROPICAL_EXAMPLE_ACCOUNT_PUBLIC_KEY="EOS6bRs6knaaHyvpVXd5EgAPoxrZkkeDv89M1jidHCt86W5rkwr1q" 14 | 15 | EXAMPLE_ACCOUNT_PRIVATE_KEY="5KkXYBUb7oXrq9cvEYT3HXsoHvaC2957VKVftVRuCy7Z7LyUcQB" 16 | EXAMPLE_ACCOUNT_PUBLIC_KEY="EOS6TWM95TUqpgcjYnvXSK5kBsi6LryWRxmcBaULVTvf5zxkaMYWf" 17 | 18 | if [ -z "$RUNNING_IN_GITPOD" ]; then 19 | echo "Running locally..." 20 | ROOT_DIR="/opt" 21 | CONTRACTS_DIR="$ROOT_DIR/eosio/bin/contracts" 22 | BLOCKCHAIN_DATA_DIR=/root/.local/share 23 | BLOCKCHAIN_CONFIG_DIR=/opt/eosio/bin/config-dir 24 | WALLET_DIR="/root/eosio-wallet/" 25 | else 26 | echo "Running in Gitpod..." 27 | ROOT_DIR="/home/gitpod" 28 | CONTRACTS_DIR="$ROOT_DIR/contracts" 29 | BLOCKCHAIN_DATA_DIR=$ROOT_DIR/eosio/chain/data 30 | BLOCKCHAIN_CONFIG_DIR=$ROOT_DIR/eosio/chain/config 31 | WALLET_DIR="$ROOT_DIR/eosio-wallet" 32 | fi 33 | 34 | mkdir -p $ROOT_DIR/bin 35 | 36 | # Set PATH 37 | PATH="$PATH:$ROOT_DIR/bin:$ROOT_DIR/bin/scripts" 38 | GITPOD_WORKSPACE_ROOT="/workspace/tropical-example-web-app" 39 | CONFIG_DIR="$ROOT_DIR/bin/config-dir" 40 | 41 | function start_wallet { 42 | echo "Starting the wallet" 43 | rm -rf $WALLET_DIR 44 | mkdir -p $WALLET_DIR 45 | nohup keosd --unlock-timeout 999999999 --wallet-dir $WALLET_DIR --http-server-address 127.0.0.1:8900 2>&1 & 46 | sleep 1s 47 | wallet_password=$(cleos wallet create --to-console | awk 'FNR > 3 { print $1 }' | tr -d '"') 48 | echo $wallet_password > "$CONFIG_DIR"/keys/default_wallet_password.txt 49 | 50 | cleos wallet import --private-key $SYSTEM_ACCOUNT_PRIVATE_KEY 51 | } 52 | 53 | function post_preactivate { 54 | curl -X POST http://127.0.0.1:8888/v1/producer/schedule_protocol_feature_activations -d '{"protocol_features_to_activate": ["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]}' 55 | } 56 | 57 | # $1 feature disgest to activate 58 | function activate_feature { 59 | cleos push action eosio activate '["'"$1"'"]' -p eosio 60 | if [ $? -ne 0 ]; then 61 | exit 1 62 | fi 63 | } 64 | 65 | # $1 account name 66 | # $2 contract directory 67 | # $3 wasm file name 68 | # $4 abi file name 69 | function setcode { 70 | retry_count="4" 71 | 72 | while [ $retry_count -gt 0 ]; do 73 | cleos set contract $1 $2 $3 $4 -p $1@active 74 | if [ $? -eq 0 ]; then 75 | break 76 | fi 77 | 78 | echo "setcode failed retrying..." 79 | sleep 1s 80 | retry_count=$[$retry_count-1] 81 | done 82 | 83 | if [ $retry_count -eq 0 ]; then 84 | echo "setcode failed too many times, bailing." 85 | exit 1 86 | fi 87 | } 88 | 89 | # $1 - parent folder where smart contract directory is located 90 | # $2 - smart contract name 91 | # $3 - account name 92 | function deploy_system_contract { 93 | # Unlock the wallet, ignore error if already unlocked 94 | cleos wallet unlock --password $(cat "$CONFIG_DIR"/keys/default_wallet_password.txt) || true 95 | 96 | echo "Deploying the $2 contract in path: $CONTRACTS_DIR/$1/$2/src" 97 | 98 | # Move into contracts /src directory 99 | cd "$CONTRACTS_DIR/$1/$2/src" 100 | 101 | # Compile the smart contract to wasm and abi files using the EOSIO.CDT (Contract Development Toolkit) 102 | # https://github.com/EOSIO/eosio.cdt 103 | eosio-cpp -abigen "$2.cpp" -o "$2.wasm" -I ../include 104 | 105 | # Move back into the executable directory 106 | cd $CONTRACTS_DIR 107 | 108 | # Set (deploy) the compiled contract to the blockchain 109 | setcode $3 "$CONTRACTS_DIR/$1/$2/src" "$2.wasm" "$2.abi" 110 | } 111 | 112 | function deploy_1.8.x_bios { 113 | # Unlock the wallet, ignore error if already unlocked 114 | cleos wallet unlock --password $(cat "$CONFIG_DIR"/keys/default_wallet_password.txt) || true 115 | 116 | echo "Deploying the v1.8.3 eosio.bios contract in path: $CONTRACTS_DIR/$1" 117 | 118 | # Move back into the executable directory 119 | cd $CONTRACTS_DIR 120 | 121 | # Set (deploy) the compiled contract to the blockchain 122 | setcode $3 "$CONTRACTS_DIR/$1" "$2.wasm" "$2.abi" 123 | } 124 | 125 | # $1 - account name 126 | # $2 - public key 127 | # $3 - private key 128 | function create_account { 129 | cleos wallet import --private-key $3 130 | cleos create account eosio $1 $2 131 | } 132 | 133 | # $1 - smart contract name 134 | # $2 - account name 135 | function deploy_app_contract { 136 | # Unlock the wallet, ignore error if already unlocked 137 | cleos wallet unlock --password $(cat "$CONFIG_DIR"/keys/default_wallet_password.txt) || true 138 | 139 | echo "Deploying the $1 contract" 140 | 141 | # Compile the smart contract to wasm and abi files using the EOSIO.CDT (Contract Development Toolkit) 142 | # https://github.com/EOSIO/eosio.cdt 143 | 144 | # Move into contracts directory 145 | cd "$CONTRACTS_DIR/$1/" 146 | ( 147 | if [ ! -f "$1.wasm" ]; then 148 | eosio-cpp -abigen "$1.cpp" -o "$1.wasm" -I ./ 149 | else 150 | echo "Using pre-built contract..." 151 | fi 152 | ) && 153 | # Move back into the executable directory 154 | cd $CONTRACTS_DIR 155 | 156 | # Set (deploy) the compiled contract to the blockchain 157 | setcode $2 "$CONTRACTS_DIR/$1/" "$1.wasm" "$1.abi" 158 | 159 | # Set the root of trust for the contract 160 | cleos push action $2 setsrvkey '["'"$TROPICAL_EXAMPLE_ACCOUNT_PUBLIC_KEY"'"]' -p $2 161 | 162 | } 163 | 164 | function issue_sys_tokens { 165 | echo "Issuing SYS tokens" 166 | cleos push action eosio.token create '["eosio", "10000000000.0000 SYS"]' -p eosio.token 167 | cleos push action eosio.token issue '["eosio", "5000000000.0000 SYS", "Half of available supply"]' -p eosio 168 | } 169 | 170 | # $1 - account name 171 | function transfer_sys_tokens { 172 | cleos transfer eosio $1 "1000000.0000 SYS" 173 | } 174 | 175 | # $1 - chain id 176 | # $2 - chain name 177 | # $3 - icon hash 178 | function assert_set_chain { 179 | echo "Setting $2 chain" 180 | cleos push action eosio.assert setchain "[ "\""$1"\"", "\""$2"\"", "\""$3"\"" ]" -p eosio@active 181 | } 182 | 183 | # $1 - account name 184 | # $2 - domain 185 | # $3 - appmeta 186 | # $4 - whitelist 187 | function assert_register_manifest { 188 | echo "Registering $1 manifest" 189 | cleos push action eosio.assert add.manifest "[ "\""$1"\"", "\""$2"\"", "\""$3"\"", $4 ]" -p $1@active 190 | } 191 | 192 | # Move into the executable directory 193 | cd $ROOT_DIR/bin/ 194 | mkdir -p $CONFIG_DIR 195 | mkdir -p $BLOCKCHAIN_DATA_DIR 196 | mkdir -p $BLOCKCHAIN_CONFIG_DIR 197 | 198 | if [ -z "$NODEOS_RUNNING" ]; then 199 | echo "Starting the chain for setup" 200 | nodeos -e -p eosio \ 201 | --data-dir $BLOCKCHAIN_DATA_DIR \ 202 | --config-dir $BLOCKCHAIN_CONFIG_DIR \ 203 | --http-validate-host=false \ 204 | --plugin eosio::producer_api_plugin \ 205 | --plugin eosio::chain_api_plugin \ 206 | --plugin eosio::http_plugin \ 207 | --http-server-address=0.0.0.0:8888 \ 208 | --access-control-allow-origin=* \ 209 | --contracts-console \ 210 | --max-transaction-time=100000 \ 211 | --verbose-http-errors & 212 | fi 213 | 214 | mkdir -p "$CONFIG_DIR"/keys 215 | 216 | sleep 1s 217 | 218 | echo "Waiting for the chain to finish startup" 219 | until curl localhost:8888/v1/chain/get_info 220 | do 221 | echo "Still waiting" 222 | sleep 1s 223 | done 224 | 225 | # Sleep for 2s to allow time for 4 blocks to be created so we have blocks to reference when sending transactions 226 | sleep 2s 227 | echo "Creating accounts and deploying contracts" 228 | 229 | start_wallet 230 | 231 | if [ ! -z "$RUNNING_IN_GITPOD" ]; then 232 | echo "INSTALLING CONTRACTS" 233 | mkdir -p $CONTRACTS_DIR 234 | mkdir -p $ROOT_DIR/downloads 235 | 236 | echo "INSTALLING EOSIO.CONTRACTS" 237 | wget https://github.com/EOSIO/eosio.contracts/archive/v1.9.0.tar.gz 238 | mkdir -p $ROOT_DIR/downloads/eosio.contracts-1.9.0 239 | mkdir -p $CONTRACTS_DIR/eosio.contracts 240 | tar xvzf ./v1.9.0.tar.gz -C $ROOT_DIR/downloads/eosio.contracts-1.9.0 241 | mv $ROOT_DIR/downloads/eosio.contracts-1.9.0/eosio.contracts-1.9.0/* $CONTRACTS_DIR/eosio.contracts 242 | rm -rf $ROOT_DIR/downloads/eosio.contracts-1.9.0 243 | rm ./v1.9.0.tar.gz 244 | 245 | echo "INSTALLING EOSIO.ASSERT CONTRACT" 246 | wget https://github.com/EOSIO/eosio.assert/archive/v0.1.0.tar.gz 247 | mkdir -p $ROOT_DIR/downloads/eosio.assert 248 | mkdir -p $CONTRACTS_DIR/eosio.assert 249 | tar xvzf ./v0.1.0.tar.gz -C $ROOT_DIR/downloads/eosio.assert 250 | mv $ROOT_DIR/downloads/eosio.assert/eosio.assert-0.1.0/* $CONTRACTS_DIR/eosio.assert 251 | rm -rf $ROOT_DIR/downloads/eosio.assert 252 | rm ./v0.1.0.tar.gz 253 | 254 | echo "COPYING APP CONTRACT" 255 | echo "GITPOD_WORKSPACE_ROOT: $GITPOD_WORKSPACE_ROOT" 256 | cp $GITPOD_WORKSPACE_ROOT/eosio/contracts/eosio.token/eosio.token.contracts.md $CONTRACTS_DIR/eosio.contracts/contracts/eosio.token/src 257 | mkdir -p $CONTRACTS_DIR/eosio.bios-v1.8.3 258 | cp $GITPOD_WORKSPACE_ROOT/eosio/contracts/eosio.bios-v1.8.3/* $CONTRACTS_DIR/eosio.bios-v1.8.3/ 259 | mkdir -p $CONTRACTS_DIR/tropical 260 | cp $GITPOD_WORKSPACE_ROOT/eosio/contracts/tropical/* $CONTRACTS_DIR/tropical/ 261 | fi 262 | 263 | # preactivate concensus upgrades 264 | post_preactivate 265 | 266 | # Create accounts and deploy contracts 267 | # eosio.assert 268 | create_account eosio.assert $SYSTEM_ACCOUNT_PUBLIC_KEY $SYSTEM_ACCOUNT_PRIVATE_KEY 269 | deploy_system_contract eosio.assert eosio.assert eosio.assert 270 | 271 | # eosio.bios 272 | 273 | deploy_1.8.x_bios eosio.bios-v1.8.3 eosio.bios eosio 274 | 275 | activate_feature "299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707" 276 | 277 | deploy_system_contract eosio.contracts/contracts eosio.bios eosio 278 | 279 | # eosio.token 280 | create_account eosio.token $SYSTEM_ACCOUNT_PUBLIC_KEY $SYSTEM_ACCOUNT_PRIVATE_KEY 281 | deploy_system_contract eosio.contracts/contracts eosio.token eosio.token 282 | issue_sys_tokens 283 | 284 | # activate Webauthn support 285 | activate_feature "4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2" 286 | 287 | # tropical 288 | create_account tropical $TROPICAL_EXAMPLE_ACCOUNT_PUBLIC_KEY $TROPICAL_EXAMPLE_ACCOUNT_PRIVATE_KEY 289 | deploy_app_contract tropical tropical 290 | transfer_sys_tokens tropical 291 | 292 | # example 293 | create_account example $EXAMPLE_ACCOUNT_PUBLIC_KEY $EXAMPLE_ACCOUNT_PRIVATE_KEY 294 | transfer_sys_tokens example 295 | 296 | # eosio.assert actions 297 | # Set chain 298 | assert_set_chain "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f" "Local Chain" "8ae3ccb19f3a89a8ea21f6c5e18bd2bc8f00c379411a2d9319985dad2db6243e" 299 | 300 | # Register tropical manifest 301 | # If running in Gitpod, we need to alter the URLs 302 | CONTRACT_NAME="tropical" 303 | MANIFEST="[{ "\""contract"\"": "\""tropical"\"", "\""action"\"": "\""like"\"" },{ "\""contract"\"": "\""tropical"\"", "\""action"\"": "\""rent"\"" },{ "\""contract"\"": "\""tropical"\"", "\""action"\"": "\""check2fa"\"" }, { "\""contract"\"": "\""eosio.assert"\"", "\""action"\"": "\""require"\"" }]" 304 | if [ -z "$RUNNING_IN_GITPOD" ]; then 305 | APP_DOMAIN="https://localhost:3000" 306 | APPMETA="https://localhost:3000/app-metadata.json#bc677523fca562e307343296e49596e25cb14aac6b112a9428a42119da9f65fa" 307 | else 308 | GP_URL=$(gp url 8000) 309 | APP_DOMAIN="${GP_URL}" 310 | APPMETA="${GP_URL}/app-metadata.json#bc677523fca562e307343296e49596e25cb14aac6b112a9428a42119da9f65fa" 311 | fi 312 | assert_register_manifest $CONTRACT_NAME $APP_DOMAIN $APPMETA "$MANIFEST" 313 | 314 | echo "All done initializing the blockchain" 315 | 316 | # If running in Gitpod, we *don't* want to shutdown the blockchain; we'll leave it running in the terminal window. 317 | if [ -z "$RUNNING_IN_GITPOD" ]; then 318 | if [[ -z $NODEOS_RUNNING ]]; then 319 | echo "Shut down Nodeos, sleeping for 2 seconds to allow time for at least 4 blocks to be created after deploying contracts" 320 | sleep 2s 321 | kill %1 322 | fg %1 323 | fi 324 | fi 325 | -------------------------------------------------------------------------------- /src/assets/images/tropical-background.svg: -------------------------------------------------------------------------------- 1 | Asset 13 2 | -------------------------------------------------------------------------------- /eosio/contracts/eosio.bios-v1.8.3/eosio.bios.abi: -------------------------------------------------------------------------------- 1 | { 2 | "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", 3 | "version": "eosio::abi/1.1", 4 | "types": [], 5 | "structs": [ 6 | { 7 | "name": "abi_hash", 8 | "base": "", 9 | "fields": [ 10 | { 11 | "name": "owner", 12 | "type": "name" 13 | }, 14 | { 15 | "name": "hash", 16 | "type": "checksum256" 17 | } 18 | ] 19 | }, 20 | { 21 | "name": "activate", 22 | "base": "", 23 | "fields": [ 24 | { 25 | "name": "feature_digest", 26 | "type": "checksum256" 27 | } 28 | ] 29 | }, 30 | { 31 | "name": "authority", 32 | "base": "", 33 | "fields": [ 34 | { 35 | "name": "threshold", 36 | "type": "uint32" 37 | }, 38 | { 39 | "name": "keys", 40 | "type": "key_weight[]" 41 | }, 42 | { 43 | "name": "accounts", 44 | "type": "permission_level_weight[]" 45 | }, 46 | { 47 | "name": "waits", 48 | "type": "wait_weight[]" 49 | } 50 | ] 51 | }, 52 | { 53 | "name": "blockchain_parameters", 54 | "base": "", 55 | "fields": [ 56 | { 57 | "name": "max_block_net_usage", 58 | "type": "uint64" 59 | }, 60 | { 61 | "name": "target_block_net_usage_pct", 62 | "type": "uint32" 63 | }, 64 | { 65 | "name": "max_transaction_net_usage", 66 | "type": "uint32" 67 | }, 68 | { 69 | "name": "base_per_transaction_net_usage", 70 | "type": "uint32" 71 | }, 72 | { 73 | "name": "net_usage_leeway", 74 | "type": "uint32" 75 | }, 76 | { 77 | "name": "context_free_discount_net_usage_num", 78 | "type": "uint32" 79 | }, 80 | { 81 | "name": "context_free_discount_net_usage_den", 82 | "type": "uint32" 83 | }, 84 | { 85 | "name": "max_block_cpu_usage", 86 | "type": "uint32" 87 | }, 88 | { 89 | "name": "target_block_cpu_usage_pct", 90 | "type": "uint32" 91 | }, 92 | { 93 | "name": "max_transaction_cpu_usage", 94 | "type": "uint32" 95 | }, 96 | { 97 | "name": "min_transaction_cpu_usage", 98 | "type": "uint32" 99 | }, 100 | { 101 | "name": "max_transaction_lifetime", 102 | "type": "uint32" 103 | }, 104 | { 105 | "name": "deferred_trx_expiration_window", 106 | "type": "uint32" 107 | }, 108 | { 109 | "name": "max_transaction_delay", 110 | "type": "uint32" 111 | }, 112 | { 113 | "name": "max_inline_action_size", 114 | "type": "uint32" 115 | }, 116 | { 117 | "name": "max_inline_action_depth", 118 | "type": "uint16" 119 | }, 120 | { 121 | "name": "max_authority_depth", 122 | "type": "uint16" 123 | } 124 | ] 125 | }, 126 | { 127 | "name": "canceldelay", 128 | "base": "", 129 | "fields": [ 130 | { 131 | "name": "canceling_auth", 132 | "type": "permission_level" 133 | }, 134 | { 135 | "name": "trx_id", 136 | "type": "checksum256" 137 | } 138 | ] 139 | }, 140 | { 141 | "name": "deleteauth", 142 | "base": "", 143 | "fields": [ 144 | { 145 | "name": "account", 146 | "type": "name" 147 | }, 148 | { 149 | "name": "permission", 150 | "type": "name" 151 | } 152 | ] 153 | }, 154 | { 155 | "name": "key_weight", 156 | "base": "", 157 | "fields": [ 158 | { 159 | "name": "key", 160 | "type": "public_key" 161 | }, 162 | { 163 | "name": "weight", 164 | "type": "uint16" 165 | } 166 | ] 167 | }, 168 | { 169 | "name": "linkauth", 170 | "base": "", 171 | "fields": [ 172 | { 173 | "name": "account", 174 | "type": "name" 175 | }, 176 | { 177 | "name": "code", 178 | "type": "name" 179 | }, 180 | { 181 | "name": "type", 182 | "type": "name" 183 | }, 184 | { 185 | "name": "requirement", 186 | "type": "name" 187 | } 188 | ] 189 | }, 190 | { 191 | "name": "newaccount", 192 | "base": "", 193 | "fields": [ 194 | { 195 | "name": "creator", 196 | "type": "name" 197 | }, 198 | { 199 | "name": "name", 200 | "type": "name" 201 | }, 202 | { 203 | "name": "owner", 204 | "type": "authority" 205 | }, 206 | { 207 | "name": "active", 208 | "type": "authority" 209 | } 210 | ] 211 | }, 212 | { 213 | "name": "onerror", 214 | "base": "", 215 | "fields": [ 216 | { 217 | "name": "sender_id", 218 | "type": "uint128" 219 | }, 220 | { 221 | "name": "sent_trx", 222 | "type": "bytes" 223 | } 224 | ] 225 | }, 226 | { 227 | "name": "permission_level", 228 | "base": "", 229 | "fields": [ 230 | { 231 | "name": "actor", 232 | "type": "name" 233 | }, 234 | { 235 | "name": "permission", 236 | "type": "name" 237 | } 238 | ] 239 | }, 240 | { 241 | "name": "permission_level_weight", 242 | "base": "", 243 | "fields": [ 244 | { 245 | "name": "permission", 246 | "type": "permission_level" 247 | }, 248 | { 249 | "name": "weight", 250 | "type": "uint16" 251 | } 252 | ] 253 | }, 254 | { 255 | "name": "producer_key", 256 | "base": "", 257 | "fields": [ 258 | { 259 | "name": "producer_name", 260 | "type": "name" 261 | }, 262 | { 263 | "name": "block_signing_key", 264 | "type": "public_key" 265 | } 266 | ] 267 | }, 268 | { 269 | "name": "reqactivated", 270 | "base": "", 271 | "fields": [ 272 | { 273 | "name": "feature_digest", 274 | "type": "checksum256" 275 | } 276 | ] 277 | }, 278 | { 279 | "name": "reqauth", 280 | "base": "", 281 | "fields": [ 282 | { 283 | "name": "from", 284 | "type": "name" 285 | } 286 | ] 287 | }, 288 | { 289 | "name": "setabi", 290 | "base": "", 291 | "fields": [ 292 | { 293 | "name": "account", 294 | "type": "name" 295 | }, 296 | { 297 | "name": "abi", 298 | "type": "bytes" 299 | } 300 | ] 301 | }, 302 | { 303 | "name": "setalimits", 304 | "base": "", 305 | "fields": [ 306 | { 307 | "name": "account", 308 | "type": "name" 309 | }, 310 | { 311 | "name": "ram_bytes", 312 | "type": "int64" 313 | }, 314 | { 315 | "name": "net_weight", 316 | "type": "int64" 317 | }, 318 | { 319 | "name": "cpu_weight", 320 | "type": "int64" 321 | } 322 | ] 323 | }, 324 | { 325 | "name": "setcode", 326 | "base": "", 327 | "fields": [ 328 | { 329 | "name": "account", 330 | "type": "name" 331 | }, 332 | { 333 | "name": "vmtype", 334 | "type": "uint8" 335 | }, 336 | { 337 | "name": "vmversion", 338 | "type": "uint8" 339 | }, 340 | { 341 | "name": "code", 342 | "type": "bytes" 343 | } 344 | ] 345 | }, 346 | { 347 | "name": "setparams", 348 | "base": "", 349 | "fields": [ 350 | { 351 | "name": "params", 352 | "type": "blockchain_parameters" 353 | } 354 | ] 355 | }, 356 | { 357 | "name": "setpriv", 358 | "base": "", 359 | "fields": [ 360 | { 361 | "name": "account", 362 | "type": "name" 363 | }, 364 | { 365 | "name": "is_priv", 366 | "type": "uint8" 367 | } 368 | ] 369 | }, 370 | { 371 | "name": "setprods", 372 | "base": "", 373 | "fields": [ 374 | { 375 | "name": "schedule", 376 | "type": "producer_key[]" 377 | } 378 | ] 379 | }, 380 | { 381 | "name": "unlinkauth", 382 | "base": "", 383 | "fields": [ 384 | { 385 | "name": "account", 386 | "type": "name" 387 | }, 388 | { 389 | "name": "code", 390 | "type": "name" 391 | }, 392 | { 393 | "name": "type", 394 | "type": "name" 395 | } 396 | ] 397 | }, 398 | { 399 | "name": "updateauth", 400 | "base": "", 401 | "fields": [ 402 | { 403 | "name": "account", 404 | "type": "name" 405 | }, 406 | { 407 | "name": "permission", 408 | "type": "name" 409 | }, 410 | { 411 | "name": "parent", 412 | "type": "name" 413 | }, 414 | { 415 | "name": "auth", 416 | "type": "authority" 417 | } 418 | ] 419 | }, 420 | { 421 | "name": "wait_weight", 422 | "base": "", 423 | "fields": [ 424 | { 425 | "name": "wait_sec", 426 | "type": "uint32" 427 | }, 428 | { 429 | "name": "weight", 430 | "type": "uint16" 431 | } 432 | ] 433 | } 434 | ], 435 | "actions": [ 436 | { 437 | "name": "activate", 438 | "type": "activate", 439 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Activate Protocol Feature\nsummary: 'Activate protocol feature {{nowrap feature_digest}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} activates the protocol feature with a digest of {{feature_digest}}." 440 | }, 441 | { 442 | "name": "canceldelay", 443 | "type": "canceldelay", 444 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Delayed Transaction\nsummary: '{{nowrap canceling_auth.actor}} cancels a delayed transaction'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{canceling_auth.actor}} cancels the delayed transaction with id {{trx_id}}." 445 | }, 446 | { 447 | "name": "deleteauth", 448 | "type": "deleteauth", 449 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Delete Account Permission\nsummary: 'Delete the {{nowrap permission}} permission of {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDelete the {{permission}} permission of {{account}}." 450 | }, 451 | { 452 | "name": "linkauth", 453 | "type": "linkauth", 454 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Link Action to Permission\nsummary: '{{nowrap account}} sets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract to {{nowrap requirement}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} sets the minimum required permission for the {{#if type}}{{type}} action of the{{/if}} {{code}} contract to {{requirement}}.\n\n{{#if type}}{{else}}Any links explicitly associated to specific actions of {{code}} will take precedence.{{/if}}" 455 | }, 456 | { 457 | "name": "newaccount", 458 | "type": "newaccount", 459 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create New Account\nsummary: '{{nowrap creator}} creates a new account with the name {{nowrap name}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{creator}} creates a new account with the name {{name}} and the following permissions:\n\nowner permission with authority:\n{{to_json owner}}\n\nactive permission with authority:\n{{to_json active}}" 460 | }, 461 | { 462 | "name": "onerror", 463 | "type": "onerror", 464 | "ricardian_contract": "" 465 | }, 466 | { 467 | "name": "reqactivated", 468 | "type": "reqactivated", 469 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Assert Protocol Feature Activation\nsummary: 'Assert that protocol feature {{nowrap feature_digest}} has been activated'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\nAssert that the protocol feature with a digest of {{feature_digest}} has been activated." 470 | }, 471 | { 472 | "name": "reqauth", 473 | "type": "reqauth", 474 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Assert Authorization\nsummary: 'Assert that authorization by {{nowrap from}} is provided'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nAssert that authorization by {{from}} is provided." 475 | }, 476 | { 477 | "name": "setabi", 478 | "type": "setabi", 479 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Deploy Contract ABI\nsummary: 'Deploy contract ABI on account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeploy the ABI file associated with the contract on account {{account}}." 480 | }, 481 | { 482 | "name": "setalimits", 483 | "type": "setalimits", 484 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Adjust Resource Limits of Account\nsummary: 'Adjust resource limits of account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} updates {{account}}’s resource limits to have a RAM quota of {{ram_bytes}} bytes, a NET bandwidth quota of {{net_weight}} and a CPU bandwidth quota of {{cpu_weight}}." 485 | }, 486 | { 487 | "name": "setcode", 488 | "type": "setcode", 489 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Deploy Contract Code\nsummary: 'Deploy contract code on account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeploy compiled contract code to the account {{account}}." 490 | }, 491 | { 492 | "name": "setparams", 493 | "type": "setparams", 494 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set System Parameters\nsummary: 'Set system parameters'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} sets system parameters to:\n{{to_json params}}" 495 | }, 496 | { 497 | "name": "setpriv", 498 | "type": "setpriv", 499 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Make an Account Privileged or Unprivileged\nsummary: '{{#if is_priv}}Make {{nowrap account}} privileged{{else}}Remove privileged status of {{nowrap account}}{{/if}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{#if is_priv}}\n{{$action.account}} makes {{account}} privileged.\n{{else}}\n{{$action.account}} removes privileged status of {{account}}.\n{{/if}}" 500 | }, 501 | { 502 | "name": "setprods", 503 | "type": "setprods", 504 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set Block Producers\nsummary: 'Set block producer schedule'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} proposes a block producer schedule of:\n{{#each schedule}}\n 1. {{this.producer_name}} with a block signing key of {{this.block_signing_key}}\n{{/each}}" 505 | }, 506 | { 507 | "name": "unlinkauth", 508 | "type": "unlinkauth", 509 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Unlink Action from Permission\nsummary: '{{nowrap account}} unsets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} removes the association between the {{#if type}}{{type}} action of the{{/if}} {{code}} contract and its minimum required permission.\n\n{{#if type}}{{else}}This will not remove any links explicitly associated to specific actions of {{code}}.{{/if}}" 510 | }, 511 | { 512 | "name": "updateauth", 513 | "type": "updateauth", 514 | "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Modify Account Permission\nsummary: 'Add or update the {{nowrap permission}} permission of {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nModify, and create if necessary, the {{permission}} permission of {{account}} to have a parent permission of {{parent}} and the following authority:\n{{to_json auth}}" 515 | } 516 | ], 517 | "tables": [ 518 | { 519 | "name": "abihash", 520 | "type": "abi_hash", 521 | "index_type": "i64", 522 | "key_names": [], 523 | "key_types": [] 524 | } 525 | ], 526 | "ricardian_clauses": [], 527 | "variants": [] 528 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌴 Tropical Example 2 | Tropical Example is a mock application for renting properties. It will be referenced throughout this guide as an example for application developers to start building secure applications with a good user experience on the EOSIO blockchain. 3 | 4 | ![EOSIO Labs](https://img.shields.io/badge/EOSIO-Labs-5cb3ff.svg) 5 | 6 | ## About EOSIO Labs 7 | 8 | EOSIO Labs repositories are experimental. Developers in the community are encouraged to use EOSIO Labs repositories as the basis for code and concepts to incorporate into their applications. Community members are also welcome to contribute and further develop these repositories. Since these repositories are not supported by Block.one, we may not provide responses to issue reports, pull requests, updates to functionality, or other requests from the community, and we encourage the community to take responsibility for these. 9 | 10 | ## Overview 11 | 12 | ### Try it out in Gitpod ### 13 | 14 | Gitpod [launches the app](https://gitpod.io/#https://github.com/EOSIO/tropical-example-web-app) for you. It starts the required blockchain in the background, launches the web server, and opens a preview window. 15 | NOTES: 16 | 1) There are several times during startup it might look like startup hangs, namely... near the end of the docker build, once the IDE comes up, and then once the preview shows. 17 | 2) Sometimes when Gitpod launches the webapp preview, it does so prematurely. Just click the small refresh circular arrow icon in the top left of the preview window. 18 | 3) Gitpod generates a dynamic URL for the browser to access the app from. This URL is needed in numerous parts of the app, so note that there is code in this repo specifically meant for Gitpod compatibility. A comment has been added in those locations to point it out. 19 | 4) To use Scatter in Gitpod, launch the demo in Gitpod, then go into Scatter's Networks section and add a custom network with the following settings as well as adding the account name. Note that these settings will need to be updated each time you launch the demo in Gitpod (because the URL will be different each time). 20 | * Network Settings 21 | * **Host**: 22 | * **Protocol**: https 23 | * **Port**: 443 24 | * **Chain ID**: cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f (This is the test chainId used in the example) 25 | ![Customer Network Configuation](docs/images/scatter-custom-network-config.png) 26 | * Adding account name 27 | * After you've imported the private key from this example (see other parts of the README for those instructions), Scatter might not pull the "example" account from the network. If that's the case, in the Wallet section, if "example" doesn't show up under your imported key with a balance to the right, on a Mac, you'll hold Ctrl when you click the three horizontal dot button to the right of your imported key. Ctrl will enable a normally-hidden item called "Link Account". Click that and in the first box, type "example" and in the drop-down, select the custom network you created above. See screenshot below for what this looks like. 28 | ![Link Account](docs/images/scatter-link-account.png) 29 | Read more about [Gitpod workspaces here](https://gitpod.io) 30 | 31 | The following open source repositories are utilized in Tropical Example: 32 | 33 | * Using the [Universal Authenticator Library (UAL)](https://github.com/EOSIO/universal-authenticator-library/) for quick and easy integration with multiple authentication providers (wallets). 34 | * Increasing the security and transparency of your application by following the [Manifest Specification](https://github.com/EOSIO/manifest-spec). 35 | * Displaying human readable Ricardian Contracts of your proposed EOSIO actions by following the [Ricardian Specification](https://github.com/EOSIO/ricardian-spec). 36 | 37 | ## Table of Contents 38 | - [Universal Authenticator Library (UAL)](#universal-authenticator-library-ual) 39 | - [Installation](#installation) 40 | - [Setup](#setup) 41 | - [UAL Context](#ual-context) 42 | - [Login](#login) 43 | - [Modal](#modal) 44 | - [Account Name](#account-name) 45 | - [Transactions](#transactions) 46 | - [Logout](#logout) 47 | - [Errors](#errors) 48 | - [Login Errors](#login-errors) 49 | - [Transactions Errors](#transactions-errors) 50 | - [Manifest Specification](#manifest-specification) 51 | - [Ricardian Specification](#ricardian-specification) 52 | - [WebAuthn](#webauthn) 53 | - [Running Tropical Example](#running-tropical-example) 54 | - [Required Tools](#required-tools) 55 | - [Setup](#setup-1) 56 | - [.env file defaults](#env-file-defaults) 57 | - [Installation](#installation-1) 58 | - [Running Nodeos](#running-nodeos) 59 | - [Running Frontend](#running-frontend) 60 | - [Login](#login-1) 61 | - [Using WebAuthn](#using-webauthn) 62 | - [Other Available Actions](#other-available-actions) 63 | - [Docker Compose Command Reference](#docker-compose-command-reference) 64 | - [Links](#links) 65 | - [Contributing](#contributing) 66 | - [License](#license) 67 | - [Important](#important) 68 | 69 | ## Universal Authenticator Library (UAL) 70 | 71 | An Authenticator provides the ability to communicate with an authentication provider. Authentication providers generally allow users to add, modify, and delete private / public key pairs and use these key pairs to sign proposed transactions. 72 | 73 | UAL allows developers to support multiple Authenticators with only a few lines of code. This significantly reduces the start up time of creating applications by removing the need to detect and create an interface for interacting with the Authenticators. 74 | 75 | UAL provides users with a common login interface in which they can select the Authenticator of their choice to interact with the EOSIO protocol. Once a user selects the Authenticator of their choice and logs in, the application developer will have access to an `activeUser` object which contains all necessary fields and functions to sign transactions and customize their user experience. 76 | 77 | ### Installation 78 | 79 | First install your [UAL Renderer](https://github.com/EOSIO/universal-authenticator-library#usage-dapp-developer) of choice. Tropical Example uses the [UAL Renderer for ReactJS](https://github.com/EOSIO/ual-reactjs-renderer) and the rest of the examples will be demonstrating usage with the React Renderer. Please view the [UAL documentation](https://github.com/EOSIO/universal-authenticator-library#usage-dapp-developer) for links to all available renderers with documentation and examples of their usage. 80 | 81 | ```bash 82 | yarn add ual-reactjs-renderer 83 | ``` 84 | 85 | Then install the Authenticators you want to allow users to interact with. Tropical Example uses the following Authenticators: 86 | - [UAL for EOSIO Reference Authenticator](https://github.com/EOSIO/ual-eosio-reference-authenticator) 87 | - [UAL for Scatter](https://github.com/EOSIO/ual-scatter) 88 | - [UAL for Lynx](https://github.com/EOSIO/ual-lynx) 89 | - [UAL for Token Pocket](https://github.com/EOSIO/ual-token-pocket) 90 | 91 | ```bash 92 | yarn add ual-eosio-reference-authenticator 93 | yarn add ual-scatter 94 | yarn add ual-lynx 95 | yarn add ual-token-pocket 96 | ``` 97 | 98 | ### Setup 99 | 100 | In your root React component (for most projects this will be index.js) wrap your App component with `UALProvider`. 101 | 102 | The `UALProvider` requires an array of Chains you wish your app to transact on, an array of Authenticators you want to allow users to interact with, and an application name. Each Authenticator requires at least an array of Chains that you want to allow the Authenticator to interact with and a second options parameter that may be required. Please see the documentation of the Authenticator if this options argument is required and what fields are necessary. 103 | 104 | ```javascript 105 | // UAL Required Imports 106 | import { UALProvider } from 'ual-reactjs-renderer' 107 | // Authenticator Imports 108 | import { EOSIOAuth } from 'ual-eosio-reference-authenticator' 109 | import { Scatter } from 'ual-scatter' 110 | import { Lynx } from 'ual-lynx' 111 | import { TokenPocket } from 'ual-token-pocket' 112 | ... 113 | const appName = 'Tropical-Example' 114 | 115 | // Chains 116 | const chain = { 117 | chainId: process.env.REACT_APP_CHAIN_ID, 118 | rpcEndpoints: [ 119 | { 120 | protocol: process.env.REACT_APP_RPC_PROTOCOL, 121 | host: process.env.REACT_APP_RPC_HOST, 122 | port: process.env.REACT_APP_RPC_PORT, 123 | }, 124 | ], 125 | } 126 | 127 | // Authenticators 128 | const eosioAuth = new EOSIOAuth([chain], { appName, protocol: 'eosio' }) 129 | const scatter = new Scatter([chain], { appName }) 130 | const lynx = new Lynx([chain]) 131 | const tokenPocket = new TokenPocket([chain]) 132 | 133 | const supportedChains = [chain] 134 | const supportedAuthenticators = [eosioAuth, scatter, lynx, tokenPocket] 135 | 136 | ReactDOM.render( 137 | 138 | 139 | , 140 | document.getElementById('root'), 141 | ) 142 | ``` 143 | 144 | ### UAL Context 145 | 146 | The UAL Renderer for ReactJS uses [Context](https://reactjs.org/docs/context.html) to expose the objects and functions needed to interact with UAL. The context is created by the `UALProvider`. There are two methods to gain access to this context: 147 | 148 | 1) The `withUAL` [HOC (Higher Order Component)](https://reactjs.org/docs/higher-order-components.html) can be used to pass the `UALProvider` context as props to the wrapped component. 149 | 150 | * **When using the `withUAL` HOC all of the `UALProvider` context will be available under the parent prop `ual`** 151 | 152 | ```javascript 153 | import { withUAL } from 'ual-reactjs-renderer' 154 | class Example extends React.Component { 155 | render() { 156 | const { ual: { logout } } = this.props 157 | return
Logout
158 | } 159 | } 160 | 161 | export default withUAL(Example) 162 | ``` 163 | 164 | 2) The `static contextType` property can be set on a class to the renderer's exported context object, `UALContext`. This allows the context to be referenced using `this.context` within the class. 165 | 166 | * **Using the static `contextType` to access the context is currently only supported by React component classes and not supported by functional components. For functional components, `withUAL` must be used if access to the context is required.** 167 | 168 | ```javascript 169 | import { UALContext } from 'ual-reactjs-renderer' 170 | class Example extends React.Component { 171 | static contextType = UALContext 172 | 173 | render() { 174 | const { logout } = this.context 175 | return
Logout
176 | } 177 | } 178 | ``` 179 | 180 | ### Login 181 | 182 | #### Modal 183 | 184 | By default, the `UALProvider` provides a modal at the root level of your application. This modal will render the login buttons of all the configured Authenticators that can be detected in the user’s environment. The modal is hidden by default, but can be displayed and dismissed by calling the functions `showModal` and `hideModal`, respectively. Both functions are set in the `UALProvider` context. 185 | 186 | ```javascript 187 | import { withUAL } from 'ual-reactjs-renderer' 188 | class App extends React.Component { 189 | ... 190 | displayLoginModal = (display) => { 191 | const { ual: { showModal, hideModal } } = this.props 192 | if (display) { 193 | showModal() 194 | } else { 195 | hideModal() 196 | } 197 | } 198 | ... 199 | } 200 | 201 | export default withUAL(App) 202 | ``` 203 | 204 | #### Account Name 205 | 206 | After logging in, an `activeUser` object is returned by the Authenticator and set in the `UALProvider` context. 207 | 208 | On the `activeUser` object a `getAccountName` method is available. This method returns a promise, which will resolve to a string containing the signed in account name. 209 | 210 | ```javascript 211 | import { UALContext } from 'ual-reactjs-renderer' 212 | class UserInfo extends React.Component { 213 | static contextType = UALContext 214 | ... 215 | async componentDidMount() { 216 | const { activeUser } = this.context 217 | if (activeUser) { 218 | const accountName = await activeUser.getAccountName() 219 | this.setState({ accountName }) 220 | } 221 | } 222 | ... 223 | } 224 | ``` 225 | 226 | ### Transactions 227 | 228 | In order to propose transactions, your application needs access to the `activeUser` object returned by the logged in Authenticator. 229 | 230 | At the time of signing, call `activeUser.signTransaction` with a valid transaction object and a [configuration object](https://github.com/EOSIO/universal-authenticator-library/blob/master/src/interfaces.ts#L40). This will propose the transaction to the logged in Authenticator. 231 | 232 | It is **highly recommended** in the transaction configuration to provide a `expireSeconds` property of a time greater than at least `300` seconds or 5 minutes. This will allow sufficient time for users to review and accept their transactions before expiration. 233 | 234 | ```javascript 235 | import { UALContext } from 'ual-reactjs-renderer' 236 | import { generateLikeTransaction } from 'utils/transaction' 237 | ... 238 | class Property extends React.Component { 239 | static contextType = UALContext 240 | ... 241 | onLike = async () => { 242 | const { login, displayError } = this.props 243 | // Via static contextType = UALContext, access to the activeUser object on this.context is now available 244 | const { activeUser } = this.context 245 | if (activeUser) { 246 | try { 247 | const accountName = await activeUser.getAccountName() 248 | const transaction = generateLikeTransaction(accountName) 249 | // The activeUser.signTransaction will propose the passed in transaction to the logged in Authenticator 250 | await activeUser.signTransaction(transaction, { broadcast: true, expireSeconds: 300 }) 251 | this.setState({ liked: true }) 252 | } catch (err) { 253 | displayError(err) 254 | } 255 | } else { 256 | login() 257 | } 258 | } 259 | ... 260 | } 261 | 262 | export default Property 263 | ``` 264 | 265 | The method `activeUser.signTransaction` returns a promise, which, if signing is successful, will resolve to the [signed transaction response](https://github.com/EOSIO/universal-authenticator-library/blob/master/src/interfaces.ts#L52). 266 | 267 | ### Logout 268 | 269 | If you want to logout, you can use the logout function set in the `UALProvider` context. 270 | 271 | ```javascript 272 | import { UALContext } from 'ual-reactjs-renderer' 273 | class UserInfo extends React.Component { 274 | static contextType = UALContext 275 | ... 276 | renderDropdown = () => { 277 | const { logout } = this.context 278 | return ( 279 |
280 | 281 |
282 | ) 283 | } 284 | ... 285 | } 286 | 287 | export default UserInfo 288 | ``` 289 | 290 | ### Errors 291 | 292 | Errors thrown by UAL are of type [`UALError`](https://github.com/EOSIO/universal-authenticator-library/blob/master/src/UALError.ts), which extends the generic `Error` class, but has extra information that is useful for debugging purposes. 293 | 294 | #### Login Errors 295 | 296 | During login, errors are set in the `UALProvider` context. 297 | 298 | ```javascript 299 | // Using withUAL() HOC 300 | this.props.ual.error 301 | ``` 302 | 303 | ```javascript 304 | // Using static contextType 305 | this.context.error 306 | ``` 307 | 308 | #### Transactions Errors 309 | 310 | During signing, errors will be thrown by `activeUser.signTransaction`. It is recommended to use the `try...catch` statement to capture these thrown errors. 311 | 312 | ```javascript 313 | try { 314 | await activeUser.signTransaction(transaction, transactionConfig) 315 | } catch (error) { 316 | // Using JSON.parse(JSON.stringify(error)) creates a copy of the error object to ensure 317 | // that you are printing the value of object at the moment you log it 318 | console.error('UAL Error', JSON.parse(JSON.stringify(error))) 319 | } 320 | ``` 321 | 322 | _If you need information not covered in this guide, you can reference the full UAL repository [here](https://github.com/EOSIO/universal-authenticator-library)._ 323 | 324 | ## Manifest Specification 325 | 326 | Tropical Example follows the Manifest Specification by providing the following: 327 | 328 | * A publicly accessible [app-metadata.json](https://github.com/EOSIO/tropical-example-web-app/blob/master/public/app-metadata.json). 329 | * A publicly accessible [chain-manifests.json](https://github.com/EOSIO/tropical-example-web-app/blob/master/public/chain-manifests.json) 330 | * Registering the app's Manifest on the local chain [via cleos](https://github.com/EOSIO/tropical-example-web-app/blob/master/eosio/scripts/deploy_contracts.sh#L114) 331 | 332 | _If you need information not covered in this guide, you can reference the Manifest Specification [here](https://github.com/EOSIO/manifest-spec)._ 333 | 334 | ## Ricardian Specification 335 | 336 | Tropical Example follows the Ricardian Specification by providing the following: 337 | 338 | * A [tropical.contracts.md](https://github.com/EOSIO/tropical-example-web-app/blob/master/eosio/contracts/tropical/tropical.contracts.md), which defines the Ricardian Contract of the `like` action of the `tropical` contract. 339 | * Generating the `tropical` abi file with [eosio-cpp](https://github.com/EOSIO/tropical-example-web-app/blob/master/eosio/scripts/deploy_contracts.sh#L80) by passing the `-abigen` flag, which will auto generate an abi including the `tropical.contracts.md` into the `ricardian_contract` field of the `like` action. 340 | 341 | _If you need information not covered in this guide, you can reference the Ricardian Specification [here](https://github.com/EOSIO/ricardian-spec)._ 342 | 343 | ## WebAuthn 344 | 345 | Tropical Example implements WebAuthn as a 2nd factor. 346 | 347 | After logging in, under the user menu, you'll find an option to "enroll" a 2FA device. Use this option in conjunction with your device's build-in biometric scanner, secure element, or external hardware key to enroll a key with the Tropical Example. 348 | 349 | Then, on the Properties Search Results page, you'll see a 'Rent' button. Where liking something is a relatively low-risk activity, the Rent button represents a real-world use case for commiting yourself to rent that property. In this case where money is on the line, the app will request you sign for the Rent action with the enrolled key. 350 | 351 | Read more about this example and technology [here -- REQUIRE LINK to blog or Release Notes of some kind](https://www.google.com) 352 | 353 | ## Running Tropical Example 354 | 355 | ### Required Tools 356 | 357 | * [Yarn](https://yarnpkg.com/lang/en/) with support at `^1.15.2` (latest stable). 358 | * [Docker](https://www.docker.com/) with support at Docker Engine `18.09.2` (latest stable). 359 | * [Docker Compose](https://docs.docker.com/compose/). 360 | * Mac and Windows environments - By default the Docker Compose tool is installed with Docker. 361 | * Linux - Follow [these instructions to install Docker Compose](https://docs.docker.com/compose/install/). 362 | * [Node.js](https://nodejs.org/en/) with support at `^10.15.3` LTS. **NOTICE** This project will not build on the current version of Node.js `12.3.1` due to an error in a sub-dependency of `react-scripts`. 363 | 364 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 365 | 366 | ### Setup 367 | 368 | Create a `.env` file from the `default.env` 369 | ```bash 370 | cp default.env .env 371 | ``` 372 | 373 | Tropical Example uses an environment configuration for the Chain and RPC endpoints. By default it will query the local node setup by Docker Compose in this repo. If you want to use another Chain, update the values in the .env file you created in the first step to set the preferred Chain you wish your app to transact on. 374 | 375 | #### .env file defaults 376 | 377 | ``` 378 | REACT_APP_CHAIN_ID=cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f 379 | REACT_APP_RPC_PROTOCOL=http 380 | REACT_APP_RPC_HOST=localhost 381 | REACT_APP_RPC_PORT=8888 382 | ``` 383 | 384 | ### Installation 385 | 386 | ```bash 387 | yarn 388 | ``` 389 | Run this first to install all the project's dependencies. 390 | 391 | ### Running Nodeos 392 | 393 | Before the app can be run, the Tropical Example contract must be deployed on the chain configured in the `.env` to the account `tropical`. 394 | 395 | This repo provides a `docker-compose.yml` that will setup and deploy the `tropical` contract using Docker Compose. 396 | 397 | Then run the following to start up a local node: 398 | ```bash 399 | yarn up 400 | ``` 401 | 402 | You can view the contract in the [eosio/contracts directory](https://github.com/EOSIO/tropical-example-web-app/tree/master/eosio/contracts/tropical). 403 | 404 | ### Running Frontend 405 | 406 | ```bash 407 | yarn start 408 | ``` 409 | This command runs the app in development mode over SSL. You will need to install a self-signed SSL certificate or enable [allow-insecure-localhost](chrome://flags/#allow-insecure-localhost) if running over SSL in chrome. 410 | Open [https://localhost:3000](https://localhost:3000) to view it in the browser. 411 | 412 | The page will reload if you make edits. 413 | 414 | ### Login 415 | 416 | The Docker Compose setup scripts provide an, `example` account, that can be imported into your Authenticator of choice to login and sign transactions: 417 | 418 | ⚠️ ***Never use this development key for a production account! Doing so will most certainly result in the loss of access to your account, this private key is publicly known.*** ⚠️ 419 | ```bash 420 | # Example Account Public Key 421 | EOS6TWM95TUqpgcjYnvXSK5kBsi6LryWRxmcBaULVTvf5zxkaMYWf 422 | # Example Account Private Key 423 | 5KkXYBUb7oXrq9cvEYT3HXsoHvaC2957VKVftVRuCy7Z7LyUcQB 424 | ``` 425 | 426 | ### Using WebAuthn 427 | 428 | After setting up the application and logging in, you can enable WebAuthn if you want to be able to `rent` a property. 429 | ![Enabling WebAuthn](docs/images/enable-webauthn.png) 430 | 431 | Once you enable WebAuthn with your choice of hardware, you can browse to the list of properties and select `rent`. Scatter will prompt you to allow this action by authenticating with your hardware. 432 | ![Renting A Property](docs/images/scatter-rent-property.png) 433 | 434 | After confirming the transaction, you should now see an indicator that your property has been rented successfully. 435 | ![Rented Property](docs/images/rented-property.png) 436 | 437 | #### Other Available Actions 438 | 439 | You can like a property (WebAuthn not required). After browsing to the list of properties and selecting `like`, scatter will prompt you to allow this action. 440 | ![Liking A Property](docs/images/scatter-like-property.png) 441 | 442 | After confirming the transaction, you should now see an indicator that your property has been liked successfully. 443 | ![Liked Property](docs/images/liked-property.png) 444 | 445 | ### Docker Compose Command Reference 446 | 447 | ```bash 448 | # Create and start the docker container 449 | docker-compose up eosio 450 | # Stop the docker container 451 | docker-compose down eosio 452 | # Open a bash terminal into the docker container 453 | docker-compose exec eosio /bin/bash 454 | ``` 455 | 456 | ## Links 457 | - [Universal Authenticator Library (UAL)](https://github.com/EOSIO/universal-authenticator-library) 458 | - [Manifest Specification](https://github.com/EOSIO/manifest-spec) 459 | - [Ricardian Specification](https://github.com/EOSIO/ricardian-spec) 460 | - [Docker Compose CLI Reference](https://docs.docker.com/compose/reference/) 461 | 462 | ## Contributing 463 | Check out the [Contributing](./CONTRIBUTING.md) guide and please adhere to the [Code of Conduct](./CONTRIBUTING.md#conduct) 464 | 465 | ## License 466 | [MIT licensed](./LICENSE) 467 | 468 | ## Important 469 | 470 | See [LICENSE](./LICENSE) for copyright and license terms. 471 | 472 | All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT.md) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice. 473 | --------------------------------------------------------------------------------