Developer Guide

Prerequisites

Usage of the ConditionalTokens smart contract requires some proficiency in Solidity.

Additionally, this guide will assume a Truffle based setup. Client-side code samples will be written in JavaScript assuming the presence of a web3.js instance and various TruffleContract wrappers.

The current state of this smart contract may be found on Github.

Installation

Via NPM

This developmental framework may be installed from Github through NPM by running the following:

npm i '@gnosis.pm/conditional-tokens-contracts'

Preparing a Condition

Before conditional tokens can exist, a condition must be prepared. A condition is a question to be answered in the future by a specific oracle in a particular manner. The following function may be used to prepare a condition:

function prepareCondition(address oracle, bytes32 questionId, uint outcomeSlotCount)
external

This function prepares a condition by initializing a payout vector associated with the condition.

Parameters:
  • oracle – The account assigned to report the result for the prepared condition.
  • questionId – An identifier for the question to be answered by the oracle.
  • outcomeSlotCount – The number of outcome slots which should be used for this condition. Must not exceed 256.

Note

It is up to the consumer of the contract to interpret the question ID correctly. For example, a client may interpret the question ID as an IPFS hash which can be used to retrieve a document specifying the question more fully. The meaning of the question ID is left up to clients.

If the function succeeds, the following event will be emitted, signifying the preparation of a condition:

event ConditionPreparation(bytes32 indexed conditionId, address indexed oracle, bytes32 indexed questionId, uint outcomeSlotCount)

Emitted upon the successful preparation of a condition.

Parameters:
  • conditionId – The condition’s ID. This ID may be derived from the other three parameters via keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount)).
  • oracle – The account assigned to report the result for the prepared condition.
  • questionId – An identifier for the question to be answered by the oracle.
  • outcomeSlotCount – The number of outcome slots which should be used for this condition. Must not exceed 256.

Note

The condition ID is different from the question ID, and their distinction is important.

The successful preparation of a condition also initializes the following state variable:

mapping (bytes32 => uint[]) public payoutNumerators

Mapping key is an condition ID. Value represents numerators of the payout vector associated with the condition. This array is initialized with a length equal to the outcome slot count. E.g. Condition with 3 outcomes [A, B, C] and two of those correct [0.5, 0.5, 0]. In Ethereum there are no decimal values, so here, 0.5 is represented by fractions like 1/2 == 0.5. That’s why we need numerator and denominator values. Payout numerators are also used as a check of initialization. If the numerators array is empty (has length zero), the condition was not created/prepared. See getOutcomeSlotCount.

To determine if, given a condition’s ID, a condition has been prepared, or to find out a condition’s outcome slot count, use the following accessor:

function getOutcomeSlotCount(bytes32 conditionId)
external
view
returns (uint)

Gets the outcome slot count of a condition.

Parameters:
  • conditionId – ID of the condition.
Return:

Number of outcome slots associated with a condition, or zero if condition has not been prepared yet.

The resultant payout vector of a condition contains a predetermined number of outcome slots. The entries of this vector are reported by the oracle, and their values sum up to one. This payout vector may be interpreted as the oracle’s answer to the question posed in the condition.

A Categorical Example

Let’s consider a question where only one out of multiple choices may be chosen:

Who out of the following will be chosen?

  • Alice
  • Bob
  • Carol

Through some commonly agreed upon mechanism, the detailed description for this question becomes strongly associated with a 32 byte question ID: 0xabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc1234

Let’s also suppose we trust the oracle with address 0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF to deliver the answer for this question.

To prepare this condition, the following code gets run:

await conditionalTokens.prepareCondition(
    '0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF',
    '0xabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc1234',
    3
)

The condition ID may be determined off-chain from the parameters via web3:

web3.utils.soliditySha3({
    t: 'address',
    v: '0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF'
}, {
    t: 'bytes32',
    v: '0xabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc1234'
}, {
    t: 'uint',
    v: 3
})

A helper function for determining the condition ID also exists on both the contract and the CTHelpers library:

function getConditionId(address oracle, bytes32 questionId, uint outcomeSlotCount)
external
pure
returns (bytes32)

Constructs a condition ID from an oracle, a question ID, and the outcome slot count for the question.

Parameters:
  • oracle – The account assigned to report the result for the prepared condition.
  • questionId – An identifier for the question to be answered by the oracle.
  • outcomeSlotCount – The number of outcome slots which should be used for this condition. Must not exceed 256.

This yields a condition ID of 0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63.

Later, if the oracle 0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF makes a report that the payout vector for the condition is [0, 1, 0], the oracle essentially states that Bob was chosen, as the outcome slot associated with Bob would receive all of the payout.

A Scalar Example

Let us now consider a question where the answer may lie in a range:

What will the score be? [0, 1000]

Let’s say the question ID for this question is 0x777def777def777def777def777def777def777def777def777def777def7890, and that we trust the oracle 0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe to deliver the results for this question.

To prepare this condition, the following code gets run:

await conditionalTokens.prepareCondition(
    '0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe',
    '0x777def777def777def777def777def777def777def777def777def777def7890',
    2
)

The condition ID for this condition can be calculated as 0x3bdb7de3d0860745c0cac9c1dcc8e0d9cb7d33e6a899c2c298343ccedf1d66cf.

In this case, the condition was created with two slots: one which represents the low end of the range (0) and another which represents the high end (1000). The slots’ reported payout values should indicate how close the answer was to these endpoints. For example, if the oracle 0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe makes a report that the payout vector is [9/10, 1/10], then the oracle essentially states that the score was 100, as the slot corresponding to the low end is worth nine times what the slot corresponding with the high end is worth, meaning the score should be nine times closer to 0 than it is close to 1000. Likewise, if the payout vector is reported to be [0, 1], then the oracle is saying that the score was at least 1000.

Outcome Collections

The main concept for understanding the mechanics of this system is that of a position. We will build to this concept from conditions and outcome slots, and then demonstrate the use of this concept.

However, before we can talk about positions, we first have to talk about outcome collections, which may be defined like so:

A nonempty proper subset of a condition’s outcome slots which represents the sum total of all the contained slots’ payout values.

Categorical Example Featuring Alice, Bob, and Carol

We’ll denote the outcome slots for Alice, Bob, and Carol as A, B, and C respectively.

A valid outcome collection may be (A|B). In this example, this outcome collection represents the eventuality in which either Alice or Bob is chosen. Note that for a categorical condition, the payout vector which the oracle reports will eventually contain a one in exactly one of the three slots, so the sum of the values in Alice’s and Bob’s slots is one precisely when either Alice or Bob is chosen, and zero otherwise.

(C) by itself is also a valid outcome collection, and this simply represents the case where Carol is chosen.

() is an invalid outcome collection, as it is empty. Empty outcome collections do not make sense, as they would essentially represent no eventuality and have no value no matter what happens.

Conversely, (A|B|C) is also an invalid outcome collection, as it is not a proper subset. Outcome collections consisting of all the outcome slots for a condition also do not make sense, as they would simply represent any eventuality, and should be equivalent to whatever was used to collateralize these outcome collections.

Finally, outcome slots from different conditions (e.g. (A|X)) cannot be composed in a single outcome collection.

Index Set Representation and Identifier Derivation

A outcome collection may be represented by an a condition and an index set. This is a 256 bit array which denotes which outcome slots are present in a outcome collection. For example, the value 3 == 0b011 corresponds to the outcome collection (A|B), whereas the value 4 == 0b100 corresponds to (C). Note that the indices start at the lowest bit in a uint.

A outcome collection may be identified with a 32 byte value called a collection identifier. Calculating the collection ID for an outcome collection involves hashing its condition ID and index set into a point on the alt_bn128 elliptic curve.

Note

In order to calculate the collection ID for (A|B), the following steps must be performed.

  1. An initial value for the point x-coordinate is set by hashing the condition ID and the index set of the outcome collection, and interpreting the resulting hash as a big-endian integer.

    web3.utils.soliditySha3({
        // See section "A Categorical Example" for derivation of this condition ID
        t: 'bytes32',
        v: '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63'
    }, {
        t: 'uint',
        v: 0b011 // Binary Number literals supported in newer versions of JavaScript
    })
    

    This results in an initial x-coordinate of 0x52ff54f0f5616e34a2d4f56fb68ab4cc636bf0d92111de74d1ec99040a8da118, or 37540785828268254412066351790903087640191294994197155621611396915481249947928.

    An odd flag is set according to whether the highest bit of the hash result is set. In this case, because the highest bit of the hash result is not set,``odd = false``.

  2. The x-coordinate gets incremented by one modulo the order of the alt_bn128 base field, which is 21888242871839275222246405745257275088696311157297823662689037894645226208583.

    The first time, this results in an updated x-coordinate x = 15652542956428979189819946045645812551494983836899331958922359020836023739346.

  3. The x-coordinate is checked to see if it is the x-coordinate of points on the elliptic curve. Specifically, x**3 + 3 gets computed in the base field, and if the result is a quadratic residue, the x-coordinate belongs to a pair of points on the elliptic curve. If the result is a non-residue however, return to step 2.

    When x = 15652542956428979189819946045645812551494983836899331958922359020836023739346, x**3 + 3 == 7181824697751204416624405172148440000524665091599802536460745194285959874882 is not a quadratic residue in the base field, so go back to step 2.

    When x = 15652542956428979189819946045645812551494983836899331958922359020836023739347, x**3 + 3 == 19234863727839675005817902755221636205208068129817953505352549927470359854418 is also not a quadratic residue in the base field, so go back to step 2.

    When x = 15652542956428979189819946045645812551494983836899331958922359020836023739348, x**3 + 3 == 15761946137305644622699047885883332275379818402942977914333319312444771227121 is still not a quadratic residue in the base field, so go back to step 2.

    When x = 15652542956428979189819946045645812551494983836899331958922359020836023739349, x**3 + 3 == 18651314797988388489514246309390803299736227068272699426092091243854420201580 is a quadratic residue in the base field, so we have found a pair of points on the curve, and we may continue.

  4. Note that the base field occupies 254 bits of space, meaning the x-coordinate we found also occupies 254 bits of space, and has two free bits in an EVM word (256 bits). Leave the highest bit unset, and set the next highest bit if odd == true. In our example, odd is unset, so we’re done, and the collection ID for (A|B) is 15652542956428979189819946045645812551494983836899331958922359020836023739349, or 0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5.

We may also combine collection IDs for outcome collections for different conditions by performing elliptic curve point addition on them.

Note

Let’s denote the slots for range ends 0 and 1000 from our scalar condition example as LO and HI. We can find the collection ID for (LO) to be 0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2 using the procedure illustrated in the previous note.

The combined collection ID for (A|B)&(LO) can be calculated in the following manner:

  1. Decompress the constituent collection IDs into elliptic curve point coordinates. Take the low 254 bits as the x-coordinate, and pick the y-coordinate which is even or odd depending on the value of the second highest bit.

    • (A|B), which has a collection ID of 0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5, gets decompressed to the point:

      (15652542956428979189819946045645812551494983836899331958922359020836023739349,
      11459896044816691076313215195950563425899182565928550352639564868174527712586)
      

      Note the even y-coordinate is chosen here.

    • (LO), which has a collection ID of 0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2, gets decompressed to the point:

      (9970120961273109372766525305441055537695652051815636823675568206550524069826,
      5871835597783351455285190273403665696556137392019654883787357811704360229175)
      

      The odd y-coordinate indication bit was chopped off the compressed form before its use as the decompressed form’s x-coordinate, and the odd y-coordinate is chosen here.

  2. Perform point addition on the alt_bn128 curve with these points. The sum of these points is the point:

    (21460418698095194776649446887647175906168566678584695492252634897075584178441,
    4596536621806896659272941037410436605631447622293229168614769592376282983323)
    
  3. Compress the result by taking the x-coordinate, and setting the second highest bit, which should be just outside the x-coordinate, depending on whether the y-coordinate was odd. The combined collection ID for (A|B)&(LO) is 0x6f722aa250221af2eba9868fc9d7d43994794177dd6fa7766e3e72ba3c111909.

Warning

Both bitwise XOR and truncated addition is not used in this scenario because these operations are vulnerable to collisions via a generalized birthday attack.

Similar to with conditions, the contract and the CTHelpers library also provide helper functions for calculating outcome collection IDs:

function getCollectionId(bytes32 parentCollectionId, bytes32 conditionId, uint indexSet)
external
view
returns (bytes32)

Constructs an outcome collection ID from a parent collection and an outcome collection.

Parameters:
  • parentCollectionId – Collection ID of the parent outcome collection, or bytes32(0) if there’s no parent.
  • conditionId – Condition ID of the outcome collection to combine with the parent outcome collection.
  • indexSet – Index set of the outcome collection to combine with the parent outcome collection.

Defining Positions

In order to define a position, we first need to designate a collateral token. This token must be an ERC20 token which exists on the same chain as the ConditionalTokens instance.

Then we need at least one condition with a outcome collection, though a position may refer to multiple conditions each with an associated outcome collection. Positions become valuable precisely when all of its constituent outcome collections are valuable. More explicitly, the value of a position is a product of the values of those outcome collections composing the position.

With these ingredients, position identifiers can also be calculated by hashing the address of the collateral token and the combined collection ID of all the outcome collections in the position. We say positions are deeper if they contain more conditions and outcome collections, and shallower if they contain less.

As an example, let’s suppose that there is an ERC20 token called DollaCoin which exists at the address 0xD011ad011ad011AD011ad011Ad011Ad011Ad011A, and it is used as collateral for some positions. We will denote this token with $.

We may calculate the position ID for the position $:(A|B) via:

web3.utils.soliditySha3({
    t: 'address',
    v: '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A'
}, {
    t: 'bytes32',
    v: '0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5'
})

The ID for $:(A|B) turns out to be 0x5355fd8106a08b14aedf99935210b2c22a7f92abaf8bb00b60fcece1032436b7.

Similarly, the ID for $:(LO) can be found to be 0x1958e759291b2bde460cdf2158dea8d0f5c4e22c77ecd09d3ca6a36f01616e02, and $:(A|B)&(LO) has an ID of 0x994b964b94eb15148726de8caa08cac559ec51a90fcbc9cc19aadfdc809f34c9.

Helper functions for calculating positions also exist:

function getPositionId(IERC20 collateralToken, bytes32 collectionId)
external
pure
returns (uint)

Constructs a position ID from a collateral token and an outcome collection. These IDs are used as the ERC-1155 ID for this contract.

Parameters:
  • collateralToken – Collateral token which backs the position.
  • collectionId – ID of the outcome collection associated with this position.

All the positions backed by DollaCoin which depend on the example categorical condition and the example scalar condition form a DAG (directed acyclic graph):

DAG of every position which can be made from DollaCoin and the two example conditions, where the nodes are positions, edges are colored by condition, and directionality is implied with vertical spacing.

Graph of all positions backed by $ which are contingent on either or both of the example conditions.

Splitting and Merging Positions

Once conditions have been prepared, stake in positions contingent on these conditions may be obtained. Furthermore, this stake must be backed by collateral held by the contract. In order to ensure this is the case, stake in shallow positions may only be minted by sending collateral to the contract for the contract to hold, and stake in deeper positions may only be created by burning stake in shallower positions. Any of these is referred to as splitting a position, and is done through the following function:

function splitPosition(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] calldata partition, uint amount)
external

This function splits a position. If splitting from the collateral, this contract will attempt to transfer amount collateral from the message sender to itself. Otherwise, this contract will burn amount stake held by the message sender in the position being split worth of EIP 1155 tokens. Regardless, if successful, amount stake will be minted in the split target positions. If any of the transfers, mints, or burns fail, the transaction will revert. The transaction will also revert if the given partition is trivial, invalid, or refers to more slots than the condition is prepared with.

Parameters:
  • collateralToken – The address of the positions’ backing collateral token.
  • parentCollectionId – The ID of the outcome collections common to the position being split and the split target positions. May be null, in which only the collateral is shared.
  • conditionId – The ID of the condition to split on.
  • partition – An array of disjoint index sets representing a nontrivial partition of the outcome slots of the given condition. E.g. A|B and C but not A|B and B|C (is not disjoint). Each element’s a number which, together with the condition, represents the outcome collection. E.g. 0b110 is A|B, 0b010 is B, etc.
  • amount – The amount of collateral or stake to split.

If this transaction does not revert, the following event will be emitted:

event PositionSplit(address indexed stakeholder, IERC20 collateralToken, bytes32 indexed parentCollectionId, bytes32 indexed conditionId, uint[] partition, uint amount)

Emitted when a position is successfully split.

To decipher this function, let’s consider what would be considered a valid split, and what would be invalid:

Various valid and invalid splits of positions.

Details for some of these scenarios will follow

Basic Splits

Collateral $ can be split into conditional tokens in positions $:(A), $:(B), and $:(C). To do so, use the following code:

const amount = 1e18 // could be any amount

// user must allow conditionalTokens to
// spend amount of DollaCoin, e.g. through
// await dollaCoin.approve(conditionalTokens.address, amount)

await conditionalTokens.splitPosition(
    // This is just DollaCoin's address
    '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
    // For splitting from collateral, pass bytes32(0)
    '0x0000000000000000000000000000000000000000000000000000000000000000',
    // "Choice" condition ID:
    // see A Categorical Example for derivation
    '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
    // Each element of this partition is an index set:
    // see Outcome Collections for explanation
    [0b001, 0b010, 0b100],
    // Amount of collateral token to submit for holding
    // in exchange for minting the same amount of
    // conditional token in each of the target positions
    amount,
)

The effect of this transaction is to transfer amount DollaCoin from the message sender to the conditionalTokens to hold, and to mint amount of conditional token for the following positions:

  • $:(A)
  • $:(B)
  • $:(C)

Note

The previous example, where collateral was split into shallow positions containing collections with one slot each, is similar to Event.buyAllOutcomes from Gnosis’ first prediction market contracts.

The set of (A), (B), and (C) is not the only nontrivial partition of outcome slots for the example categorical condition. For example, the set (B) (with index set 0b010) and (A|C) (with index set 0b101) also partitions these outcome slots, and consequently, splitting from $ to $:(B) and $:(A|C) is also valid and can be done with the following code:

await conditionalTokens.splitPosition(
    '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
    '0x0000000000000000000000000000000000000000000000000000000000000000',
    '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
    // This partition differs from the previous example
    [0b010, 0b101],
    amount,
)

This transaction also transfers amount DollaCoin from the message sender to the conditionalTokens to hold, but it mints amount of conditional token for the following positions instead:

  • $:(B)
  • $:(A|C)

Warning

If non-disjoint index sets are supplied to splitPosition, the transaction will revert.

Partitions must be valid partitions. For example, you can’t split $ to $:(A|B) and $:(B|C) because (A|B) (0b011) and (B|C) (0b110) share outcome slot B (0b010).

Splits to Deeper Positions

It’s also possible to split from a position, burning conditional tokens in that position in order to acquire conditional tokens in deeper positions. For example, you can split $:(A|B) to target $:(A|B)&(LO) and $:(A|B)&(HI):

await conditionalTokens.splitPosition(
    // Note that we're still supplying the same collateral token
    // even though we're going two levels deep.
    '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
    // Here, instead of just supplying 32 zero bytes, we supply
    // the collection ID for (A|B).
    // This is NOT the position ID for $:(A|B)!
    '0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5',
    // This is the condition ID for the example scalar condition
    '0x3bdb7de3d0860745c0cac9c1dcc8e0d9cb7d33e6a899c2c298343ccedf1d66cf',
    // This is the only partition that makes sense
    // for conditions with only two outcome slots
    [0b01, 0b10],
    amount,
)

This transaction burns amount of conditional token in position $:(A|B) (position ID 0x5355fd8106a08b14aedf99935210b2c22a7f92abaf8bb00b60fcece1032436b7) in order to mint amount of conditional token in the following positions:

  • $:(A|B)&(LO)
  • $:(A|B)&(HI)

Because the collection ID for (A|B)&(LO) is just the sum of the collection IDs for (A|B) and (LO), we could have split from (LO) to get (A|B)&(LO) and (C)&(LO):

await conditionalTokens.splitPosition(
    '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
    // The collection ID for (LO).
    // This collection contains an outcome collection from the example scalar condition
    // instead of from the example categorical condition.
    '0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2',
    // This is the condition ID for the example categorical condition
    // as opposed to the example scalar condition.
    '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
    // This partitions { A, B, C } into [{ A, B }, { C }]
    [0b011, 0b100],
    amount,
)

The $:(A|B)&(LO) position reached is the same both ways.

There is a single class of conditional tokens which resolves to collateral if Alice gets chosen and the score is high.

There are many ways to split to a deep position.

Splits on Partial Partitions

Supplying a partition which does not cover the set of all outcome slots for a condition, but instead some outcome collection, is also possible. For example, it is possible to split $:(B|C) (position ID 0x5d06cd85e2ff915efab0e7881432b1c93b3e543c5538d952591197b3893f5ce3) to $:(B) and $:(C):

await conditionalTokens.splitPosition(
    '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
    // Note that we also supply zeroes here, as the only aspect shared
    // between $:(B|C), $:(B) and $:(C) is the collateral token
    '0x0000000000000000000000000000000000000000000000000000000000000000',
    '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
    // This partition does not cover the first outcome slot
    [0b010, 0b100],
    amount,
)

Merging Positions

Merging positions does precisely the opposite of what splitting a position does. It burns conditional tokens in the deeper positions to either mint conditional tokens in a shallower position or send collateral to the message sender:

A couple examples of merging positions.

Splitting positions, except with the arrows turned around.

To merge positions, use the following function:

function mergePositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] calldata partition, uint amount)
external

If successful, the function will emit this event:

event PositionsMerge(address indexed stakeholder, IERC20 collateralToken, bytes32 indexed parentCollectionId, bytes32 indexed conditionId, uint[] partition, uint amount)

Emitted when positions are successfully merged.

Note

This generalizes sellAllOutcomes from Gnosis’ first prediction market contracts like splitPosition generalizes buyAllOutcomes.

Querying and Transferring Stake

The ConditionalTokens contract implements the ERC1155 multitoken interface. In addition to a holder address, each token is indexed by an ID in this standard. In particular, position IDs are used to index conditional tokens. This is reflected in the balance querying function:

function balanceOf(address owner, uint256 positionId)
external
view
returns (uint256)

To transfer conditional tokens, the following functions may be used, as per ERC1155:

function safeTransferFrom(address from, address to, uint256 positionId, uint256 value, bytes data)
external
function safeBatchTransferFrom(address from, address to, uint256[] positionIds, uint256[] values, bytes data)
external

These transfer functions ignore the data parameter.

Note

When sending to contract accounts, transfers will be rejected unless the recipient implements the ERC1155TokenReceiver interface and returns the expected magic values. See the ERC1155 multitoken spec for more information.

Approving an operator account to transfer conditional tokens on your behalf may also be done via:

function setApprovalForAll(address operator, bool approved)
external

Querying the status of approval can be done with:

function isApprovedForAll(address owner, address operator)
external
view
returns (bool)

Redeeming Positions

Before this is possible, the payout vector must be set by the oracle:

function reportPayouts(bytes32 questionId, uint[] calldata payouts)
external

Called by the oracle for reporting results of conditions. Will set the payout vector for the condition with the ID keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount)), where oracle is the message sender, questionId is one of the parameters of this function, and outcomeSlotCount is the length of the payouts parameter, which contains the payoutNumerators for each outcome slot of the condition.

Parameters:
  • questionId – The question ID the oracle is answering for
  • payouts – The oracle’s answer

This will emit the following event:

event ConditionResolution(bytes32 indexed conditionId, address indexed oracle, bytes32 indexed questionId, uint outcomeSlotCount, uint[] payoutNumerators)

Then positions containing this condition can be redeemed via:

function redeemPositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] calldata indexSets)
external

This will trigger the following event:

event PayoutRedemption(address indexed redeemer, IERC20 indexed collateralToken, bytes32 indexed parentCollectionId, bytes32 conditionId, uint[] indexSets, uint payout)

Also look at this chart:

Oracle reporting and corresponding redemption rates.