|
## |
|
## 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() |
Great tool thanks!