Skip to content

Instantly share code, notes, and snippets.

@pipermerriam
Last active August 22, 2018 07:47
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pipermerriam/6f2a06feddf8f6b8a5b6bb929b8dc16e to your computer and use it in GitHub Desktop.
Save pipermerriam/6f2a06feddf8f6b8a5b6bb929b8dc16e to your computer and use it in GitHub Desktop.

Smart Contract Based Token Purchase Order

Contract Address: 0xE197529709D7cBaf31756C6d9B8742718E17FCA5

What follows is an experiment in a trustless smart contract based buy order for tokens. The contract source code can be used for any token contract that conforms to EIP20.
This contract has been configured for the recently live MKR token.

The contract deployed @ 0xE197529709D7cBaf31756C6d9B8742718E17FCA5 represents a buy order for MKR tokens @ 6.9 ETH per MKR. The contract has been funded with 7 ETH, enough to purchase a bit more than a single MKR token.

If you are interested in participating int his experiment by selling me MKR using this contract you can do so by following these steps.

Step 1: Approve the transfer.

Via the MKR Token contract @ 0xC66eA802717bFb9833400264Dd12c2bCeAa34a6d make an approval for 0xE197529709D7cBaf31756C6d9B8742718E17FCA5 in the amount of MKR tokens you wish to sell.
Because MKR is divisible up to 18 decimal places, if you would like to sell 1.5 MKR then you would need to give an approval in the amount of 1500000000000000000 (1.5e18).

Step 2: Trigger the fillOrder function.

Once the token transfer has been approved you will need to tell the order contract @ 0xE197529709D7cBaf31756C6d9B8742718E17FCA5 to fill the order. This will execute a transferFrom call from the MKR token contract, transfering the tokens to my ethereum address @ 0xd3CdA913deB6f67967B99D67aCDFa1712C293601 and tranfer the coresponding amount of ETH to the ethereum address that the MKR tokens were transferred from.

This can be done with any of the following 4 functions.

  • fillOrder(address _from, uint numTokens): pays ether to and transfers numTokens tokens from the _from address.
  • fillMyOrder(uint numTokens): pays ether to and transfers numTokens tokens from the msg.sender address.
  • fillTheirOrder(address who): queries the MKR token contract for the pre-authorized allowance and transfers that amount to the who address.
  • fillOrderAuto(): queries the MKR token contract for the pre-authorized allowance and transfers that amount to the msg.sender address.

You can also trigger filling of the order by just sending an empty transaction to the order contract, which will execute the fillOrderAuto function by default.

I would recommend using the Ethereum Wallet application to execute the transactions as it provides a user friendly interface for executing these transactions.

Verifying Things

I've attached the contract ABI, Runtime Bytecode, and Source. The source was compiled using solc 0.3.0-1f9578ce and I have verified that browser solidity produces the same bytecode as my locally compiled version.

Other Functions on the contract

weiPerToken()

The constant getter function weiPerToken() can be used to query the amount that will be paid in WEI for each MKR token that is transferred. Note that this amount is the amount for 1 whole MKR token, which is not the same as the smallest divisible MKR unit.

numTokensAbleToPurchase()

The constant getter function numTokensAbleToPurchase() can be used to query the number of MKR tokens that the contract can currently affort to purchase in units of the smallest divisible MKR unit. For example if the contract has exactly enough ether to purchase a single whole MKR token then this function would return 1e18.

[{"constant":false,"inputs":[],"name":"fillOrderAuto","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"numTokensAbleToPurchase","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"numTokens","type":"uint256"}],"name":"fillOrder","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"numTokens","type":"uint256"}],"name":"fillMyOrder","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"weiPerToken","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"decimalPlaces","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"cancel","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"transferOwnership","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"who","type":"address"}],"name":"fillTheirOrder","outputs":[{"name":"","type":"bool"}],"type":"function"},{"inputs":[{"name":"_token","type":"address"},{"name":"_weiPerToken","type":"uint256"},{"name":"_decimalPlaces","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"numTokens","type":"uint256"}],"name":"OrderFilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"}],"name":"OwnershipTransfer","type":"event"}]
contract owned {
function owned() {
owner = msg.sender;
}
address public owner;
modifier onlyowner { if (msg.sender != owner) throw; _ }
event OwnershipTransfer(address indexed from, address indexed to);
function transferOwnership(address to) public onlyowner {
owner = to;
OwnershipTransfer(msg.sender, to);
}
}
// Token standard API
// https://github.com/ethereum/EIPs/issues/20
contract ERC20 {
function totalSupply() constant returns (uint supply);
function balanceOf(address who) constant returns (uint value);
function allowance(address owner, address spender) constant returns (uint _allowance);
function transfer(address to, uint value) returns (bool ok);
function transferFrom(address from, address to, uint value) returns (bool ok);
function approve(address spender, uint value) returns (bool ok);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
contract Order is owned {
ERC20 public token;
uint public weiPerToken;
uint public decimalPlaces;
function Order(address _token, uint _weiPerToken, uint _decimalPlaces) {
token = ERC20(_token);
weiPerToken = _weiPerToken;
decimalPlaces = _decimalPlaces;
}
function sendRobust(address to, uint value) internal {
if (!to.send(value)) {
if (!to.call.value(value)()) throw;
}
}
function min(uint a, uint b) internal returns (uint) {
if (a <= b) {
return a;
} else {
return b;
}
}
function getTransferableBalance(address who) internal returns (uint amount) {
uint allowance = token.allowance(msg.sender, address(this));
uint balance = token.balanceOf(msg.sender);
amount = min(min(allowance, balance), numTokensAbleToPurchase());
return amount;
}
function numTokensAbleToPurchase() constant returns (uint) {
return (this.balance / weiPerToken) * decimalPlaces;
}
event OrderFilled(address _from, uint numTokens);
// Fills or partially fills the order.
function _fillOrder(address _from, uint numTokens) internal returns (bool) {
if (numTokens == 0) throw;
if (this.balance < numTokens * weiPerToken / decimalPlaces) throw;
if (!token.transferFrom(_from, owner, numTokens)) return false;
sendRobust(_from, numTokens * weiPerToken / decimalPlaces);
OrderFilled(_from, numTokens);
return true;
}
function fillOrder(address _from, uint numTokens) public returns (bool) {
return _fillOrder(_from, numTokens);
}
// Simpler call signature that uses `msg.sender`
function fillMyOrder(uint numTokens) public returns (bool) {
return _fillOrder(msg.sender, numTokens);
}
// Simpler call signature that defaults to the account allowance.
function fillTheirOrder(address who) public returns (bool) {
return _fillOrder(who, getTransferableBalance(who));
}
// Simpler call signature that uses `msg.sender` and the current approval
// value.
function fillOrderAuto() public returns (bool) {
return _fillOrder(msg.sender, getTransferableBalance(msg.sender));
}
// Even simpler call signature that tries to transfer as many as possible.
function () {
// allow receipt of funds
if (msg.value > 0) {
return;
} else {
fillOrderAuto();
}
}
// Cancel the order, returning all funds to the owner.
function cancel() onlyowner {
selfdestruct(owner);
}
}
6060604052361561008d5760e060020a600035046339f4debc811461009e5780638da5cb5b146101ad5780638f367001146101bf5780639c7264d7146101da578063d9feeeb6146101fb578063dab8263a1461020d578063e1725c9214610216578063ea8a1af01461021f578063f2fde38b1461023f578063fc0c546a14610262578063fcc6b5d514610274575b610289600034111561028b57610295565b6102975b600061032f3361032a335b600154604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152600160a060020a033381166004830152308116602483015291516000938493849391169163dd62ed3e91604481810192602092909190829003018187876161da5a03f1156100025750506040805180516001547f70a08231000000000000000000000000000000000000000000000000000000008352600160a060020a033381166004850152935191965090921692506370a08231916024808301926020929190829003018187876161da5a03f115610002575050604051519150610344905061034c83835b60008183116104b4575081610324565b6102a9600054600160a060020a031681565b6102975b60035460025430600160a060020a03163104025b90565b610297600435602435600061032183835b6000816000141561035457610002565b610297600435600061032433836101eb565b61029760025481565b61029760035481565b610289600054600160a060020a0390811633919091161461033657610002565b610289600435600054600160a060020a039081163391909116146102c657610002565b6102a9600154600160a060020a031681565b61029760043560006103248261032a846100ad565b005b6102936100a2565b505b565b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b6000805473ffffffffffffffffffffffffffffffffffffffff191682178155604051600160a060020a03838116923391909116917f22500af037c600dd7b720644ab6e358635085601d9ac508ad83eb2d6b2d729ca9190a350565b90505b92915050565b6101eb565b90506101d7565b600054600160a060020a0316ff5b949350505050565b61019d6101c3565b60035460025483020430600160a060020a031631101561037357610002565b60408051600154600080547f23b872dd000000000000000000000000000000000000000000000000000000008452600160a060020a038881166004860152908116602485015260448401879052935191909316926323b872dd926064818101936020939092839003909101908290876161da5a03f115610002575050604051511515905061040357506000610324565b600354600254610464918591850204604051600160a060020a03831690600090839082818181858883f1935050505015156104b057604051600160a060020a038316908290600081818185876185025a03f19250505015156104b057610002565b60408051600160a060020a03851681526020810184905281517f6ac084fe52cc25f2f64ae4aea087b0bf8e92c0a900b2425c1edda1f36c8cab0d929181900390910190a1506001610324565b5050565b508061032456
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment