Market makers (often known as “bookmakers”) strive to take many bets that will offset each other while collecting a percentage of the bets as their compensation. Typically, bookmakers take bets sequentially, ever mindful of whether the bets are offsetting each other. If the bets begin to represent a risk due to imbalance, the bookmaker may need to adjust their actions. For instance, they can change the odds to try to bring the booked bets into balance.

A bookmaker acts as a market maker by participating in bets themselves, typically in anticipation of future bets that will offset the current bets. By taking many bets concurrently, there is more opportunity to keep the books in balance (by rejecting bets if they clearly would skew the betting).

This contract will support an off-chain market maker for prediction markets. Exactly like the
clearinghouse contract,
trade offers (bets) will be proposed off-chain with signed messages.

Why Not Just Use the Clearinghouse Contract?

Given that the token-based prediction markets simply exchange tokens, and the clearinghouse contract handles off-chain token trading, why would a prediction market bookmaker need anything more? It’s because prediction market tokens can be bought and sold in bundles for a fixed price. A market maker can exploit this special property to accept bets that would otherwise have been impossible. An example will help.

Assume the following two independent prediction markets exist, with each winning token worth 1 (wrapped) ETH:

The NFC Championship, with two securities/tokens: COWBOYS and PACKERS.

The AFC Championship, with two securities: PATRIOTS and STEELERS.

Further, assume that the following two trades are proposed:

Alice would like to trade 1,000 COWBOYS tokens for 1,000 PATRIOTS tokens.

Bob would like to trade 1,000 PACKERS tokens for 1,000 STEELERS tokens.

A market maker contract can exploit the ability to buy and refund complete bundles of tokens to satisfy these exchanges by doing the following:

The market maker takes possession of the 1,000 COWBOYS and PACKERS tokens from Alice and Bob.

The market maker uses refundBundle on the NFC prediction market to exchange those for 1,000 wrapped ETH tokens.

Buying Bundles

Buying bundles is straightforward. The contract approves transfer of payment tokens to the prediction market, it buys the bundle with those tokens, and then it puts all the bought tokens into the contract owner’s escrow accounts:

functionbuyBundle(TokenPredictionMarketpm,uint256amount)internal{// approve transfer of payment tokensIERC20Tokenwt=pm.wagertoken();wt.approve(pm,amount);escrowBalance[owner][wt]-=amount;// buy the bundlepm.buyBundle(amount);// transfer bundle from this to owner and adjust escrowuint256length=pm.outcomeCount();for(uint256i=0;i<length;i++){IERC20Tokent=IERC20Token(pm.tokens(i));escrowBalance[owner][t]+=amount;}}

Refunding Bundles

Similar logic holds for the refunding of bundles:

functionrefundBundle(TokenPredictionMarketpm,uint256amount)internal{// approve transfer of bundle and adjust escrowuint256length=pm.outcomeCount();for(uint256i=0;i<length;i++){IERC20Tokent=IERC20Token(pm.tokens(i));t.approve(pm,amount);escrowBalance[owner][t]-=amount;}// refund the bundlepm.refundBundle(amount);// account for the received tokens in owner's escrow accountIERC20Tokenwt=pm.wagertoken();escrowBalance[owner][wt]+=amount;}

Summary

An off-chain market maker for prediction markets can be supported with a smart contract.

The smart contract is a simple adaptation of the clearinghouse contract.

The market maker can exploit the buying and refunding of bundles to accept more bets.

The Complete Contract

bookmaker.sol

pragma solidity^0.4.24;import"clearinghouse.sol";import"wagertokenpredictionmarket.sol";contractBookmakerisClearinghouse{constructor(uint256_escrowTime)Clearinghouse(_escrowTime)public{}functionbuyBundle(TokenPredictionMarketpm,uint256amount)internal{// approve transfer of payment tokensIERC20Tokenwt=pm.wagertoken();wt.approve(pm,amount);escrowBalance[owner][wt]-=amount;// buy the bundlepm.buyBundle(amount);// transfer bundle from this to owner and adjust escrowuint256length=pm.outcomeCount();for(uint256i=0;i<length;i++){IERC20Tokent=IERC20Token(pm.tokens(i));escrowBalance[owner][t]+=amount;}}functionrefundBundle(TokenPredictionMarketpm,uint256amount)internal{// approve transfer of bundle and adjust escrowuint256length=pm.outcomeCount();for(uint256i=0;i<length;i++){IERC20Tokent=IERC20Token(pm.tokens(i));t.approve(pm,amount);escrowBalance[owner][t]-=amount;}// refund the bundlepm.refundBundle(amount);// transfer the received tokens to ownerIERC20Tokenwt=pm.wagertoken();escrowBalance[owner][wt]+=amount;}functionbuyOrRefundBundles(TokenPredictionMarket[]pms,int256[]amounts)internal{for(uint256i=0;i<pms.length;i++){if(amounts[i]>0){buyBundle(pms[i],uint256(amounts[i]));}else{refundBundle(pms[i],uint256(-amounts[i]));}}}functionexecuteWagers(address[]sellers,IERC20Token[]sellTokens,uint256[]sellAmounts,IERC20Token[]receiveTokens,uint256[]receiveAmounts,uint256[]timeLimits,uint256[]nonces,bytes32[]rs,bytes32[]ss,uint8[]vs,TokenPredictionMarket[]pms,int256[]bundleAmounts)public{require(msg.sender==owner);acceptOffers(sellers,sellTokens,sellAmounts,receiveTokens,receiveAmounts,timeLimits,nonces,rs,ss,vs);gatherOffers(sellers,sellTokens,sellAmounts);buyOrRefundBundles(pms,bundleAmounts);distributeOffers(sellers,receiveTokens,receiveAmounts);}}