Skip to content

Instantly share code, notes, and snippets.

@tyler-smith
Last active July 21, 2017 08:23
Show Gist options
  • Save tyler-smith/9bb5a0b984a3edfdcb1478b0e1333aa0 to your computer and use it in GitHub Desktop.
Save tyler-smith/9bb5a0b984a3edfdcb1478b0e1333aa0 to your computer and use it in GitHub Desktop.
OB Contract Fund Transfer

OB Contract Fund Transfer

payout.py is a tool that allows an OpenBazaar buyer and one of (vendor|moderator) to move funds from an OpenBazaar contract.

Both parties agree to payout the funds in an OB contract to a given address. They need two pieces of data. Order id, and BTC address to send funds to.

In this example we'll use:

Order ID: 02b11c97fe709c8c732a5e37faea598ae27016cf

BTC address: 18bTBMa8wW2tebieGRpLXqonKeVteFZRjr

Installation

First both parties should put the payout.py file in the root of your OpenBazaar server. If you have a manual installation (i.e. from the git repo) you put payout.py in the OpenBazaar-Server directory. If you have the installed version, first install the manual version from: https://github.com/OpenBazaar/OpenBazaar-Server

Buyer signature

The buyer should execute python payout.py <order_id> <btc_address>.

Example: python payout.py 02b11c97fe709c8c732a5e37faea598ae27016cf 18bTBMa8wW2tebieGRpLXqonKeVteFZRjr > buyer_signatures.json

This will output a file called buyer_signatures.json which contains the buyer's signature for the transaction. The buyer should now send this to the other party.

2nd party signature

The vendor or moderator should save the buyer_signatures.json along side the payout.py file and then execute:

Example: python payout.py 02b11c97fe709c8c732a5e37faea598ae27016cf 18bTBMa8wW2tebieGRpLXqonKeVteFZRjr -s buyer_signatures.json

This should finalize the transaction and broadcast it to the Bitcoin network.

##
## payout.py - Creates transactions to sweep an OpenBazaar contract's address
##
## To sweep the holding address for direct transactions:
## python payout.py <order_id> <payout_address>
##
## E.g. python payout.py de529024a6e7ddcbe08e46929db4bedfd7002428 1AXzTxyBBcoeVVqgDdwhf1sLrU42n15akg
##
## To sweep from moderated addresses first the buyers creates a signature:
##
## python payout.py <order_id> <payout_address> > signatures.json
##
## The signatures.json file is given to the 2nd party (vendor or moderator)
## Then the 2nd party creates the final transaction:
##
## python payout.py <order_id> <payout_address> -s signatures.json
##
__author__ = "Tyler Smith"
__license__ = "MIT"
__version__ = "1.0.0"
__copyright__ = "Copyright 2016, OB1"
import os
import sys
import json
import argparse
import bitcointools
from log import Logger
from binascii import unhexlify, hexlify
from config import DATA_FOLDER, LIBBITCOIN_SERVERS, LIBBITCOIN_SERVERS_TESTNET
from db.datastore import Database
from keys.keychain import KeyChain
from keys.bip32utils import derive_childkey
from obelisk.client import LibbitcoinClient
from market.transactions import BitcoinTransaction
def signature_data(tx, key, redeem_script):
sigs = tx.create_signature(key, redeem_script)
return {
"value": tx.get_out_value(),
"signature(s)": sigs,
}
def contract_file(order_id):
vendor_directory = os.path.join(DATA_FOLDER, 'store', 'contracts', 'in progress', order_id + '.json')
if os.path.exists(vendor_directory):
return vendor_directory
buyer_directory = os.path.join(DATA_FOLDER, 'purchases', 'in progress', order_id + '.json')
if os.path.exists(buyer_directory):
return buyer_directory
mod_directory = os.path.join(DATA_FOLDER, 'cases', order_id + '.json')
if os.path.exists(mod_directory):
return mod_directory
raise "Contract file not found"
def load_payment_data(order_id):
with open(contract_file(order_id)) as json_data:
return json.load(json_data).get("buyer_order", {}).get("order", {})["payment"]
def load_outpoints(db, order_id):
outpoints = db.sales.get_outpoint(order_id)
if outpoints is None or len(outpoints) == 0:
outpoints = db.purchases.get_outpoint(order_id)
return json.loads(outpoints)
def finalize_transaction(tx, blockchain=None):
print("Transaction ID: " + tx.get_hash())
print("Transaction:")
print(tx)
print("Raw Transaction:")
print(tx.to_raw_tx())
if blockchain is not None:
tx.broadcast(blockchain)
print("Sent transaction to the to Bitcoin network")
def parse_cli():
parser = argparse.ArgumentParser(
description='OpenBazaar Contract Payout',
usage='python payout.py <order_id> <payout_address> (-s <buyer_signature_file>)'
)
parser.add_argument('order_id', help='The ID of the order to move funds from')
parser.add_argument('payout_address', help='The BTC address to send funds to')
parser.add_argument('-s', '--sigfile', help='Signature file container signatures from the buyer')
parser.add_argument('-d', '--dryrun', help="Don't broadcast transaction", dest='dryrun', action='store_true')
parser.add_argument('-t', '--testnet', help='Use testnet database', dest='testnet', action='store_true')
parser.set_defaults(dryrun=False,testnet=False)
args = parser.parse_args(sys.argv[1:])
return {
"dryrun": args.dryrun,
"testnet": args.testnet,
"sigfile": args.sigfile,
"order_id": args.order_id,
"payout_address": args.payout_address,
}
def parse_sig_file(sigfile):
if sigfile is None:
return None
with open(sigfile) as json_data:
return json.load(json_data)
def main():
# Extract args to vars
args = parse_cli()
dryrun = args["dryrun"]
testnet = args["testnet"]
order_id = args["order_id"]
payout_address = args["payout_address"]
buyer_sig_data = parse_sig_file(args["sigfile"])
# Create DB and keychain
db = Database(testnet)
keychain = KeyChain(db)
# Get the correct blockchain to use
blockchain = None
if not dryrun:
if testnet:
blockchain = LibbitcoinClient(LIBBITCOIN_SERVERS_TESTNET, log=Logger(service="LibbitcoinClient"))
else:
blockchain = LibbitcoinClient(LIBBITCOIN_SERVERS, log=Logger(service="LibbitcoinClient"))
# Get payment data from contract
payment_data = load_payment_data(order_id)
# Check if it's multisig or not
# Testnet only supports multisig right now
is_multisig = payment_data["address"][0] == "3"
if testnet:
is_multisig = True
# Generate the private key for the order
master_key = bitcointools.bip32_extract_key(keychain.bitcoin_master_privkey)
order_key = derive_childkey(master_key, payment_data["chaincode"], bitcointools.MAINNET_PRIVATE)
if testnet:
order_key = derive_childkey(master_key, payment_data["chaincode"], bitcointools.TESTNET_PRIVATE)
# Get inputs
outpoints = load_outpoints(db, order_id)
# Calculate transaction total (sum of inputs - tx fee)
out_value = sum(x["value"] for x in outpoints) - long(payment_data["refund_tx_fee"])
# Build the transaction
redeem_script = payment_data["redeem_script"]
tx = BitcoinTransaction.make_unsigned(outpoints, payout_address, testnet=testnet, out_value=out_value)
# Handle direct payments. Just a simple signature, broadcast, and we're done
if not is_multisig:
tx.sign(order_key)
finalize_transaction(tx, blockchain)
return
# Handle multisig 2-of-3 payments
# Generate our signature data
our_sig_data = signature_data(tx, order_key, redeem_script)
# If no buyer sigs provided then we are the buyer
# Just dump our sig data out and exit
if buyer_sig_data is None:
print(json.dumps(our_sig_data))
return
# Otherwise we're the vendor or moderator. Finish signing and broadcast
signatures = []
for i in range(len(outpoints)):
signatures.append({
"index": i,
"signatures": [
next(s for s in buyer_sig_data["signature(s)"] if s["index"] == i)["signature"].encode('ascii','ignore'),
next(s for s in our_sig_data["signature(s)"] if s["index"] == i)["signature"].encode('ascii','ignore')
]
})
tx.multisign(signatures, redeem_script.encode('ascii','ignore'))
finalize_transaction(tx, blockchain)
if __name__ == "__main__":
main()
@mariodian
Copy link

Great tool thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment