Tailor-made smart contract interaction
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci
.github
docs
flow-typed
integration-testing
scripts
src
.babelrc
.editorconfig
.eslintrc
.flowconfig
.gitignore
.nvmrc
CHANGELOG.md
CODE_OF_CONDUCT.md
LICENSE
README.md
jest-integration.conf.js
jest.conf.js
package.json
webpack.config.js
yarn.lock

README.md

Tailor

Tailor is a library for interacting with Ethereum smart contracts, built by Colony. It acts as a powerful and easy to use layer between lower-level libraries such as Web3, and your dApp, with features including dynamic ABI loading and extensible type checking.

yarn add @colony/tailor

Getting Started

Let's start by loading a contract from Etherscan. To interact with a contract we need its ABI - a description of what functions and events it has. This example relies on the code having been verified on Etherscan.

import Tailor from '@colony/tailor'
import Web3 from 'web3'
import { open } from '@colony/purser-software'

const web3 = new Web3('wss://mainnet.infura.io/ws')
const wallet = await open({ mnemonic: '...' })

const cryptoKitties = await Tailor.load({
  loader: 'etherscan',
  query: {
    contractAddress: '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d'
  },
  adapter: {
    name: 'web3',
    options: { web3 }
  },
  wallet
})

// get kitty ids owned by us
const myKittyIds = await cryptoKitties.constants
  .tokensOfOwner(cryptoKitties.wallet.address)

console.log(myKittyIds)
// -> ['123456', ...]

// transfer our first kitty
await cryptoKitties.methods.transfer({
  to: '0xabc...',
  tokenId: myKittyIds[0]
}).send()

// helper method to get our kitties
cryptoKitties.extend({
  async getKitties(count) {
    const kittyIds = await this.constants
      .tokensOfOwner(cryptoKitties.wallet.address)
    return kittyIds
      .slice(0, count)
      .map(async id => await this.constants.getKitty(id))
  }
})

// list our first 5 kitties
const myKitties = await cryptoKitties.getKitties(5)

console.log(myKitties)
// -> [{ generation, genes, ... }, ...]

// listen for kitties being transferred to us
cryptoKitties.events.Transfer.addListener(({ from, to, tokenId }) => {
  if (to === cryptoKitties.wallet.address)
    console.log(`${from} sent you kitty with id ${tokenId}!`)
})

In this example, you can see we set up a Web3 instance pointing to Infura, and open a Purser wallet to use with Tailor. Once the ABI has been loaded and parsed, all of the CryptoKitties constants, events and methods are available to interact with.

As long as parameters are named in the contract, they can be passed as an object, like with the transfer above. You can also just pass them as positional arguments.

Configuration

Loaders

As well as getting contract data from Etherscan like in the getting started, there are several other ways of loading this in. The query is what tells the loader what it should be loading, with some supporting contractName, contractAddress, or both. Some loaders also take options.

const client = await Tailor.load({
  loader: {
    name: 'truffle',
    options: {}
  },
  // or just loader: 'truffle',
  query: {
    contractName: 'MyContract'
  },
  ...
})

// also supports fs, http, trufflepig, etherscan

// alternatively pass pre-loaded contractData
// contractData: {
//   abi: [{ ... }],
//   address: '0xabc'
// }

Loaders are super flexible and allow for great development experiences using tools like TrufflePig. See loaders for more info, as well as how to create your own custom loader.

Wallet

Tailor supports a wide range of wallets through Purser, including MetaMask and various hardware wallets.

import { open: software } from '@colony/purser-software'
import { open: trezor } from '@colony/purser-trezor'

// mnemonic
await Tailor.load({
  wallet: await software({ mnemonic: '...' }),
  ...
})

// Trezor
await Tailor.load({
  wallet: await trezor(),
  ...
})

See the Purser docs for more info on how wallets work, as well as a full list of those available.

Contract Overrides

Sometimes it helps to be able to specify in more detail how Tailor should interact with your contract, and what values it can expect to be returned. We can do this by passing method, constant and event overrides.

const KITTY_ID_TYPE = {
  validate: value => true,
  convertInput: value => value,
  convertOutput: value => value
}

const cryptoKitties = await Tailor.load({
  methods: {
    sendKitty: {
      functionName: 'transfer',
      type: 'contract',
      input: [{
        name: 'to',
        type: 'address'
      }, {
        name: 'kitty',
        type: KITTY_ID_TYPE
      }]
    }
  },
  events: {
    Transfer: {
      output: [{
        name: 'from',
        type: 'address'
      }, {
        name: 'to',
        type: 'address'
      }, {
        name: 'tokenId',
        type: 'integer'
      }]
    }
  },
  ...
})

const { events } = await cryptoKitties.methods.sendKitty({
  to: '0xabc...',
  kitty: 123456
}).send()

console.log(events)
// -> [{ from, to, tokenId }, ...]

Notice how at the top we define a custom type. This one doesn't actually do any checking/conversion, but you can see how it might do so.

Overriden methods have a few different options, including the functionName which they should call, and the type of transaction to be used (e.g. deploy or multisig for ERC191 off-chain multisig). See contract specification for more info.

Extending

Event Emitters

Various things which happen internally within Tailor emit events. These can be used, for example, to update off-chain stores, display UI elements, or for logging purposes.

const cryptoKitties = await Tailor.load({ ... })
const tx = cryptoKitties.methods.transfer(...)

tx.on('confirmation', (confirmationNumber, receipt) =>
  console.log(confirmationNumber))

await tx.send()
// -> 1, 2, 3...

For all available events see events info.

Hooks

Emitted events are great for when you want to perform an action in response to something happening, but sometimes you need more control. That's where hooks come in - they let you asynchronously transform an internal Tailor value before it's used. We call these hooked values.

const cryptoKitties = await Tailor.load({ ... })

cryptoKitties.methods.transfer.hooks({
  // always transfer to this address
  send: (state, tx) => { ...state, to: '0x7e57...' }
})

const tx = cryptoKitties.methods.transfer(...)

tx.hooks({
  receipt: receipt => { ...receipt, hooked: true }
})

Hooks are called in the order global > local, meaning if we were to also set a send hook on the tx in the example above, the hooked value from the first would be what the second received as input. The hook functions must return the transformed input, but some hooks also provide additional arguments which should not be modified.

For a full list of available hooks see hooks info.

Extend

In a lot of cases, it's fine to pass all your Tailor configuration when you load the instance. But what if, for example, you wanted to provide developers using your contract with an instance which could be further extended? We've got you covered.

import myExtension from './myExtension'

const client = await Tailor.load({ ... })

// conveniently use external extension modules
client.extend(myExtension)

// or do it manually
client.extend({
  ...
})

This is particularly handy for redux-logger style debugging, or optional extra features which you can distribute for your contract libraries.

See contract spec for full details of how Tailor can be extended.