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.
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.
The following open source repositories are utilized in Tropical Example:
- Using the Universal Authenticator Library (UAL) for quick and easy integration with multiple authentication providers (wallets).
- Increasing the security and transparency of your application by following the Manifest Specification.
- Displaying human readable Ricardian Contracts of your proposed EOSIO actions by following the Ricardian Specification.
- Universal Authenticator Library (UAL)
- Manifest Specification
- Ricardian Specification
- Running Tropical Example
- Links
- Contributing
- License
- Important
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.
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.
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.
First install your UAL Renderer of choice. Tropical Example uses the UAL Renderer for ReactJS and the rest of the examples will be demonstrating usage with the React Renderer. Please view the UAL documentation for links to all available renderers with documentation and examples of their usage.
yarn add ual-reactjs-renderer
Then install the Authenticators you want to allow users to interact with. Tropical Example uses the following Authenticators:
yarn add ual-eosio-reference-authenticator
yarn add ual-scatter
yarn add ual-lynx
yarn add ual-token-pocket
In your root React component (for most projects this will be index.js) wrap your App component with UALProvider
.
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.
// UAL Required Imports
import { UALProvider } from 'ual-reactjs-renderer'
// Authenticator Imports
import { EOSIOAuth } from 'ual-eosio-reference-authenticator'
import { Scatter } from 'ual-scatter'
import { Lynx } from 'ual-lynx'
import { TokenPocket } from 'ual-token-pocket'
...
const appName = 'Tropical Example'
// Chains
const chain = {
chainId: process.env.REACT_APP_CHAIN_ID,
rpcEndpoints: [
{
protocol: process.env.REACT_APP_RPC_PROTOCOL,
host: process.env.REACT_APP_RPC_HOST,
port: process.env.REACT_APP_RPC_PORT,
},
],
}
// Authenticators
const eosioAuth = new EOSIOAuth([chain], { appName, protocol: 'eosio' })
const scatter = new Scatter([chain], { appName })
const lynx = new Lynx([chain])
const tokenPocket = new TokenPocket([chain])
const supportedChains = [chain]
const supportedAuthenticators = [eosioAuth, scatter, lynx, tokenPocket]
ReactDOM.render(
<UALProvider chains={supportedChains} authenticators={supportedAuthenticators} appName={appName}>
<App />
</UALProvider>,
document.getElementById('root'),
)
The UAL Renderer for ReactJS uses Context 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:
- The
withUAL
HOC (Higher Order Component) can be used to pass theUALProvider
context as props to the wrapped component.
- When using the
withUAL
HOC all of theUALProvider
context will be available under the parent propual
import { withUAL } from 'ual-reactjs-renderer'
class Example extends React.Component {
render() {
const { ual: { logout } } = this.props
return <div onClick={logout}>Logout</div>
}
}
export default withUAL(Example)
- 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 usingthis.context
within the class.
- 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.
import { UALContext } from 'ual-reactjs-renderer'
class Example extends React.Component {
static contextType = UALContext
render() {
const { logout } = this.context
return <div onClick={logout}>Logout</div>
}
}
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.
import { withUAL } from 'ual-reactjs-renderer'
class App extends React.Component {
...
displayLoginModal = (display) => {
const { ual: { showModal, hideModal } } = this.props
if (display) {
showModal()
} else {
hideModal()
}
}
...
}
export default withUAL(App)
After logging in, an activeUser
object is returned by the Authenticator and set in the UALProvider
context.
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.
import { UALContext } from 'ual-reactjs-renderer'
class UserInfo extends React.Component {
static contextType = UALContext
...
async componentDidMount() {
const { activeUser } = this.context
if (activeUser) {
const accountName = await activeUser.getAccountName()
this.setState({ accountName })
}
}
...
}
In order to propose transactions, your application needs access to the activeUser
object returned by the logged in Authenticator.
At the time of signing, call activeUser.signTransaction
with a valid transaction object and a configuration object. This will propose the transaction to the logged in Authenticator.
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.
import { UALContext } from 'ual-reactjs-renderer'
import { generateTransaction } from 'utils/transaction'
...
class Property extends React.Component {
static contextType = UALContext
...
onLike = async () => {
const { login, displayError } = this.props
// Via static contextType = UALContext, access to the activeUser object on this.context is now available
const { activeUser } = this.context
if (activeUser) {
try {
const accountName = await activeUser.getAccountName()
const transaction = generateTransaction(accountName)
// The activeUser.signTransaction will propose the passed in transaction to the logged in Authenticator
await activeUser.signTransaction(transaction, { broadcast: true, expireSeconds: 300 })
this.setState({ liked: true })
} catch (err) {
displayError(err)
}
} else {
login()
}
}
...
}
export default Property
The method activeUser.signTransaction
returns a promise, which, if signing is successful, will resolve to the signed transaction response.
If you want to logout, you can use the logout function set in the UALProvider
context.
import { UALContext } from 'ual-reactjs-renderer'
class UserInfo extends React.Component {
static contextType = UALContext
...
renderDropdown = () => {
const { logout } = this.context
return (
<div className='user-info-dropdown-content'>
<UserDropdown logout={logout} />
</div>
)
}
...
}
export default UserInfo
Errors thrown by UAL are of type UALError
, which extends the generic Error
class, but has extra information that is useful for debugging purposes.
During login, errors are set in the UALProvider
context.
// Using withUAL() HOC
this.props.ual.error
// Using static contextType
this.context.error
During signing, errors will be thrown by activeUser.signTransaction
. It is recommended to use the try...catch
statement to capture these thrown errors.
try {
await activeUser.signTransaction(transaction, transactionConfig)
} catch (error) {
// Using JSON.parse(JSON.stringify(error)) creates a copy of the error object to ensure
// that you are printing the value of object at the moment you log it
console.error('UAL Error', JSON.parse(JSON.stringify(error)))
}
If you need information not covered in this guide, you can reference the full UAL repository here.
Tropical Example follows the Manifest Specification by providing the following:
- A publicly accessible app-metadata.json.
- A publicly accessible chain-manifests.json
- Registering the app's Manifest on the local chain via cleos
If you need information not covered in this guide, you can reference the Manifest Specification here.
Tropical Example follows the Ricardian Specification by providing the following:
- A tropical.contracts.md, which defines the Ricardian Contract of the
like
action of thetropical
contract. - Generating the
tropical
abi file with eosio-cpp by passing the-abigen
flag, which will auto generate an abi including thetropical.contracts.md
into thericardian_contract
field of thelike
action.
If you need information not covered in this guide, you can reference the Ricardian Specification here.
- Yarn with support at
^1.15.2
(latest stable). - Docker with support at Docker Engine
18.09.2
(latest stable). - Docker Compose.
- Mac and Windows environments - By default the Docker Compose tool is installed with Docker.
- Linux - Follow these instructions to install Docker Compose.
- Node.js with support at
^10.15.3
LTS. NOTICE This project will not build on the current version of Node.js12.3.1
due to an error in a sub-dependency ofreact-scripts
.
This project was bootstrapped with Create React App.
Create a .env
file from the default.env
cp default.env .env
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.
REACT_APP_CHAIN_ID=cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f
REACT_APP_RPC_PROTOCOL=http
REACT_APP_RPC_HOST=localhost
REACT_APP_RPC_PORT=8888
yarn
Run this first to install all the project's dependencies.
Before the app can be run, the Tropical Example contract must be deployed on the chain configured in the .env
to the account tropical
.
This repo provides a docker-compose.yml
that will setup and deploy the tropical
contract using Docker Compose.
Then run the following to start up a local node:
yarn up
You can view the contract in the eosio/contracts directory.
yarn start
This command runs the app in the development mode. Open http://localhost:3000 to view it in the browser.
The page will reload if you make edits.
The Docker Compose setup scripts provide an, example
account, that can be imported into your Authenticator of choice to login and sign transactions:
# Example Account Public Key
EOS6TWM95TUqpgcjYnvXSK5kBsi6LryWRxmcBaULVTvf5zxkaMYWf
# Example Account Private Key
5KkXYBUb7oXrq9cvEYT3HXsoHvaC2957VKVftVRuCy7Z7LyUcQB
# Create and start the docker container
docker-compose up eosio
# Stop the docker container
docker-compose down eosio
# Open a bash terminal into the docker container
docker-compose exec eosio /bin/bash
- Universal Authenticator Library (UAL)
- Manifest Specification
- Ricardian Specification
- Docker Compose CLI Reference
Check out the Contributing guide and please adhere to the Code of Conduct
See LICENSE for copyright and license terms. Block.one makes its contribution on a voluntary basis as a member of the EOSIO community and is not responsible for ensuring the overall performance of the software or any related applications. We make no representation, warranty, guarantee or undertaking in respect of the software or any related documentation, 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 documentation or the use or other dealings in the software or documentation. Any test results or performance figures are indicative and will not reflect performance under all conditions. Any reference to any third party or third-party product, service or other resource is not an endorsement or recommendation by Block.one. We are not responsible, 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 the information here may be out of date or inaccurate. Any person using or offering this software in connection with providing software, goods or services to third parties shall advise such third parties of these license terms, disclaimers and exclusions of liability. Block.one, EOSIO, EOSIO Labs, EOS, the heptahedron and associated logos are trademarks of Block.one.
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 this software.