Last modified: Fri Aug 16 2019 11:27:04 GMT+0000 (Coordinated Universal Time)

Smart contracts

Winding Tree smart contracts architecture has several layers:

  • The Entrypoint smart contract
  • Segment Directory smart contracts
  • ORG.ID smart contracts

Winding Tree Architecture

Entrypoint

The entry point is formed by a smart contract called, well, Winding Tree Entrypoint. It's basically just a list of all segment directories with their Ethereum addresses that the platform instance offers. A segment can be for instance hotels, airlines or otas.

Ideally, the Entrypoint Ethereum address is everything you need to know to start working with the platform. You should interact with it only to find out how the platform instance is configured and where its parts are located.

truffle(ropsten)> entrypoint = await WindingTreeEntrypoint.at('0xa268937c2573e2AB274BF6d96e88FfE0827F0D4D')
truffle(ropsten)> entrypoint.getSegment('hotels')
'0x2c29c421d7fd7be4cc2bfb6d1a44426e43021914'

Segment directory

A Segment Directory is a collection of organizations in a certain business segment, such as hotels. They are meant as a primary source for organization discovery. So, if you want to get a list of hotels participating in the platform, you would look up a Hotels segment directory and from there, you could list all ORG.IDs that claim to be hotels.

truffle(ropsten)> hotelsDirectory = await SegmentDirectory.at('0x2c29c421d7fd7be4cc2bfb6d1a44426e43021914')
truffle(ropsten)> hotelsDirectory.getOrganizations()
[ '0x0000000000000000000000000000000000000000',
  ...
  '0xE61d952f077EfF0C022cC0FEC841059DA2289526' ]

Organization Factory

An organization factory is a smart contract that creates new instances of ORG.IDs for you. It always uses our reference implementation of 0xORG and comes with a free upgradeability of the contract.

Factory's address is always registered in the entrypoint.

truffle(ropsten)> entrypoint = await WindingTreeEntrypoint.at('0xa268937c2573e2AB274BF6d96e88FfE0827F0D4D')
truffle(ropsten)> entrypoint.getOrganizationFactory()
'0x8E6463ea056d812094Ed514455Ab3C88fc23D59C'
truffle(ropsten)> factory = await OrganizationFactory.at('0x8E6463ea056d812094Ed514455Ab3C88fc23D59C')
truffle(ropsten)> factory.create('https://gist.githubusercontent.com/JirkaChadima/9c86f9ed1cfd157f71a172ee9379f35f/raw/0287be953438ba04a9fb98b625589b2b28c64b8b/legal-entity-hotel-api.json', '0xea937104edca4af1f37e47808a5667173e83cc6033e0cf6e6a3c9f7c102b8beb')
{ tx:
   '0x1658f9c0d5184075ce8579379b223a210cb9adbc3373ebea9a75243863685d40',
   ...
}

ORG.ID

Every Organization is represented by the 0xORG smart contract that is only modifiable by its creator. It holds a single URI that points to an off chain location where the ORG.JSON data is stored and a keccak256 hash of its contents is provided. The hash is used to prevent malicious actors from tampering with the content.

While it is possible to have a new ORG.ID created by the Organization Factory registered in the Entrypoint, it might more suit your needs to use a custom implementation. The only requirement is the conformity to a minimal interface:

contract OrganizationInterface {
  /**
   * Ethereum account address of the organization owner
   */
  function owner() public view returns (address);

  /**
   * URI of the JSON data stored off-chain
   */
  function getOrgJsonUri() external view returns (string memory);

  /**
   * @dev Returns keccak256 hash of raw ORG.JSON contents.
   */
  function getOrgJsonHash() external view returns (bytes32);

  /**
   * Check if an Ethereum address can represent the ORG.ID
   */
  function hasAssociatedKey(address addr) external view returns (bool);

  /**
   * List all associated keys that can represetn the ORG.ID
   */
  function getAssociatedKeys() external view returns (address[] memory);


  /**
   * Used to check the interface conformity
   * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
   */
  function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

Smart contracts may have complex governance rules. E.g. a smart contract can be owned by more than one Ethereum account (this approach is called multisig - for multisignature wallet). For instance, a smart contract can have three "owners" and it may require two out of three owners to execute a certain action (e.g. add, edit or remove data from it). We recommend using multisig accounts for ORG.ID control.

The getOrgJsonUri method should return a pointer to the ORG.JSON file stored off-chain (it is protocol-agnostic, it could be http, ftp, ipfs, swarm, etc.). The contents of the file might differ based on the used segment, but the common denominator is the presence of a legal entity:

{
  "dataFormatVersion": "0.0.0",
  "updatedAt": "2019-06-03T13:20:06.398Z",
  "legalEntity": {
    "name": "Awesome Ltd.",
    "address": {
      "road": "5th Avenue",
      "houseNumber": "123",
      "city": "New York",
      "countryCode": "US"
    },
    "contact": {
      "email": "[email protected]"
    }
  }
}

The getOrgJsonHash should return a keccak256 hash of the aforementioned contents. For our example, it would look like below. You can quickly verify the hash by using this excellent online tool. The 0x prefix is just a thing commonly used in Ethereum world.

0x90b2f54616adc812c15d34912bf14d838340ce6710edd385d6239e32a90e28e0

Please pay special attention to the hasAssociatedKey method. Associated keys are Ethereum accounts that the organization declares to use on the platform. This is especially useful when message signing is required. The 0xORG cannot sign messages (as it does not have a private key), so another account has to be used. The hasAssociatedKey method is used to verify that the message signer can be considered as a valid Organization member.

You can also link to another ORG.ID's legal entity with a signed relationship guarantee. Let's imagine that Buy N Large ORG.ID shares the same legal entity with ACME ORG.ID. The flow would then look like this:

  1. Buy N Large asks ACME to give them a signed legal entity relationship guarantee.
  2. ACME does her due dilligence of Buy N Large.
  3. ACME creates claim and one of its associated keys signs it. Claim is an encoded message that contains Ethereum address of both Buy N Large and ACME ORG.IDs and an expiration date. The claim then has to be signed by a private key associated with one of the ACME's associated keys, thus creating the signature.
  4. ACME sends the claim and the signature back to the Buy N Large.
  5. Buy N Large publishes claim and signature in their JSON data.
{
  "dataFormatVersion": "0.0.0",
  "updatedAt": "2019-06-03T13:20:06.398Z",
  "legalEntity": {
    "claim": "0x7b227375626a656374223a22307841303239664236316530373936334345333538323438436246636646343730353532443636374638222c2267756172616e746f72223a22307863636130343832324164396331373862646639646139303931323138653234316634433238303432222c22657870697265734174223a313536343636333936377d",
    "signature": "0xb835573ebf85c5ced0b089a462eac5c4bc9a4948ee4a5542734f6093af48e45e0a942c96e4a9743d03cc5ef5a142f7a68f9893bcc8e869803aabec4bbf6a10d91c"
  }
}

claim is a hex encoded JSON object (to work around JSON serialization ambiguity) and the signature is produced with eth_sign compatible signature method.

Info

You can play with signatures in our Debugging Tools.

Where to next