Skip to content

How to create a contract

kolinko edited this page Sep 16, 2014 · 5 revisions

This tutorial will teach you to create a BTC/USD future smart contract. It assumes you already went through the other steps of mastering distributed oracles.

If you're looking to participate in (not build) an options market, this might not be for you. But contact support@orisi.org - perhaps we can help.

Create your first contract

This is basic tutorial on how to create your first Orisi contract.

We will create a contract that after specific time checks Bitcoin price on Bitstamp and compares it to given value. It will send funds to address A if price will be greater than value or to B in other case.

We need to do few steps.

  1. Fork Orisi repository and clone it with

git clone https://github.com/<your_github_nick>/orisi.

  1. Enter orisi directory and copy

cp -r src/oracle/handlers/timelock_contract src/oracle/handlers/pricecheck_contract.

  1. Further operations will happen in pricecheck folder.

cd src/oracle/handlers/pricecheck_contract

  1. Change file name:

mv timelock_create_handler.py pricecheck_create_handler.py

  1. In file pricecheck_create_handler.py change class name from TimelockCreateHandler to PricecheckCreateHandler.
  2. Change all occurences of word timelock to pricecheck inside pricecheck_create_handler.py. For example in line 28 change timelock_created to pricecheck_created. Etc. (especially in debugging code - that might be useful)
  3. At the beggining of the same file add
from shared.liburl_wrapper import safe_read
from decimal import Decimal
  1. Now let’s change function handle_task. After line
message = json.loads(task[‘json_data’])

add following code:

response = safe_read("https://www.bitstamp.net/api/ticker/", 10)
if not response:
  if not 'retries_number' in message:
    message['retries_number'] = 0

  if message['retries_number'] > 10:
    return

  message['retries_number'] += 1
  self.oracle.task_queue.save({
      "operation": 'pricecheck_create',
      "json_data": json.dumps(message),
      "done": 0,
      "next_check": int(task['locktime']) + 600
  })

response_dict = json.loads(response)

price = Decimal(response_dict['last'])
expected_price = Decimal(message['price'])

if price > expected_price:
  return_address = message['return_if_greater']
else:
  return_address = message['return_if_lesser']

message['return_address'] = return_address
  1. We need to do some modifications in file src/oracle/handlers/handlers.py. In OPERATION_REQUIRED_FIELDS add following field:
'pricecheck_create': ['message_id', 'sum_satoshi', 'prevtxs', 'outputs', 'miners_fee_satoshi', 'return_if_greater', 'return_if_lesser', 'price', 'locktime', 'pubkey_list', 'req_sigs'],

At the beginning add import line:

from pricecheck_contract.pricecheck_create_handler import pricecheckcreatehandler

And in op_handlers add:

pricecheck_create’: PricecheckCreateHandler,
  1. Create client part - add following function after function main2 in file src/client/main.py:
def pricecheck_create(args):
  if len(args)<5:
    print "USAGE: `%s pricecheck_create <pubkey_once> <locktime_minutes> <return_address_if_greater> <return_address_if_smaller> <value>`" % START_COMMAND
    print "- run `%s main` to obtain pubkey_once" % START_COMMAND
    print "- keep in mind that this is alpha, don't expect oracles to run properly for any extended periods of time"
    return

  btc = BitcoinClient()

  request = {}
  client_pubkey = args[0]
  request['locktime'] = time.time() + int(args[1])*60
  request['return_if_greater'] = args[2]
  request['return_if_lesser'] = args[3]
  request['price'] = float(args[4])

  print "fetching charter url" # hopefully it didn't check between running main1 and main2
  charter = fetch_charter(CHARTER_URL)

  oracle_pubkeys = []
  oracle_fees = {}
  oracle_bms = []

  for o in charter['nodes']:
    oracle_pubkeys.append(o['pubkey'])
    oracle_fees[o['address']] = o['fee']
    oracle_bms.append(o['bm'])

  oracle_fees[charter['org_address']] = charter['org_fee']

  min_sigs = int(ceil(float(len(oracle_pubkeys))/2))

  key_list = [client_pubkey] + oracle_pubkeys

  response = btc.create_multisig_address(min_sigs, key_list)
  msig_addr = response['address'] # we're using this as an identificator
  redeemScript = response['redeemScript']

  request['message_id'] = "%s-%s" % (msig_addr, str(randrange(1000000000,9000000000)))
  request['pubkey_list'] = key_list

  request['miners_fee_satoshi'] = MINERS_FEE

  print "fetching transactions incoming to %s ..." % msig_addr

  # for production purposes you might want to fetch the data using bitcoind, but that's expensive
  address_json = liburl_wrapper.safe_read("https://blockchain.info/address/%s?format=json" % msig_addr, timeout_time=10)
  try:
    address_history = json.loads(address_json)
  except:
    print "blockchain.info problem"
    print address_json
    return

  prevtxs = []
  sum_satoshi = 0

  for tx in address_history['txs']:
    outputs = []
    if 'out' in tx:
      outputs = outputs + tx['out']
    if 'outputs' in tx:
      outputs = outputs + tx['outputs']

    for vout in tx['out']:
      print vout
      if vout['addr'] == msig_addr:
        prevtx = {
          'scriptPubKey' : vout['script'],
          'vout': vout['n'],
          'txid': tx['hash'],
          'redeemScript': redeemScript,
        }
        sum_satoshi += vout['value']
        prevtxs.append(prevtx)

  if len(prevtxs) == 0:
    print "ERROR: couldn't find transactions sending money to %s" % msig_addr
    return

  request['prevtxs'] = prevtxs
  request['outputs'] = oracle_fees

  request["req_sigs"] = min_sigs
  request['operation'] = 'pricecheck_create'
  request['sum_satoshi'] = sum_satoshi

  bm = BitmessageClient()
  print "sending: %r" % json.dumps(request)
  print bm.chan_address

  request_content = json.dumps(request)

  print bm.send_message(bm.chan_address, request['operation'], request_content)

This function is basically a copy of function main2, but that'll do, we just want to test functionality.

  1. Still in file src/client/main.py change two dictionaries. To OPERATIONS add:
'pricecheck_create': pricecheck_create,

and to SHORT_DESCRIPTIONS add:

'pricecheck_create': 'create pricecheck transaction and pushes it into network',
  1. Call git add . from src.
  2. git commit -m "Pricecheck contract"
  3. git push
  4. Run an oracle on vagrant. Learn how to do that. Please note: One of the steps there is to git clone https://github.com/orisi/orisi (step 7). Please remember to clone your repository instead..
  5. On vagrant machine create new screen where oracle will work. Do that with screen -S oracle.
  6. On that screen run ./runoracle.sh. In debug code you’ll see public_key, bitcoin_address and bm_address. Copy them - we’ll use them later
  7. Leave screen with Ctrl-a and then Ctrl-d. Leave vagrant box.
  8. Create your own JSON with charter file. Look at example charter file and create similar file with pubkey, address and bm_address you’ve got earlier from your vagrant machine.
  9. (TODO USABILITY) Put that file on FTP server or any other server. The point is to get URL to that file. (we’re working on making that easier, sorry)
  10. In file src/client/main.py change CHARTER_URL to your url from previous point.
  11. Call git add . from src.
  12. git commit -m "charter url changed"
  13. git push
  14. Create second vagrant box (again with tutorial).
  15. ssh to second vagrant box and call ./runclient main
  16. Add funds to given Bitcoin address.
  17. Call ./runclient pricecheck_create <pubkey_once> <locktime> <return_address_if_greater> <return_address_if_smaller> <price>
  18. Wait for transaction to get through network, observe messages from Oracle to know if it works.