2.0.1 • Published 5 years ago

worldsfair v2.0.1

Weekly downloads
2
License
ISC
Repository
github
Last release
5 years ago

World's Fair Developer Docs

Welcome

This documentation is meant to help developers implement novel/niche applications of World's Fair, and, in general, support the emergence of decentralized mechanisms to fund art and open source technology.

For live chat with other dapp developers and additional help/resources/networking please consider joining our #buidl channel on the World's Fair Discord server.

The API is actually three different APIs, mirroring the 3-layer structure of World's Fair:

  • Web API: Get json data from our server. Our backend is continually reading the most recent World's Fair data from the blockchain, and processing/organising it into a more easily queryable format, as well as integrating with meta data from our frontend. We also provide routes to GET/POST cryptographically signed threads and comments, stored on IPFS and linked to a user's identity on the blockchain.

  • JavaScript API: Read/write World's Fair data on the blockchain using web3 from browser or Node backend.

  • Solidity API: The actual source code of the World's Fair contract, with lots of comments so you can understand how it works and how to integrate your contract.

Self Funding Open Source

Wouldn't it be great (for both developers and users) if make it awesome and make everything free was a valid monetization strategy? That's the central theoretical principle of a value-consensus economy!

Each asset on World's Fair is seeking a state of agreement about its monetary value. In the process of reaching value-consensus, funding naturally flows to assets in proportion to the perceived value of what it is they represent. Open source creates an incredible amount of value already. World's Fair is simply a mechanism by which to negotiate what that value is in monetary terms. It's as if value were brought into existence by the act of measuring it.

We'd like to be able to accomplish this in a secure, manipulation-resistant, and socially responsible way. Nobody quite knows what kind of applications are, or may become, possible. If you can contribute something valuable (ha) consider creating an asset for your git repo so people can support your work and keep growing the ecosystem.

For ideas and discussion about ongoing projects, check out the community forum. Happy coding

Web API

JavaScript API

Solidity API / Contract Code


Web API

The Web API provides json data which has been processed and packaged by our backend server into a queryable database (this is a subset of the api used by the official front-end). The endpoint for testnet data is https://api-rinkeby.worldsfair.io while mainnet data can accessed at https://api.worldsfair.io. You don't need an API key. Certain rate limits are enforced, so do avoid making lots of requests in rapid succession from the same IP.

Much of the data in the Web API is the same as can be read directly from the blockchain using the JavaScript API, but also includes certain public meta data (like watching and following figures for assets) and various high-level data and functions for the global state of World's Fair.

In addition to the various GET routes, there are two routes that allow you to POST signed data: POST /thread and POST /comment are provided to allow the implementation of third-party community clients (the Community Section functions much like reddit, check it out).

GET World's Fair Data

GET /global Global Stats

const res = await axios.get(`${API}/global`);

Returns top-level statistics for the World's Fair. Data is updated every few minutes.

Parameters

None

Returns Object

  • total: String, total valuation, sum of all profits
  • figures: Object
    • creators: String, wei paid to creators in icos and royalties
    • shareholders: String, wei paid to investors from trading
    • beneficiaries: String, wei paid to charity
  • content: Object, number of assets tagged as each type
    • video / film: Number
    • audio / music: Number
    • visual / design: Number
    • words / language: Number
    • podcast / spoken: Number
    • source / code: Number
    • project / tech: Number
    • peace / love: Number

GET /search Search Assets and Accounts

const query = 'reggae dub'; // match account username/address or asset title/tags/ipfs
const res = await axios.get(`${API}/search?q=${query}`);

Given a search term, returns up to 8 objects representing assets and accounts. Assets are matched by their title and tags, while accounts are matched by username. When the query matches an account, results may include assets that were created by that account. You can also fetch an account by querying its linked Ethereum address. If the query is an IPFS hash, server returns matching assets. The server will reject queries exceeding 100 chars.

Parameters

  • q: required, search query

Returns [Object]

  • Object account
    • type: String, account
    • username: String, username of the account
    • address: String, account's linked address
  • Object asset
    • type: String, asset
    • signature: String, signature of asset
    • title: String, title of asset
    • owner: String, username of author account
    • valuation: String, current valuation of asset in wei
    • time: Number, timestamp of block when asset was published

GET /asset Assets

// Default front-page asset feed (no params)
const q0 = '';

// Specific asset (provide asset signature)
const q1 = '?signature=0x6a03e1da5ad3563f6afb74f7a1f53f7edaf9e5d6';

// Most recent assets created by a specific account (provide username)
const q2 = '?owner=picklerick&sort=new';

// Exclude assets that don't have proper meta data (ie title and tags)
const q3 = '?mode=strict';

// Assets matching any of the provided tags
const tags = ['music / audio', 'reggae', 'dub'];
const q4 = `?match=any&tags=${encodeURI(JSON.stringify(tags))}`;

const res = await axios.get(`${API}/asset${/*your_query*/}`);

Returns assets (in batches of 25) matching various search parameters. This route should allow developers to implement asset browser clients in environments that don't yet have a lot of support for blockchain-enabled actions (eg mobile). The current default batch size for results is 25. The examples above are just a few cases—you can freely combine/omit query components as needed to suit your application.

Parameters

  • signature: optional, get specific asset
  • owner: optional, get assets created by account
  • sort: optional, order results by recent (recently created), ico (upcoming ico), watching (number of accounts watching), investment (highest valuation first), or investment24h (most profits generated in last 24 hours)
  • match: optional, return assets matching any or all tags
  • tags: optional, uri-encoded array of up to 8 tags to match
  • mode: optional, if strict exclude assets without proper meta
  • skip: optional, number of results to skip (for pagination)

Returns Object

  • endOfList: Boolean, true if returned results represent all matches for query
  • assets: Object, array of asset objects
    • Object asset
      • title: String, asset title
      • description: String, asset description
      • descriptionHtml: String, rendered and sanitized description html
      • tags: String, array of tags
      • embed: String, http platform (for content embedding)
      • signature: String, asset signature
      • ipfsHash: String, IPFS hash of content
      • http: String, canonical link to content
      • owner: String, username of author account
      • beneficiary: String, beneficiary Ethereum address
      • timePublished Number, unix time when asset was created
      • totalShares: Number, how many shares asset is tokenized into
      • pOwner: Number, percent or profits that owner receives as royalites
      • pBeneficiary: Number, percent of profits sent to beneficiary
      • beneficiaryName: String, human-readable name for known beneficiary addresses
      • contributed: String, amount asset has contributed to beneficiary to date
      • limitConstant: Number, Appreciation Limiting Constant (ALC) x 10000
      • icoOpen: Number, unix time when ICO opens (shares become tradeable)
      • icoCompleted: Number, unix time when asset reached Minimum Valuation target (undefined for ongoing ICOs)
      • initialPrice: String, ICO price for one share in wei
      • watching: Number, number of accounts watching this asset
      • ipfsValid: Boolean, true if ipfsHash is a valid IPFS multihash
      • valuation: String, asset's total valuation (sum of all profits in wei)
      • v24h: String, sum of profits in last 24 hours in wei

GET /shareholder Asset Shareholders

const res = await axios.get(`${API}/shareholder?asset=0x6a03e1da5ad3563f6afb74f7a1f53f7edaf9e5d6`);

Given an asset signature, returns all the current (meaning that they own at least one share) shareholders for that asset.

Parameters

  • asset: required, signature of asset you want to fetch shareholders for

Returns Object

  • Object shareholder
    • account: String, username of shareholder account
    • shares: Number, how many shares currently owned by shareholder
    • ask: String, shareholders current asking price in wei
    • weiIn: String, total wei account has ever spent on shares of this asset
    • weiOut: String, total wei account has ever received as revenue from selling shares of this asset
    • lastBuy: Number, unix time when shareholder last acquired shares
    • address: String, Ethereum address currently linked to shareholder account

GET /asset/exists Check If Asset Exists

const res = await axios.get(`${API}/asset/exists?username=picklerick&ipfs=QmWn3yBeu8Akm6rwC1zNvHTmvpCjfHchmBQs15YTMawu9S`);

Given an account username and an IPFS multihash, returns true if such an asset exists (an asset is uniquely defined by these two pieces of data).

Parameters

  • username: required, username of account
  • ipfs: required, ipfs multihash of content

Returns Boolean

GET /asset/transactions Asset Transactions

const res = await axios.get(`${API}/asset/transactions?asset=0x6a03e1da5ad3563f6afb74f7a1f53f7edaf9e5d6`);

Given an asset signature, returns the transaction history for that asset.

Parameters

  • asset: required, signature of the asset you wish to fetch transactions for

Returns [Object]

  • Object transaction
    • seller: String, username of account that sold shares
    • buyer: String, username of account that bought shares
    • shares: Number, how many shares
    • buyPrice: String, share price in wei buyer paid
    • newAsk: String, new share price in wei buyer is asking
    • time: Number, unix time of transaction
    • transactionHash: String, hash of transaction
    • transactionIndex: Number, index of transaction in block
    • blockNumber: Number, block number transaction was included in

GET /beneficiary/list List of Beneficiaries

const res = await axios.get(`${API}/beneficiary/list?set=whitelisted&sort=cumulative`);

Returns a list of beneficiaries. Each time someone creates an asset with a new beneficiary, our server reads that data off the blockchain and creates a model for it if it doesn't already exist. Admins manually review these addresses to see (basically by Googling the address) if they are linked to some known charitable entity. If so, we add the name, domain, and link properties. If you want to only fetch results these human-reviewed results, pass the set parameter as whitelisted. This beneficiary list is ever-growing and anyone is welcome to use it as a general purpose source of Ethereum donation addresses, even if your application has nothing to do with World's Fair. Results are returned in batches of 50.

The full json file of known beneficiaries is also available on GitHub.

Parameters

  • set: optional, pass whitelisted to only get admin-reviewed results
  • sort: optional, can be cumulative (highest total contributions first) or current (highest balance ready for payout first)
  • skip: optional, number of results to skip

Returns Object

  • endOfList: Boolean, true if returned results represent all matches for query
  • beneficiaries: Object, array of beneficiary objects
    • Object: beneficiary
      • address: String, Ethereum address of beneficiary
      • supporting: Number, how many assets named this address as beneficiary
      • domain: String, link to main website of beneficiary
      • name: String, human readable name of beneficiary, if known
      • link: String, link to webpage where donation address was sourced
      • current: String, amount of wei ready for payout
      • cumulative: String, cumulative amount of wei ever raised
      • updated: Number, when the beneficiary's info was added or last updated

GET /beneficiary/data Beneficiary Data

const res = await axios.get(`${API}/beneficiary/data?beneficiary=0xc7FF9555C8df69f923C0A72EDdbfd6a079d6D9B3`);

Given an Ethereum address, returns data about contributions received and possibly meta data about the charitable entity associated with the address, if known. Server responds 404 if the provided address has never been named as beneficiary by any asset.

Parameters

  • beneficiary: required, beneficiary Ethereum address

Returns Object

  • name: String, human readable name of beneficiary, if known
  • link: String, link to webpage where donation address was sourced
  • domain: String, link to main website of beneficiary
  • known: Boolean, true if meta data has been manually added by admins
  • supporting: Number, how many assets named this address as beneficiary
  • current: String, amount of wei ready for payout
  • cumulative: String, cumulative amount of wei ever raised

GET /beneficiary/transactions Beneficiary Credits & Payouts

const res = await axios.get(`${API}/beneficiary/transactions?beneficiary=0xc7FF9555C8df69f923C0A72EDdbfd6a079d6D9B3`);

Given a beneficiary Ethereum address, return events in which this beneficiary recieved ether as a result of trading shares, in addition to events in which the payout was triggered (causing the ether to actually be sent from the World's Fair contract). In the case of such payouts, if it was triggered by an Ethereum address linked to an account, the account name is triggeredBy (otherwise it's an empty string). Results are returned in batches of 50.

Parameters

  • beneficiary: required, beneficiary Ethereum address
  • before: optional, unix time for which to omit results that are timestamped later
  • skip: optional, number of results to skip

Returns Object

  • endOfList: Boolean, true if returned results represent all matches for query
  • events: Object, array of beneficiary events
    • Object: credit event
      • event: String, credit
      • asset: String, signature of asset in transaction resulting in credit
      • amount: String, amount credited in wei
      • beneficiary: String, Ethereum address of beneficiary
      • time: Number, unix time of credit
    • Object: payout event
      • event: String, payout
      • triggeredBy: String, username of account (if any) linked to address that triggered payout
      • amount: String, amount of wei paid to beneficiary
      • beneficiary: String, recipient Ethereum address
      • time: Number, unix time of payout
      • number: Number, index of payout

GET /account/available Check Username Availability

const res = await axios.get(`${API}/account/available?username=PiCkLeRiCk`);
console.log(res.data); // false

Given a username, returns true if that account is able to be registered. It's worth pointing out that just because an account does not exist, this does not neccessarily mean that it's available for registration. That's because, in an effort to provide good UX and make scams more difficult, World's Fair server considers all case variants of a username "taken" when an account is created. It's certainly possible to register case-variants (because the contract only cares about the byte representation of the username) but it's not recommended because our server will refuse to index them when synchronizing with new data from the blockchain. You are encouraged to follow this convention when designing your own UX to maximize cross-compatibility between World's Fair clients.

Parameters

  • username: required, username you want to check availability for

Returns Boolean

GET /account/portfolio User's Public Portfolio

const res = await axios.get(`${API}/account/portfolio?username=picklerick`);

Given a username, returns data about all the asset shares currently held by that account, in additional to stats about portfolio totals.

Parameters

  • username: required, username of account
  • sort: optional, order results by

Returns Object

  • stats
    • consensusValue: String, total consensus value of holdings
    • totalInvested: String, total wei invested in asset shares
    • change24h: String, total change in consensus value of holdings in the last 24 hours
    • charity: String, total wei contributed to beneficiaries from account's holdings
    • netProfit: String, amount in wei that total revenue exceeds total investment for all holdings (can be negative)
  • holdings: Object, array of holdings
    • Object portfolio item
      • ask: String, account's current asking price in wei
      • asset: String, signature of asset
      • title: String, title of asset
      • shares: Number, how many shares account owns
      • weiIn: String, total amount (in wei) account has ever spent on shares of this asset
      • weiOut: String, total amount (in wei) account has ever recieved in as revenue from sale of shares
      • totalShares: Number, total number of shares asset is divided into
      • initialPrice: String, ICO price of asset in wei
      • limitConstant: Number, asset's Appreciation Limiting Constant (ALC) x 10000
      • pOwner: Number, percent of profits (in addition to ICO revenue) sent to author/owner
      • pBeneficiary: Number, percent of profits sent to beneficiary address (from account's investment)
      • toBeneficiary: String, amount of wei sent to beneficiary (from account's investment)
      • toOwner: String, amount of wei sent to owner (from account's investment)
      • profit: String, amount in wei that revenue exceeds investment (can be negative)
      • lastBuy: Number, unix time when account most recently bought shares in this asset
      • lastSell: Number, unix time when account most recently sold shares
      • v24h: String, amount of wei consensus value of shares has increased in the last 24 hours
      • percentStake: Number, approximate percent of asset's shares owner by account
      • consensusValue: String, consensus value of shares in wei
      • contentType: String, content type of asset

GET /account/data User's Public Profile

const res = await axios.get(`${API}/account/data?username=picklerick`);

Given account username, returns public bio information for account.

Parameters

  • username: required, account username

Returns Object

  • address: String, account's linked address
  • followers: Number, how may accounts are following this account
  • created: Number, uinx time account was created

GET /thread Threads

// Get default front-page threads
const threads = await axios.get(`${API}/thread`);

// Get only threads by picklerick
const authorThreads = await axios.get(`${API}/thread?author=picklerick`);

// Get one specific thread by its IPFS hash
const thread = await axios.get(`${API}/thread?hash=Qmeg8qRQc4ce6QtRi4zy6tSvQqEW7sPWkh1qVwRMJKywK9`);

// Get older threads (useful for pagination)
const before = Math.floor(Date.now() / 1000) - (60 * 60 * 24); // Unix time 24 hours ago
const olderThreads = axios.get(`${API}/thread?before=${before}`);

Returns community threads. To fetch the default threads (those found on the front page of the Community section) just hit the url with no parameters. If you just need to fetch a specific thread, pass that thread's hash in the query. If you want to fetch all the threads created by a certain account, pass that account's username as author. Data is returned in batches of 50 threads. The before parameter can be used to paginate results by setting it equal to the oldest thread that your client has already fetched on the previous request. as tells the server to mark the upvoted and downvoted fields with the appropriate value, defaulting to false.

Parameters

  • hash: optional, hash of specific thread to fetch
  • author: optional, fetch all threads by certain account (pass username)
  • before: optional, unix time for which to omit results that are timestamped later
  • as: optional, username of account making request (ie allows server to set upvoted and downvoted for signed in user)

Returns Object

  • endOfList: Boolean, true if returned results represent all matches for query
  • threads: Object, array of thread objects
    • Object: thread
      • hash: String, IPFS hash of signed thread data (use as GUID)
      • author: String, username of account linked to address which signed data
      • address: String, Ethereum address that signed data
      • title: String, thread title
      • content: String, selftext of thread, if any
      • html: String, rendered and sanitized html (threads support markdown rendering)
      • announcement: Boolean, if announcement thread
      • sticky: Boolean, if sticky thread
      • upvoted: Boolean, if username passed in as parameter has upvoted thread
      • downvoted: Boolean, if username passed in as parameter has downvoted thread
      • points: Number, net upvotes/downvotes received
      • comments: Number, how many comments on this thread
      • time: Number, unix time when thread was posted

GET /comment Comments

// Get a thread's comments
const threadComments = axios.get(`${API}/comment?thread=Qmeg8qRQc4ce6QtRi4zy6tSvQqEW7sPWkh1qVwRMJKywK9`);

// Get an asset's comments
const assetComments = axios.get(`${API}/comment?asset=0x6a03e1da5ad3563f6afb74f7a1f53f7edaf9e5d6`);

// Get an account's comments with `as` specified so server can return upvoted/downvoted flags
const authorComments = axios.get(`${API}/comment?author=picklerick&as=morty`);

Returns comments. sort parameter is required and may be set to new or top. It required that you pass either asset (signature of an asset), thread (hash of a thread) or author (username of an account). as tells the server to mark the upvoted and downvoted fields with the appropriate value, defaulting to false.

Parameters

  • sort: optional, can be top (default, most points first) or new (most recent)
  • asset: required (only if thread or author not specified), signature of asset to fetch comments for
  • thread: required (only if asset or author not specified), hash of thread to fetch comments for
  • author: required (only if asset or thread not specified), username of account to fetch comments for
  • as: optional, username of account making request (ie allows server to set upvoted and downvoted for signed in user)

Returns [Object]

  • Object comment
    • asset OR thread: String, signature of asset (if asset comment) OR hash of thread (if thread comment) to which comment belongs
    • parent: String, hash of parent comment (empty string indicates top level comment)
    • content: String, content of comment
    • html: String, rendered and sanitized html (comments support markdown rendering)
    • sticky: Boolean, if sticky comment
    • upvoted: Boolean, if username passed in as has upvoted comment
    • downvoted: Boolean, if username passed in as has downvoted comment
    • hash: String, IPFS hash of signed comment data (use as GUID)
    • author: String, username of account linked to address which signed data
    • address: String, Ethereum address that signed data
    • points: Number, net upvotes/downvotes received
    • time: Number, unix time when comment was posted

POST Signed Community Data

You might notice that you get a MetaMask popup asking to sign data using your Ethereum address whenever you create a new thread or post a comment on World's Fair. Doing it this way means your client does not have to authenticate with the our server to POST comments and threads: our backend simply calls directory(decoded_address) on the contract to find the account linked to that Ethereum address (which is saved at the author). Prior to sending the signed data, you must upload the signed comment and its signature to IPFS. The multihash that you obtain is used as the GUID of the comment or thread, which is also recorded by the server.

Importantly, the signed data includes a parent field (the IPFS hash of the parent comment, if not top-level). This gives comment trees a property that is almost blockchain-like in the sense that the hash of every comment in a thread depends on the hash of every one its ancestors (the hash of a comment depends on the hash of its parent, which in turn depends on the hash of its parent, up until the top comment is reached). This makes comment trees extremely resistant to manipulation because it's easy to compute the hash of each comment to check if it matches the hash encoded in the data of all the child comments.

So the process of posting data is bascically this: 1. A comment or thread is signed by the user's linked Ethereum address using MetaMask. 2. The signed data (which includes the IPFS hash of any parent comment) and its resulting sig are added to IPFS to obtain an IPFS of that data hash. 3. This data is posted to the server, which infers the author of the thread or comment by decoding which address was used to sign it and then looking at the blockchain to find the linked World's Fair account. If the address is not linked to any account, or if the reported IPFS hash does not actually match the hash of the data, the server will reject the request.

Our server is one gateway for this kind of open and decentralized commenting protocol. The fact that you don't have to authenticate with our server is a great illustration of how powerful a global, distributed record like the Ethereum blockchain really is... especially when data can be cryptographically linked to a real identity. Since World's Fair accounts are associated with certain types of state that are fundamentally scare (ie ether balances, asset shares) they can, to a certain extent, act as a bridge between the Ethereum blockchain and real humans—and the applications are not at all limited to World's Fair. If you wanted to build a dapp that allowed people to "sign in with World's Fair" (or otherwise authorize certain functions) it would be trivially easy to do so by prompting the user to sign { foo: 'bar' } and then sending that data to another party who would simply decode the signing address and look at the blockchain to see what account it was linked to. Being an "identity provider" is nothing new, obviously, but the advantage gained by having the blockchain act as the source of truth (rather than Google/Facebook/whatever) is that nobody not even World's Fair is in any sort of privileged position. Ultimately, it makes more sense to think of "ownership" of identity-linked data structures on the blockchain being determined solely by a private key, not by the contract in which the data happens to be defined. The bottom line is this: if you want to use World's Fair accounts for your own purposes, go for it, because we can't stop you anyway.

In the examples below, it's assumed that your dapp is using MetaMask and the World's Fair npm module:

npm install --save worldsfair

In your js, pass the web3 that you got from MetaMask to the World's Fair constructor and set the appropriate network option.

import WorldsFair from 'worldsfair';
const wf = new WorldsFair(web3, { network: 'rinkeby' });

Now you can use the helper methods addToIPFS(), signComment(), and signThread() which makes the whole process quite straightforward. Check out the following examples for posting a comment or thread, respectively:

POST /comment Post Comment

// These examples show how to post a comment on a thread or on an asset:

let threadComment;
let assetComment;

// First we need to cryptographically sign the comment data with
// user's linked Ethereum address. MetaMask will prompt this action
// with a popup to be signed by the currently selected address. There
// are two types of comments: *asset comments* and *thread comments*.
// The only distinction is whether you pass the `asset` or `thread`
// option. The examples below illustrate each case. The last thing to
// know is that for top-level comments (replying directely to the thread
// or asset instead of another comment) you should leave `parent` undefined.

try {
  
  // Comment on a thread, replying to another content
  threadComment = await wf.signComment({
    content: "This is a comment", // Content limited to 5000 chars
    thread: 'QmRvyEWWnpERD9xEkAMiCeGZM6Pvj2hYSerAqh3WwKwrVe', // IPFS hash of thread
    parent: 'QmZQ6KRJxxAhYohPJatY3NtL4GkhYFDbkCRDhn1M8XZL9b' // IPFS hash of parent comment
  });

} catch () {
  // Most likely user rejected transaction signature
}

// *OR*

try {

  // Top-level comment on an asset (no parent)
  await wf.signComment({
    content: "This is a comment",
    asset: '0x075b2dd5bae7005105135a9a82429b9b9365cad8' // Signature of asset
  });

} catch () {
  // Most likely user rejected transaction signature
}

// Let's add the first example (the thread comment) to IPFS.
// By default, World's Fair uses the public Infura IPFS gateway.
// You are free to specify your own by passing an options object
// like `{ gateway, port, protocol }` as the second parameter
// for the `addToIPFS()` helper function. For this example,
// we'll stick with the defaults.

let hash;

try {

  // Uploading the data will return an IPFS hash. You don't
  // need to keep this hash (because our server computes
  // it again anyway, but maybe you need it for something...)
  hash = await WorldsFair.addToIPFS(threadComment);

} catch () {
  // Possibly an issue with the IPFS gateway
}

// Now that the data is on IPFS, let's post it to the
// World's Fair server... no prior authentication required!

try {

  // Response returns saved comment object
  const res = await axios.post(`${API}/comment`, threadComment);

} catch () {
  // See 'Restrictions' to troubleshoot failed requests
}

Data

  • content: required, text of the comment
  • thread: optional, IPFS hash of thread
  • asset: optional, signature of asset
  • parent: optional, IPFS hash of parent comment

Restrictions

  • content must not be empty
  • content length must be less than or equal to 5000 chars
  • asset OR thread must be specified (but not both)
  • asset, if provided, must be signature of an existing asset
  • thread, if provided, must map to an existing thread
  • parent, if provided, must map to an existing comment
  • address used to sign data must be currently linked to a World's Fair account

POST /thread Post Thread

// Posting a thread is substantially the same as posting a comment.
// See the above comment examples for elaboration on the finer points.

// Sign the data
let thread;
try {
  thread = await wf.signThread({
    title: 'Title of the thread' // Limited to 160 chars
    content: "Optional self text of the thread" // Limited to 40000 chars
  });
} catch () {
  // Most likely user rejected transaction signature
}

// Add to IPFS
let hash;
try {
  hash = await WorldsFair.addToIPFS(thread);
} catch () {
  // Possibly an issue with the IPFS gateway
}

// POST to server
try {
  const res = await axios.post(`${API}/comment`, threadComment);
} catch () {
  // See 'Restrictions' to troubleshoot failed requests
}

Data

  • title: required, title of the thread
  • content: required, self text

Restrictions

  • title must not be empty
  • title length must be less than or equal to 160 chars
  • content length must be less than or equal to 40000 chars
  • address used to sign data must be currently linked to a World's Fair account

JavaScript API

Getting Started

The Easy Way

npm install --save worldsfair

The official World's Fair npm module is a just a lightweight wrapper around a vanilla web3 contract instance that comes bundled with the contract address, interface, and some static utility methods.

Pass the ethereum provider object injected by MetaMask as the first parameter in the constructor. Then set the network option to either 'rinkeby' (for the version of the contract deployed to the Rinkeby Test Network) or 'main' (for the real contract on the mainnet). Check it out:

import WorldsFair from 'worldsfair';

// Pass provider object from MetaMask as first parameter
// The network option can be 'rinkeby' or 'main'
const wf = new WorldsFair(window.ethereum, { network: 'rinkeby' });
let contract;

try {
	contract = await wf.getContractInstance(); // Get a web3 contract instance
} catch (err) {
	// something went wrong
}

// Now you can do stuff with the contract object

getContractInstance() returns a web3 contract object that you can use to send transactions or make calls to read data as you normally would. That's it.

The Slightly Less Easy Way

If you don't want to use the npm module, you can get the contract object directly from web3, but you'll need to supply the contract address and interface, which can be found here.

import web3 from 'web3';
import abi from './interface.json'; // Your path may vary

let contract;

try {
	contract = await new web3.eth.Contract(
		abi, // Interface
		'0x731C54d14d853af7f6CB587c680Efc1db11a3757' // Address
	);
} catch (err) {
	// something went wrong
}

// Now you can do stuff with the contract object

Writing World's Fair Data

The following functions allow you to write data to the World's Fair contract. You might notice that in the examples the gas property is not set when sending the transaction. This is because it is assumed that you are using the web3 object injected by MetaMask, which calculates max gas automatically. For clarity, we're also assuming that you already got accounts by calling web3.eth.getAccounts() somewhere in scope.

Create an Account

contract.methods.createAccount(
	web3.utils.asciiToHex('picklerick', 20), // username must be 3-20 chars, alphanum + dash/underscore
	accounts[1] // Recovery address is passed as string
).send({
	from: accounts[0] // Address that sends the transaction will be user's linked address
});

createAccount() creates a World's Fair account. The address used to send the transaction will become the account's linked address. The first parameter is the username—you'll want to use the asciiToHex() function that comes with web3 to convert this value to hex so it can be understood by the contract, which is expecting bytes20. It is possible to register usernames less than 3 characters in length, but they won't be indexed by the World's Fair server. More than 20 characters will be truncated.

The second parameter is the recovery address. This is optional, and you can skip it by passing the zero address. View Contract Code

Parameters

  • bytes20 username : required
  • address recovery : optional, pass zero address to skip

Restrictions

  • username must not be empty string
  • username must not match an existing account
  • recovery must not be the same as sender address
  • sender must never have used before

Create an Asset

contract.methods.createAsset(
	'QmWn3yBeu8Akm6rwC1zNvHTmvpCjfHchmBQs15YTMawu9S', // IPFS multihash
	'worldsfair.io', // Canonical link to content
	31416, // Total number of shares
	web3.utils.toWei('0.01', 'ether'), // ICO share price (in wei)
	'0xc7FF9555C8df69f923C0A72EDdbfd6a079d6D9B3', // Beneficiary address
	20, // Owner receives 20% royalties on profits
	5, // Beneficiary receives 5% royalties on profits
	(5 * 10000), // Appreciation limiting constant is 5 (must always multiply by ten thousand)
	Math.floor(Date.now() / 1000) + (60 * 60 * 24) // ICO opens in 24 hours
).send({
	from: accounts[0] // Transaction must be sent from author's linked address
});

createAsset() allows a user to create a new asset. The address used to send the transaction must be linked to a World's Fair account, which will be the owner/author of the new asset. If you name a beneficiary address, pBeneficiary may not be equal to zero. Similarly, if pBeneficiary is greater than zero, you must name a beneficiary address. Since pOwner and pBeneficiary represent percent values, their sum cannot exceed 100. View Contract Code

Parameters

  • string ipfsHash : required
  • string http : optional, can be empty string
  • uint totalShares : required, min 1, max 1 billion
  • uint initialPrice : required, min 0.000001 ETH, max 100 million ETH
  • address beneficiary : optional, pass zero address to skip
  • uint pOwner : optional, min 0, max 100
  • uint pBeneficiary : optional, min 0, max 100
  • uint icoStart : optional, pass zero for ICO to open immediately

Restrictions

  • ipfsHash must have never previously been published by this account
  • ipfsHash must not be empty string
  • totalShares must be greater than zero
  • totalShares must be less than MAX_SHARES
  • initialPrice must be greater than or equal to MIN_SHARE_PRICE
  • initialPrice must be less than or equal to MAX_SHARE_PRICE
  • sum of pOwner and pBeneficiary must be less than or equal to 100
  • limitConstant must be zero or greater than or equal to LIMIT_RESOLUTION
  • beneficiary must not be the same as sender address
  • if beneficiary is not zero address, pBeneficiary must be greater than zero
  • if pBeneficiary is greater than zero, beneficiary must not be zero address
  • sender address must be linked to an account

Buy Shares

import bigInt from 'big-integer';

// You must send enough ether to cover the cost of
// of the order. Ether held in the buyers balance
// will be applied to the cost of the order, but for
// this example let's assume the buyer's internal balance
// is zero. To avoid precision errors, please don't carry
// out calculations involving amounts of wei using JavaScript
// numbers. It's recommended that you use big-integer in such cases.

// Calculate cost of order
const sharePrice = web3.utils.toWei('0.01', 'ether');
const sharesOrdered = 1500;
const totalCost = bigInt(sharePrice).multiply(sharesOrdered).toString();

contract.methods.buyShares(
  '0xd5fa8790cb17eb20055bf59cae8d48f32dbbb149', // Signature of the asset
  web3.utils.asciiToHex('morty', 20), // Username of shareholder selling shares in bytes20
  sharesOrdered, // Number of shares to buy from this seller
  sharePrice, // Seller's asking price (in wei)
  web3.utils.toWei('0.025', 'ether'), // Buyer's new asking price (in wei)
  false // Buy whatever shares seller has left, if less than sharesOrdered
).send({
	from: accounts[0], // Transaction must be sent from buyer's linked address
	value: totalCost // Ether to pay for the shares
});

buyShares() allows a user to buy shares of an asset. Make sure to send enough ether with the transaction to cover the cost of the shares, as shown in the above example. Any extra ether that you send will be credited to the buyer's account balance. You must specify the username of the shareholder from whom you wish to buy shares. If the ICO is still ongoing (which is to say that the author has not sold all of their shares) attempts to buy shares from any user other than the author will fail.

Transactions can also fail because newAsk or sharePrice fall outside bounds set by an asset's Appreciation Limiting Constant. The last parameter is a boolean to enable 'strict' mode, meaning that the transaction should fail if the seller doesn't have enough shares to match sharesOrdered. Otherwise, the contract will complete the transaction by falling back to however many shares the seller has left. View Contract Code

Parameters payable

  • bytes20 signature : required
  • bytes20 sellerUsername : required
  • uint sharesOrdered : required
  • uint sharePrice : required
  • uint newAsk : required
  • bool strict : required

Restrictions

  • sender address must be linked to an account
  • current blocktime must be greater than or equal to than asset's icoStart
  • sender address must not be linked to sellerUsername
  • sharesOrdered must be greater than zero
  • sender address must not be linked to account that created the asset
  • if strict is true, sharesOrdered must be less than or equal to number of shares owned by shareholder matching sellerUsername at the time that the transaction is mined
  • sharePrice must equal asking price of shareholder matching sellerUsername
  • newAsk must be greater than or equal to asset's initialPrice
  • if buyer currently owns shares of this asser newAsk cannot be higher than current asking price
  • if ico is not yet complete sellerUsername must be asset author/owner
  • for asset's with an Appreciation Limiting Constant, newAsk must be less than or equal to sharePrice multiplied by the ALC and sharePrice must be less than or equal to asset's valuation per share (see contract code)
  • for asset's without an Appreciation Limiting Constant, newAsk must be less than or equal to MAX_SHARE_PRICE
  • value of transaction added to internal balance of account linked to sender address must less greater than or equal to sharesOrdered multiplied by sharePrice

Lower Asking Price

contract.methods.lowerAskingPrice(
	'0xd5fa8790cb17eb20055bf59cae8d48f32dbbb149', // Signature of the asset
	web3.utils.toWei('0.02', 'ether') // New asking price (in wei)
).send({
	from: accounts[0] // Transaction must be sent from shareholder's linked address
});

lowerAskingPrice() allows a user to lower the asking price for shares that they already hold. The asking price for shares of a given asset can never be lower than the ICO price. View Contract Code

Parameters

  • bytes20 signature : required
  • uint newAsk : required

Restrictions

  • account linked to sender address must match asset's owner
  • shareholder linked to sender address must own at least 1 share in asset
  • newAsk must be greater than or equal to asset's initialPrice
  • newAsk must be less than existing asking price

Set Canonical Link

contract.methods.setCanonicalHttp(
	'0xd5fa8790cb17eb20055bf59cae8d48f32dbbb149', // Signature of the asset
	'rinkeby.worldsfair.io' // New canonical link
).send({
	from: accounts[0] // Transaction must be sent from author's linked address
});

setCanonicalHttp() allows the owner/author to change the canonical link of their already-published asset. There is no limit (other than gas cost) on the length of the string. It's recommended that you do not include the protocol in the URL. View Contract Code

Parameters

  • bytes20 signature : required
  • string http : optional, can be empty string

Restrictions

  • account linked to sender address must match asset's owner

Change Linked Address

contract.methods.changeLinkedAddress(
	web3.utils.asciiToHex('picklerick', 20), // Username of account in bytes20
	accounts[2] // New linked address
).send({
	from: accounts[0] // Transaction must be sent from account's current linked address
});

changeLinkedAddress() allows a user to change the linked address of their account. View Contract Code

Parameters

  • bytes20 username : required
  • address newOwner : required

Restrictions

  • sender address must be account owner
  • newOwner must never have previously been linked to or used as the recovery address for any account
  • newOwner must not be zero address
  • newOwner must not be the same as existing linked address

Set Recovery Address

contract.methods.setRecoveryAddress(
	web3.utils.asciiToHex('picklerick', 20), // Username of account in bytes20
	accounts[3] // New recovery address
).send({
	from: accounts[1], // Transaction must be sent from account's linked address
});

setRecoveryAddress() allows a user to set a recovery address for their account. This function can only be called if the recovery address is not already set. The only way it can be set again is after calling recover(). This is a security feature to prevent an attacker who obtains the private to the linked address from simply using it to change the recovery address. View Contract Code

Parameters

  • bytes20 username : required
  • address recovery : required

Restrictions

  • sender address must be account owner
  • account must not already have a recovery address set
  • recovery parameter must not be zero address
  • recovery must never have previously been linked to or used as the recovery address for any account

Recover Account

contract.methods.recover(
	web3.utils.asciiToHex('picklerick', 20) // Username of account in bytes20
).send({
	from: accounts[1] // Transaction must be sent from account's recovery address
});

recover() allows a user to reassert ownership of their account in the event that they lose control of their linked address. When calling this function, the transaction must be sent from the recovery address—not the linked address. The recovery address becomes the new linked address and sets the recovery address is reset to zero, at which point the user may set a new recovery address. It's like a blockchain version of "forgot your password". View Contract Code

Parameters

  • bytes20 username : required

Restrictions

  • sender address must must match account's recovery address

Withdraw, Deposit, & Transfer Ether

Deposit Ether to Own Account

contract.methods.depositToSelf().send({
	from: accounts[1], // Transaction must be sent from account's linked address
	value: web3.utils.toWei('1', 'ether') // Deposit 1 ETH to picklerick's account
});

depositToSelf() allows a user to deposit ether to their World's Fair account balance from their linked address. There are no parameters for this function. The amount of ether you wish to deposit must be set on the value property when sending the transaction. View Contract Code

Parameters payable

None

Restrictions

  • sender address must be linked to an account
  • value of transaction must greater than zero

Deposit Ether to Other Account

contract.methods.depositToAccount(
	web3.utils.asciiToHex('morty', 20) // Username of recipient in bytes20
).send({
	from: accounts[9], // Can be deposited by any address (doesn't have to be account-linked)
	value: web3.utils.toWei('1', 'ether') // Deposit 1 ETH to morty's account
});

depositToAccount() allows any Ethereum address to deposit ether to the balance of any World's Fair account even if that address is not linked to any account. The one exception is that the linked address of an account cannot deposit ether into the account to which it's currently linked—use depositToSelf() instead (see above). The amount of ether you wish to deposit must be set on the value property when sending the transaction. View Contract Code

Parameters payable

  • bytes20 recipient : required

Restrictions

  • recipient must be an existing account
  • value of transaction must be greater than zero
  • recipient must not be the same as username of account linked to sender address

Withdraw Ether

contract.methods.withdrawFunds(
	web3.utils.toWei('0.5', 'ether') // Withdraw 0.5 ETH
).send({
	from: accounts[1] // Transaction must be sent from account's linked address
});

withdrawFunds() allows a user to withdraw ether from the balance of their World's Fair account. Obviously this function can only be called by the account's linked address. View Contract Code

Parameters

  • uint amount : required

Restrictions

  • amount must be greater than zero
  • amount must be less than or equal to internal balance of account linked to sender address

Transfer Ether Internally

contract.methods.transferFundsInternally(
	web3.utils.asciiToHex('morty', 20), // Username of recipient in bytes20
	web3.utils.toWei('0.15', 'ether') // Transfer 0.15 ETH
).send({
	from: accounts[1] // Transaction must be sent from sender account's linked address
});

transferFundsInternally() allows a user to make an internal transfer of ether directly from their World's Fair balance to the balance of another account. View Contract Code

Parameters

  • bytes20 recipient : required
  • uint amount : required

Restrictions

  • amount must be greater than zero
  • amount must be less than or equal to internal balance of account linked to sender address
  • recipient must be an existing account
  • recipient must not be the same as username of account linked to sender address

Activate Beneficiary Payout

contract.methods.payBeneficiary(
	'0xc7FF9555C8df69f923C0A72EDdbfd6a079d6D9B3' // Beneficiary address
).send({
	from: accounts[9] // Transaction can be sent from any address
});

payBeneficiary() allows any Ethereum address to trigger a beneficiary payout. View Contract Code

Parameters

  • address beneficiary : required

Restrictions

  • beneficiary funds ready for payout must be greater than zero

World's Fair State

This is where the primary, core state of World's Fair is maintained. You can read data from each of these mappings via the standard compiler-generated getter methods. It's pretty straightforward. For each example, we've included a brief description of what the data represents and, for certain mappings, various considerations you'll want to be familiar with when interpreting the data. By default, if you pass a key to the getter that does not map to anything (such as trying to lookup a nonexistent username by its linked address) your call will return whatever the zero value is for the return type in Solidity.

Asset Data

import WorldsFair from 'worldsfair';
import bigInt from 'big-integer';

const data = await contract.methods.catalogue(
  '0xd5fa8790cb17eb20055bf59cae8d48f32dbbb149' // Asset signature
).call();

console.log(data.ipfsHash); // 'QmWn3yBeu8Akm6rwC1zNvHTmvpCjfHchmBQs15YTMawu9S'
console.log(data.http); // 'worldsfair.io'
console.log(web3.utils.hexToUtf8(data.owner)); // 'picklerick'
console.log(data.beneficiary); // '0xc7FF9555C8df69f923C0A72EDdbfd6a079d6D9B3'
console.log(data.timePublished); // '1555690800'
console.log(data.totalShares); // '31416'
console.log(web3.utils.fromWei(data.initialPrice, 'ether')); // '0.01'
console.log(data.pOwner); // '20'
console.log(data.pBeneficiary); // '5'
console.log(bigInt(data.limitConstant).divide(WorldsFair.LIMIT_RESOLUTION).toString()); // '5'
console.log(web3.utils.fromWei(data.valuation)); // '15'

catalogue() returns the contract data for the asset matching the given signature. Note that for limitConstant (due to technical reasons involving integer arithmetic, see the ) the returned value is equal to the actual value of the Appreciation Limiting Constant (ALC) multiplied by LIMIT_RESOLUTION (ten thousand). In the above example, this constant is accessed from the World's Fair module as a static class property and used to divide the raw value so that that actual value of the ALC is printed to the console. View Contract Code

Parameters

  • bytes20 signature : required, signature of asset

Returns

  • string ipfsHash : ipfs multihash of content
  • string http : http link to content
  • bytes20 owner : username of author
  • address beneficiary : beneficiary address
  • uint64 timePublished : unix time of asset creation
  • uint totalShares : number of shares asset is divided into
  • uint initialPrice : ico price of shares
  • uint pOwner : percent of profits author receives as royalites
  • uint pBeneficiary : percent of profits beneficiary receives as royalites
  • uint limitConstant : appreciation limiting constant
  • uint valuation : sum of all profits generated

ICO Calendar

const icoOpen = await contract.methods.calendar(
  '0xd5fa8790cb17eb20055bf59cae8d48f32dbbb149' // Signature of asset
).call();

console.log(icoOpen); // '1524266400'

calendar() returns the unix time (according to block timestamp) when an asset's ICO opens. For assets that were set to open their ICO immeditalely upon publication, this method will simply return timePublished. View Contract Code

Paramaters

  • bytes20 signature: required, signature of asset

Returns

  • uint64: unix time when asset's shares become tradable

Account Data

const account = await contract.methods.accounts(
  web3.utils.asciiToHex('picklerick', 20) // Username of account, in bytes20
).call();

console.log(account); // { owner: '0x7fF67C7B94399a4413FE18143580c4F0885D2359', recovery: '0x58e29914027bC34D82C47a8b515d1d9572De04Ed' }

accounts() returns two Etherem addresses: owner (which is the account's linked address) and recovery (the account's recovery address, if they have set one). Note that if you just want to get the account's linked address, you can use getLinkedAddress() instead. View Contract Code

Paramaters

  • bytes20 username: required, account username

Returns

  • bytes20 owner: account's linked address
  • bytes20 recovery: account's recovery address (zero indicates no recovery address)

Account Directory

const username = await contract.methods.directory(
  '0x7fF67C7B94399a4413FE18143580c4F0885D2359' // picklerick's linked address
).call();

console.log(web3.utils.hexToUtf8(username)); // 'picklerick'

directory() returns the username of the account linked to a given address. View Contract Code

Paramaters

  • address addr: required, linked address of account

Returns

  • bytes20 username: username of account

Addresses Encountered

const encountered = await contract.methods.encountered(
  '0x7fF67C7B94399a4413FE18143580c4F0885D2359' // Some address
).call();

console.log(encountered); // true

encountered() returns true if the given address is, or has ever been, linked to an account or set as an account's recovery for this. This mapping exists as a security measure, allowing the contract to enforce a strict single use policy for all addresses (allowing address reuse, while not impossible, would greatly increase the attack surface of World's Fair) This method only returns a boolean telling you if the address has ever been used, but not by which account. If you want to find that out you can query DirRecord events. View Contract Code

Paramaters

  • address addr: required, address to check

Returns

  • bool encountered if that address has ever been used

User Balances

const balance = await contract.methods.balances(
  web3.utils.asciiToHex('picklerick', 20) // Username