diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..7ad43c2 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +node_modules/ +.vscode/ +phoenix.db +examples/ +test/ +.editorconfig diff --git a/examples/react/README.md b/examples/react/README.md index 4d79b21..014433e 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -1,11 +1,34 @@ -# -``` -In the parent folder: -yarn link +phoenix - react example +=== +Simple application using a react observable component to receive a stream of emitted events. This app will deploy a test contract to **Ganache**. -In the current folder: +## Requirements +- `ganache-cli` +- `yarn` or `npm` installed. + +## Install +In the parent folder, link the package with `yarn` or `npm` +``` +yarn link +``` +Then in the current folder link `phoenix`, and install the packages +``` yarn link phoenix yarn -ganache-cli +``` + +## Usage +In a terminal execute +``` +ganache-cli +``` + +In a different session, execute +``` yarn run start -``` \ No newline at end of file +``` + +Browse the DApp in [http://localhost:3000](http://localhost:3000) + + +*Note*: this is a simple example application that does not include error handling for the web3 connection. Be sure `ganache-cli` is running in `localhost:8545` before browsing the dapp. \ No newline at end of file diff --git a/examples/react/src/App.js b/examples/react/src/App.js index d654953..735167f 100644 --- a/examples/react/src/App.js +++ b/examples/react/src/App.js @@ -1,138 +1,50 @@ import React from "react"; import Phoenix from "phoenix"; -import Web3 from "web3"; +import { web3, MyContract, onWeb3Available } from "./ethService"; +import { scan } from 'rxjs/operators'; + import MyComponentObserver from "./MyComponentObserver"; -const web3 = new Web3("ws://localhost:8545"); +let MyContractInstance; class App extends React.Component { state = { - escrowObservable: null + myEventObservable$: null }; - constructor(props) { - super(props); - this.EscrowContract = null; - } - componentDidMount() { - (async () => { - let accounts = await web3.eth.getAccounts(); - this.EscrowContract = await deployContract(); - - await this.EscrowContract.methods.createEscrow(1, accounts[0], accounts[1]).send({ from: accounts[0] }); - await this.EscrowContract.methods.createEscrow(1, accounts[1], accounts[2]).send({ from: accounts[0] }); - await this.EscrowContract.methods.createEscrow(1, accounts[1], accounts[0]).send({ from: accounts[0] }); - await this.EscrowContract.methods.createEscrow(1, accounts[0], accounts[2]).send({ from: accounts[0] }); + // Verify if web3 connection is available + onWeb3Available(async () => { + MyContractInstance = await MyContract.deploy().send({ from: web3.eth.defaultAccount }); const eventSyncer = new Phoenix(web3.currentProvider); - await eventSyncer.init(); + eventSyncer.init().then(() => { + const myEventObservable$ = eventSyncer.trackEvent(MyContractInstance, "MyEvent", {filter: {}, fromBlock: 1 }); - this.setState({ - escrowObservable: eventSyncer.trackEvent(this.EscrowContract, "Created", { filter: { buyer: accounts[0] }, fromBlock: 1 }) + // If you want to return all the events in an array, you can pipe the scan operator to the observable + // const myEventObservable$ = eventSyncer.trackEvent(MyContractInstance, "MyEvent", {filter: {}, fromBlock: 1 }) + // .pipe(scan((accum, val) => [...accum, val], [])); + // Your observable component would receive the eventData as an array instead of an object + + this.setState({ myEventObservable$ }); }); - - })(); + }); } - createTrx = async () => { - let accounts = await web3.eth.getAccounts(); - await this.EscrowContract.methods - .createEscrow(Date.now(), accounts[0], accounts[1]) - .send({ from: accounts[0] }); + createTrx = () => { + MyContractInstance.methods + .myFunction() + .send({ from: web3.eth.defaultAccount }); }; render() { - const {escrowObservable} = this.state; - return (
- - + +
); } } export default App; - - - - - - - - - - - - - -async function deployContract() { - let accounts = await web3.eth.getAccounts(); - - // pragma solidity >=0.4.22 <0.6.0; - // contract Escrow { - // event Created(uint indexed escrowId, address buyer, address seller); - // function createEscrow(uint escrowId, address buyer, address seller) external { - // emit Created(escrowId, buyer, seller); - // } - // } - - let abi = [ - { - "constant": false, - "inputs": [ - { - "name": "escrowId", - "type": "uint256" - }, - { - "name": "buyer", - "type": "address" - }, - { - "name": "seller", - "type": "address" - } - ], - "name": "createEscrow", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "escrowId", - "type": "uint256" - }, - { - "indexed": false, - "name": "buyer", - "type": "address" - }, - { - "indexed": false, - "name": "seller", - "type": "address" - } - ], - "name": "Created", - "type": "event" - } - ] - - var contract = new web3.eth.Contract(abi) - let instance = await contract.deploy({ - data: '0x608060405234801561001057600080fd5b50610184806100206000396000f3fe60806040526004361061003b576000357c01000000000000000000000000000000000000000000000000000000009004806378015cf414610040575b600080fd5b34801561004c57600080fd5b506100b96004803603606081101561006357600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100bb565b005b827fcbd6f84bfed2ee8cc01ea152b5d9f7126a72c410dbc5ab04c486a5800627b1908383604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a250505056fea165627a7a72305820cc868ec126578f5508ee248fb823cd9f1ac6deb0562091cdf31843840b2a56410029', - arguments: [] - }).send({ - from: accounts[0], - gas: '4700000' - }) - return instance -} diff --git a/examples/react/src/MyComponentObserver.js b/examples/react/src/MyComponentObserver.js index 32b0976..aebb2fe 100644 --- a/examples/react/src/MyComponentObserver.js +++ b/examples/react/src/MyComponentObserver.js @@ -1,17 +1,20 @@ import React from "react"; -import {observe} from "phoenix/react"; +import { observe } from "phoenix/react"; -const MyComponent = props => { - const { escrow, myCustomProperty } = props; - if(!escrow) return

Loading...

; - return ( - - ); +const MyComponent = ({ eventData }) => { + // Handle initial state when no data is available + if (!eventData) { + return

No data

; + } + + console.log("Data received", eventData); + + return ; }; +// MyComponent will now observe any observable prop it receives +// and update its state whenever the observable emits an event export default observe(MyComponent); diff --git a/examples/react/src/ethService.js b/examples/react/src/ethService.js new file mode 100644 index 0000000..f19b5e1 --- /dev/null +++ b/examples/react/src/ethService.js @@ -0,0 +1,67 @@ +import Web3 from 'web3'; + +export const web3 = new Web3("ws://localhost:8545"); + +let web3AvailableCB; +export const onWeb3Available = (cb) => { + web3AvailableCB = cb; +} + +web3.eth.getAccounts().then(async accounts => { + web3.eth.defaultAccount = accounts[0]; + if(web3AvailableCB){ + web3AvailableCB(); + } + /*SimpleStorageContract.deploy().send({ from: web3.eth.defaultAccount }).then(instance => { + SimpleStorage = instance; + });*/ +}); + + + +// pragma solidity ^0.5.0; +// +// contract MyContract { +// event MyEvent(uint someValue, bytes32 anotherValue); +// +// function myFunction() public { +// uint a = block.timestamp * block.number; // Just to have a pseudo random value +// bytes32 b = keccak256(abi.encodePacked(a)); +// +// emit MyEvent(a, b); +// } +// } + + +const abi = [ + { + "constant": false, + "inputs": [], + "name": "myFunction", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "someValue", + "type": "uint256" + }, + { + "indexed": false, + "name": "anotherValue", + "type": "bytes32" + } + ], + "name": "MyEvent", + "type": "event" + } +]; + +const data = "0x6080604052348015600f57600080fd5b5060f38061001e6000396000f3fe6080604052600436106039576000357c010000000000000000000000000000000000000000000000000000000090048063c3780a3a14603e575b600080fd5b348015604957600080fd5b5060506052565b005b60004342029050600081604051602001808281526020019150506040516020818303038152906040528051906020012090507fc3d6130248b5b68a864c047b2f68d895d420924130388d02d64b648005fe9ac78282604051808381526020018281526020019250505060405180910390a1505056fea165627a7a72305820613e35c5d1e8684ef5b31a7d993a139f1b5bbb409039d92db0fe78ed571d2ce20029"; + +export const MyContract = new web3.eth.Contract(abi, {data, gas: "470000"});