README
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.
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
- Getting Started
- Writing World's Fair Data
- Withdraw, Deposit, & Transfer Ether
- World's Fair State
- Detecting Events
- Read Data Helper Fuctions
- Get Current Maximum Buy Price
- Get Maximum New Asking Price
- Check If Account Exists
- Check If Address Is Linked
- Get Linked Address Matching Username
- Get Username Matching Linked Address
- Get Recovery Address of Account
- Check If Asset Exists
- Get Asset Model
- Derive Asset Signature
- Get Shareholder Model
- Check If ICO Complete
- Arbitrary Data
Solidity API / Contract Code
- Global Constants
- Data Structures
- Event Logs
- World's Fair State
- Primary Writable API
- Primary Readable API
- Internal Helper Functions
- Withdraw, Deposit, and Transfer Ether
- Arbitrary State
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 /globalGlobal 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
Object
Returns total
: String, total valuation, sum of all profitsfigures
: Objectcreators
: String, wei paid to creators in icos and royaltiesshareholders
: String, wei paid to investors from tradingbeneficiaries
: String, wei paid to charity
content
: Object, number of assets tagged as each typevideo / film
: Numberaudio / music
: Numbervisual / design
: Numberwords / language
: Numberpodcast / spoken
: Numbersource / code
: Numberproject / tech
: Numberpeace / love
: Number
GET /searchSearch 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
[Object]
Returns Object
accounttype
: String,account
username
: String, username of the accountaddress
: String, account's linked address
Object
assettype
: String,asset
signature
: String, signature of assettitle
: String, title of assetowner
: String, username of author accountvaluation
: String, current valuation of asset in weitime
: Number, timestamp of block when asset was published
GET /assetAssets
// 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 assetowner
: optional, get assets created by accountsort
: optional, order results byrecent
(recently created),ico
(upcoming ico),watching
(number of accounts watching),investment
(highest valuation first), orinvestment24h
(most profits generated in last 24 hours)match
: optional, return assets matchingany
orall
tagstags
: optional, uri-encoded array of up to 8 tags to matchmode
: optional, ifstrict
exclude assets without proper metaskip
: optional, number of results to skip (for pagination)
Object
Returns endOfList
: Boolean, true if returned results represent all matches for queryassets
: [Object], array of asset objectsObject
assettitle
: String, asset titledescription
: String, asset descriptiondescriptionHtml
: String, rendered and sanitized description htmltags
: [String], array of tagsembed
: String, http platform (for content embedding)signature
: String, asset signatureipfsHash
: String, IPFS hash of contenthttp
: String, canonical link to contentowner
: String, username of author accountbeneficiary
: String, beneficiary Ethereum addresstimePublished
Number, unix time when asset was createdtotalShares
: Number, how many shares asset is tokenized intopOwner
: Number, percent or profits that owner receives as royalitespBeneficiary
: Number, percent of profits sent to beneficiarybeneficiaryName
: String, human-readable name for known beneficiary addressescontributed
: String, amount asset has contributed to beneficiary to datelimitConstant
: Number, Appreciation Limiting Constant (ALC) x 10000icoOpen
: 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 weiwatching
: Number, number of accounts watching this assetipfsValid
: Boolean, true ifipfsHash
is a valid IPFS multihashvaluation
: String, asset's total valuation (sum of all profits in wei)v24h
: String, sum of profits in last 24 hours in wei
GET /shareholderAsset 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
shareholderaccount
: String, username of shareholder accountshares
: Number, how many shares currently owned by shareholderask
: String, shareholders current asking price in weiweiIn
: String, total wei account has ever spent on shares of this assetweiOut
: String, total wei account has ever received as revenue from selling shares of this assetlastBuy
: Number, unix time when shareholder last acquired sharesaddress
: String, Ethereum address currently linked to shareholder account
GET /asset/existsCheck 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 accountipfs
: required, ipfs multihash of content
Boolean
Returns
GET /asset/transactionsAsset 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
[Object]
Returns Object
transactionseller
: String, username of account that sold sharesbuyer
: String, username of account that bought sharesshares
: Number, how many sharesbuyPrice
: String, share price in wei buyer paidnewAsk
: String, new share price in wei buyer is askingtime
: Number, unix time of transactiontransactionHash
: String, hash of transactiontransactionIndex
: Number, index of transaction in blockblockNumber
: Number, block number transaction was included in
GET /beneficiary/listList 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, passwhitelisted
to only get admin-reviewed resultssort
: optional, can becumulative
(highest total contributions first) orcurrent
(highest balance ready for payout first)skip
: optional, number of results to skip
Object
Returns endOfList
: Boolean, true if returned results represent all matches for querybeneficiaries
: [Object], array of beneficiary objectsObject
: beneficiaryaddress
: String, Ethereum address of beneficiarysupporting
: Number, how many assets named this address as beneficiarydomain
: String, link to main website of beneficiaryname
: String, human readable name of beneficiary, if knownlink
: String, link to webpage where donation address was sourcedcurrent
: String, amount of wei ready for payoutcumulative
: String, cumulative amount of wei ever raisedupdated
: Number, when the beneficiary's info was added or last updated
GET /beneficiary/dataBeneficiary 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
Object
Returns name
: String, human readable name of beneficiary, if knownlink
: String, link to webpage where donation address was sourceddomain
: String, link to main website of beneficiaryknown
: Boolean, true if meta data has been manually added by adminssupporting
: Number, how many assets named this address as beneficiarycurrent
: String, amount of wei ready for payoutcumulative
: String, cumulative amount of wei ever raised
GET /beneficiary/transactionsBeneficiary 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 addressbefore
: optional, unix time for which to omit results that are timestamped laterskip
: optional, number of results to skip
Object
Returns endOfList
: Boolean, true if returned results represent all matches for queryevents
: [Object], array of beneficiary eventsObject
: credit eventevent
: String,credit
asset
: String, signature of asset in transaction resulting in creditamount
: String, amount credited in weibeneficiary
: String, Ethereum address of beneficiarytime
: Number, unix time of credit
Object
: payout eventevent
: String,payout
triggeredBy
: String, username of account (if any) linked to address that triggered payoutamount
: String, amount of wei paid to beneficiarybeneficiary
: String, recipient Ethereum addresstime
: Number, unix time of payoutnumber
: Number, index of payout
GET /account/availableCheck 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
Boolean
Returns
GET /account/portfolioUser'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 accountsort
: optional, order results by
Object
Returns stats
consensusValue
: String, total consensus value of holdingstotalInvested
: String, total wei invested in asset shareschange24h
: String, total change in consensus value of holdings in the last 24 hourscharity
: String, total wei contributed to beneficiaries from account's holdingsnetProfit
: String, amount in wei that total revenue exceeds total investment for all holdings (can be negative)
holdings
: [Object], array of holdingsObject
portfolio itemask
: String, account's current asking price in weiasset
: String, signature of assettitle
: String, title of assetshares
: Number, how many shares account ownsweiIn
: String, total amount (in wei) account has ever spent on shares of this assetweiOut
: String, total amount (in wei) account has ever recieved in as revenue from sale of sharestotalShares
: Number, total number of shares asset is divided intoinitialPrice
: String, ICO price of asset in weilimitConstant
: Number, asset's Appreciation Limiting Constant (ALC) x 10000pOwner
: Number, percent of profits (in addition to ICO revenue) sent to author/ownerpBeneficiary
: 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 assetlastSell
: Number, unix time when account most recently sold sharesv24h
: String, amount of wei consensus value of shares has increased in the last 24 hourspercentStake
: Number, approximate percent of asset's shares owner by accountconsensusValue
: String, consensus value of shares in weicontentType
: String, content type of asset
GET /account/dataUser'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
Object
Returns address
: String, account's linked addressfollowers
: Number, how may accounts are following this accountcreated
: Number, uinx time account was created
GET /threadThreads
// 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 fetchauthor
: optional, fetch all threads by certain account (pass username)before
: optional, unix time for which to omit results that are timestamped lateras
: optional, username of account making request (ie allows server to setupvoted
anddownvoted
for signed in user)
Object
Returns endOfList
: Boolean, true if returned results represent all matches for querythreads
: [Object], array of thread objectsObject
: threadhash
: String, IPFS hash of signed thread data (use as GUID)author
: String, username of account linked to address which signed dataaddress
: String, Ethereum address that signed datatitle
: String, thread titlecontent
: String, selftext of thread, if anyhtml
: String, rendered and sanitized html (threads support markdown rendering)announcement
: Boolean, if announcement threadsticky
: Boolean, if sticky threadupvoted
: Boolean, if username passed inas
parameter has upvoted threaddownvoted
: Boolean, if username passed inas
parameter has downvoted threadpoints
: Number, net upvotes/downvotes receivedcomments
: Number, how many comments on this threadtime
: Number, unix time when thread was posted
GET /commentComments
// 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 betop
(default, most points first) ornew
(most recent)asset
: required (only ifthread
orauthor
not specified), signature of asset to fetch comments forthread
: required (only ifasset
orauthor
not specified), hash of thread to fetch comments forauthor
: required (only ifasset
orthread
not specified), username of account to fetch comments foras
: optional, username of account making request (ie allows server to setupvoted
anddownvoted
for signed in user)
[Object]
Returns Object
commentasset
ORthread
: String, signature of asset (if asset comment) OR hash of thread (if thread comment) to which comment belongsparent
: String, hash of parent comment (empty string indicates top level comment)content
: String, content of commenthtml
: String, rendered and sanitized html (comments support markdown rendering)sticky
: Boolean, if sticky commentupvoted
: Boolean, if username passed inas
has upvoted commentdownvoted
: Boolean, if username passed inas
has downvoted commenthash
: String, IPFS hash of signed comment data (use as GUID)author
: String, username of account linked to address which signed dataaddress
: String, Ethereum address that signed datapoints
: Number, net upvotes/downvotes receivedtime
: 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:
- A comment or thread is signed by the user's linked Ethereum address using MetaMask.
- 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 datahash
. - 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 /commentPost 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 commentthread
: optional, IPFS hash of threadasset
: optional, signature of assetparent
: optional, IPFS hash of parent comment
Restrictions
content
must not be emptycontent
length must be less than or equal to 5000 charsasset
ORthread
must be specified (but not both)asset
, if provided, must be signature of an existing assetthread
, if provided, must map to an existing threadparent
, if provided, must map to an existing comment- address used to sign data must be currently linked to a World's Fair account
POST /threadPost 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 threadcontent
: required, self text
Restrictions
title
must not be emptytitle
length must be less than or equal to 160 charscontent
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
: requiredaddress recovery
: optional, pass zero address to skip
Restrictions
username
must not be empty stringusername
must not match an existing accountrecovery
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
: requiredstring http
: optional, can be empty stringuint totalShares
: required, min 1, max 1 billionuint initialPrice
: required, min 0.000001 ETH, max 100 million ETHaddress beneficiary
: optional, pass zero address to skipuint pOwner
: optional, min 0, max 100uint pBeneficiary
: optional, min 0, max 100uint icoStart
: optional, pass zero for ICO to open immediately
Restrictions
ipfsHash
must have never previously been published by this accountipfsHash
must not be empty stringtotalShares
must be greater than zerototalShares
must be less thanMAX_SHARES
initialPrice
must be greater than or equal toMIN_SHARE_PRICE
initialPrice
must be less than or equal toMAX_SHARE_PRICE
- sum of
pOwner
andpBeneficiary
must be less than or equal to 100 limitConstant
must be zero or greater than or equal toLIMIT_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
payable
Parameters bytes20 signature
: requiredbytes20 sellerUsername
: requireduint sharesOrdered
: requireduint sharePrice
: requireduint newAsk
: requiredbool 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 matchingsellerUsername
at the time that the transaction is mined sharePrice
must equal asking price of shareholder matchingsellerUsername
newAsk
must be greater than or equal to asset'sinitialPrice
- 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 tosharePrice
multiplied by the ALC andsharePrice
must be less than or equal to asset'svaluation
per share (see contract code) - for asset's without an Appreciation Limiting Constant,
newAsk
must be less than or equal toMAX_SHARE_PRICE
- value of transaction added to internal balance of account linked to sender address must less greater than or equal to
sharesOrdered
multiplied bysharePrice
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
: requireduint 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'sinitialPrice
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
: requiredstring 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
: requiredaddress 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 accountnewOwner
must not be zero addressnewOwner
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
: requiredaddress recovery
: required
Restrictions
- sender address must be account
owner
- account must not already have a recovery address set
recovery
parameter must not be zero addressrecovery
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
payable
Parameters 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
payable
Parameters 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 zeroamount
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
: requireduint amount
: required
Restrictions
amount
must be greater than zeroamount
must be less than or equal to internal balance of account linked to sender addressrecipient
must be an existing accountrecipient
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 contentstring http
: http link to contentbytes20 owner
: username of authoraddress beneficiary
: beneficiary addressuint64 timePublished
: unix time of asset creationuint totalShares
: number of shares asset is divided intouint initialPrice
: ico price of sharesuint pOwner
: percent of profits author receives as royalitesuint pBeneficiary
: percent of profits beneficiary receives as royalitesuint limitConstant
: appreciation limiting constantuint 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 addressbytes20 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