OpenRelay Widgets

Internals

## Widgets Overview * See Episode 5.5 for introduction * Core Elements * Base Classes * Templating * Data Binding * Logic * Events

Web3 Tag


							<or-web3>
								This shows if web3 is ready to go
								<span slot="noweb3">This shows if you do not have web3</span>
								<span slot="locked">This shows if your web3 plugin is locked </span>
								<span slot="netunsupported">
								  This shows if the dApp does not support your current network
								</span>
								<span slot="net">1</span>
							</or-web3>
						
This shows if web3 is ready to go This shows if you do not have web3 This shows if your web3 plugin is locked This shows if the dApp does not support your current network 1
## Web3 Tag Features * Select content based on web3 status * Web3 Injection for child elements * Monitor for * Account Changes * Network Changes * New Blocks * Emit events to children
## Web3 Events * Watches for: * **web3-child** - Emitted by child elements when added to the DOM * **set-web3** - Triggered by application to specify web3 * **subscribe-block** - Emitted by child elements to request block information
## Web3 Events * Dispatches to children * **web3-ready** - When a new web3 object is injected * **web3-account** - When the account changes * **web3-network** - When the network changes * **block** - When new blocks happen
## OrWeb3Base * Base class for elements that require Web3 * Emits and watches for events from the Web3 tag * Provides: * `e.network` * `e.account` * `e.web3` * `e.onBlock()`

Constructor


							import {LitElement} from '@polymer/lit-element';

							export default class OrWeb3Base extends LitElement {
							  constructor() {
							    super();
							    this.addEventListener('web3-ready', e => this.setWeb3(e));
							    this.addEventListener('web3-account', e => this.setAccount(e));
							    this.addEventListener('web3-network', e => this.setNetwork(e));
							    this.addEventListener('block', (e) => {
							      for(var cb of this.blockCallbacks) {
							        cb(e);
							      }
							    });
							    this.blockCallbacks = [];
							    this.accountReady = new Promise((resolve, reject) => {
							      this.resolveAccount = resolve;
							    });
							    this.networkReady = new Promise((resolve, reject) => {
							      this.resolveNetwork = resolve;
							    })
							  }
						

Initialization


							ready() {
							  super.ready();
							  setTimeout(() => this.dispatchEvent(new CustomEvent('web3-child', {
							    detail: {element: this}, bubbles: true, composed: true}))
							  );
							}
							setWeb3(e) {
							  this.web3 = e.detail.web3;
							  this.account = e.detail.account;
							  this.network = e.detail.network;
							  this.web3Updated();
							}
							setAccount(e) {
							  this.account = e.detail.account;
							  this.resolveAccount(this.account);
							  this.accountReady = Promise.resolve(this.account);
							  this.web3Updated();
							}
							setNetwork(e) {
							  this.network = e.detail.network;
							  this.resolveNetwork(this.network);
							  this.networkReady = Promise.resolve(this.network);
							  this.web3Updated();
							}
						

Other Functions


						bindToValue(selector, property, transform) {
						  transform = transform || (x => x);
						  this.shadowRoot.querySelector(selector).addEventListener("change", (e) => {
						    this[property] = transform(this.shadowRoot.querySelector(selector).value);
						  })
						}
						onBlock(cb) {
						  this.dispatchEvent(new CustomEvent('subscribe-block',
						    {detail: {element: this}, bubbles: true, composed: true}));
						  this.blockCallbacks.push(cb);
						}
						clearBlockCallbacks() {
						  this.blockCallbacks = [];
						}
						web3Updated() {}
						
## or-web3-account * Derived from OrWeb3Base * Displays account value

Account Module


							import {html} from '@polymer/lit-element';
							import OrWeb3Base from '@openrelay/web3-base';

							export default class OrWeb3Account extends OrWeb3Base {
							  static get is() { return "or-web3-account" };
							  _render({account}) {
							    return html`${account}`;
							  }
							  static get properties() {
							    return {
							      account: String
							    };
							  }
							}
							window.customElements.define(OrWeb3Account.is, OrWeb3Account)
						
## Unit Tests * Uses Karma & Mocha to run tests in browser * Consistent approach across modules

Test Suite


							import "@openrelay/web3-account-element";
							import "@openrelay/web3-element";
							import {getFakeWeb3} from "@openrelay/element-test-utils";
							import {assert} from "chai";


							describe('<or-web3-account>', () => {
							  var testArea;
							  before(() => {
							    testArea = document.createElement("div");
							    document.body.appendChild(testArea);
							  });
							  beforeEach(() => {
							    testArea.innerHTML = "";
							  })
						

Unit Tests


							it('should have id of 0xf00df00d...', () => {
							  testArea.innerHTML = '<or-web3 id="fixture">Content <or-web3-account id="test-element"></or-web3-account></or-web3>';
							  var web3 = getFakeWeb3();
							  document.getElementById("fixture").dispatchEvent(
							    new CustomEvent('set-web3',
							      {detail: {web3: web3}, bubbles: false, composed: false}
							    )
							  );
							  return document.getElementById('test-element').accountReady.then(() => {
							    document.getElementById("test-element").requestRender();
							    return document.getElementById("fixture").renderComplete.then(() => {
							      assert.notEqual(
							        document.getElementById('test-element').shadowRoot.innerHTML.indexOf("0xf00df00df00df00df00df00df00df00df00df00d"),
							        -1
							      );
							      web3.currentProvider.stop(console.log);
							    });
							  })
							});
						
## or-sra * Derived from or-web3 * Injects 0x Standard Relayer API Information * Configurable to different API endpoints * Specify Fee Recipient * Injects network-specific 0x Contract Addresses
## or-sra-fee * Gets Fees from SRA Target * Lets user select fee allocation

Fee Render


							import {html} from '@polymer/lit-element';
							import OrSRABase from '@openrelay/sra-base';
							import request from "@openrelay/element-utilities/request";

							export default class OrSRAFee extends OrSRABase {
							  static get is() { return "or-sra-fee" };
							  _render({value, totalFee, disabled}) {
							    let makerFeeDecimal = "";
							    let takerFeeDecimal = "";
							    if(this.web3) {
							      makerFeeDecimal = this.web3.fromWei(this.makerFee, "ether").toString();
							      takerFeeDecimal = this.web3.fromWei(this.takerFee, "ether").toString();
							    }
							    return html`
							      <div style="display: grid; grid-template-columns: 40px auto 40px">
							        <div style="grid-column-start: 1;">Maker</div>
							        <div style="grid-column-start: 3;">Taker</div>
							        <div style="grid-column-start: 1" id="maker-fee">
							          ${makerFeeDecimal}
							        </div>
							        <input style="grid-column-start: 2" disabled="${disabled}" type="range" min="0" max="100" value="${value}"></input>
							        <div style="grid-column-start: 3" id="taker-fee">${takerFeeDecimal}</div>
							      </div>
							      `;
							  }
						

Initialization


							constructor() {
							  super();
							  this._lastFeeRequest = {};
							  this.feePromise = new Promise((resolve, reject) => {
							    this._resolveFee = resolve;
							  });
							  this.value = 0;
							}
							ready() {
							  super.ready();
							  if(this.totalFee) {
							    this._initialTotalFee = this.totalFee;
							  } else {
							    this._initialTotalFee = null;
							  }
							  this.shadowRoot.querySelector("input").addEventListener("input", (e) => {
							    this.value = e.target.value;
							  });
							  this.shadowRoot.querySelector("input").addEventListener("change", (e) => {
							    this.emitChange();
							  });
							}
						

Fee Lookup


							refreshFee() {
							  if(!this.web3) {
							    return;
							  }
							  var body = {};
							  var change = false;
							  // If any of these properties change, we need to refresh
							  for(var key of ["makerAssetAddress", "takerAssetAddress", "makerAddress", "feeRecipient"]) {
							    if(this[key]) {
							      body[key] = this[key];
							    }
							    if(body[key] != this._lastFeeRequest[key]) {
							      change = true;
							    }
							  }
							  this._lastFeeRequest = body;
							  if(change || !this.totalFee) {
							    request({
							      url: this.sra + "v1/fees",
							      method: "post",
							      body: JSON.stringify(body)
							    }).then((result) => {
							      var feeBody = JSON.parse(result);
							      if(feeBody.feeRecipient != this.feeRecipient) {
							        // If the provided fee recipieint does not match the fee recipient in
							        // the response, emit an event so other elements can update.
							        this.dispatchEvent(new CustomEvent('sra-fee-recipient',
							          {detail: {
							            elementFeeRecipient: this.feeRecipient,
							            apiFeeRecipient: body.feeRecipient
							          }, bubbles: true, composed: true}));
							      }
							      let makerFee = this.web3.toBigNumber(feeBody.makerFee);
							      let takerFee = this.web3.toBigNumber(feeBody.takerFee);
							      this.totalFee = makerFee.add(takerFee);
							      this.value = takerFee.div(this.totalFee).mul(100).toNumber();
							      if(this._initialTotalFee && this.totalFee.lt(this._initialTotalFee)) {
							        // This allows app developers to set total fees higher than
							        // OpenRelay's minimum
							        this.totalFee = this.web3.toBigNumber(this._initialTotalFee);
							      }
							      this._resolveFee();
							      this.emitChange();
							    }).catch((error) => {
							      console.log(error);
							      this.dispatchEvent(new CustomEvent('sra-fee-error', {error: error, bubbles: true, composed: true}));
							    });
							  }
							}
						

Fee Lookup In Action


							<or-sra sra="https://api.openrelay.xyz">
								<or-sra-fee></or-sra-fee>
							</or-sra>
						
## or-web3-sign * Use Web3 to sign messages * Message provided as HTML attribute * Click to sign

Signing Template


							import {html} from '@polymer/lit-element';
							import OrWeb3Base from '@openrelay/web3-base';
							import * as ethjsutil from 'ethereumjs-util';

							export default class OrWeb3Sign extends OrWeb3Base {
							  static get is() { return "or-web3-sign" };
							  _render({message}) {
							    return html`<button disabled="${!message}">Sign</button>`;
							  }
						

Signing Process


							ready() {
							  super.ready();
							  this.shadowRoot.querySelector("button").addEventListener("click", () => {
							    this.web3.eth.sign(this.account, this.message, (err, result) => {
							      if(err) {
							        return dispatch(err);
							      }
							      let signature = ethjsutil.toBuffer(result);
							      let v = signature[64];
							      if(v < 27) {
							        // Some clients return v={0,1} instead of v={27,28}
							        v += 27;
							      }
							      let r = signature.slice(0, 32);
							      let s = signature.slice(32, 64);
							      var msgBuffer = ethjsutil.toBuffer(this.message);
							      if (this.account == this._recover(msgBuffer, v, r, s)) {
							        this.dispatch(null, v, r, s, 2);
							      } else if (this.account == this._recover(msgBuffer, v, s, r)) {
							        this.dispatch(null, v, s, r, 2);
							      } else if (this.account == this._recover(this._prefixedMsg(msgBuffer), v, r, s)) {
							        this.dispatch(null, v, r, s, 3);
							      } else if (this.account == this._recover(this._prefixedMsg(msgBuffer), v, s, r)) {
							        this.dispatch(null, v, s, r, 3);
							      } else {
							        this.dispatch("Error signing message. Signature could not be verified.")
							      }
							    });
							  });
							}
						

Signing in action


							<or-web3>
								<or-web3-sign message="0xecd0e108a98e192af1d2c25055f4e3bed784b5c877204e73219a5203251feaab">
								</or-web3-sign>
							</or-web3>
						
## Functional Widgets * or-web3 * or-web3-account * or-web3-sign * or-erc20-balance * or-token-select * or-sra * or-sra-fee * or-sra-maker
## Future Widgets * Signed Order Widget * Search Orderbook / Filter Widget * Set Token Allowance * Market Fill Widgets * Wrap / Unwrap WETH Widget * Depth chart
## Contributing * [github.com/openrelayxyz/widgets/issues](https://github.com/openrelayxyz/widgets/issues) * Functional Implementation * Styling * Documentation * Translation
## Next Week * Open to suggestions * Probably something a bit less technical