refactor protocol, put test stubs in place, linting, prep for cli tests
This commit is contained in:
parent
9157d12081
commit
270bd7000f
60
xcat/cli.py
60
xcat/cli.py
|
@ -1,29 +1,32 @@
|
||||||
import argparse, textwrap
|
import argparse
|
||||||
from xcat.utils import *
|
import textwrap
|
||||||
|
import subprocess
|
||||||
import xcat.db as db
|
import xcat.db as db
|
||||||
import xcat.userInput as userInput
|
import xcat.userInput as userInput
|
||||||
|
from xcat.protocol import Protocol
|
||||||
from xcat.trades import *
|
from xcat.trades import *
|
||||||
from xcat.protocol import *
|
from xcat.utils import *
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def save_state(trade, tradeid):
|
def save_state(trade, tradeid):
|
||||||
save(trade)
|
save(trade)
|
||||||
db.create(trade, tradeid)
|
db.create(trade, tradeid)
|
||||||
|
|
||||||
|
|
||||||
def checkSellStatus(tradeid):
|
def checkSellStatus(tradeid):
|
||||||
trade = db.get(tradeid)
|
trade = db.get(tradeid)
|
||||||
status = seller_check_status(trade)
|
status = seller_check_status(trade)
|
||||||
print("Trade status: {0}\n".format(status))
|
print("Trade status: {0}\n".format(status))
|
||||||
if status == 'init':
|
if status == 'init':
|
||||||
userInput.authorize_fund_sell(trade)
|
userInput.authorize_fund_sell(trade)
|
||||||
fund_tx = fund_sell_contract(trade)
|
fund_tx = protocol.fund_sell_contract(trade)
|
||||||
print("Sent fund_tx", fund_tx)
|
print("Sent fund_tx", fund_tx)
|
||||||
trade.sell.fund_tx = fund_tx
|
trade.sell.fund_tx = fund_tx
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
elif status == 'buyerFunded':
|
elif status == 'buyerFunded':
|
||||||
secret = db.get_secret(tradeid)
|
secret = db.get_secret(tradeid)
|
||||||
print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret))
|
print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret))
|
||||||
txs = seller_redeem_p2sh(trade, secret)
|
txs = protocol.seller_redeem_p2sh(trade, secret)
|
||||||
if 'redeem_tx' in txs:
|
if 'redeem_tx' in txs:
|
||||||
trade.buy.redeem_tx = txs['redeem_tx']
|
trade.buy.redeem_tx = txs['redeem_tx']
|
||||||
print("Redeem tx: ", txs['redeem_tx'])
|
print("Redeem tx: ", txs['redeem_tx'])
|
||||||
|
@ -38,9 +41,10 @@ def checkSellStatus(tradeid):
|
||||||
elif status == 'sellerRedeemed':
|
elif status == 'sellerRedeemed':
|
||||||
print("You have already redeemed the p2sh on the second chain of this trade.")
|
print("You have already redeemed the p2sh on the second chain of this trade.")
|
||||||
|
|
||||||
|
|
||||||
def buyer_check_status(trade):
|
def buyer_check_status(trade):
|
||||||
sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh)
|
sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh)
|
||||||
buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh)
|
buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh)
|
||||||
if sellState == 'funded' and buyState == 'empty':
|
if sellState == 'funded' and buyState == 'empty':
|
||||||
return 'sellerFunded' # step1
|
return 'sellerFunded' # step1
|
||||||
# TODO: Find funding txid. How does buyer get seller redeemed tx?
|
# TODO: Find funding txid. How does buyer get seller redeemed tx?
|
||||||
|
@ -54,9 +58,10 @@ def buyer_check_status(trade):
|
||||||
else:
|
else:
|
||||||
return 'init'
|
return 'init'
|
||||||
|
|
||||||
|
|
||||||
def seller_check_status(trade):
|
def seller_check_status(trade):
|
||||||
sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh)
|
sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh)
|
||||||
buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh)
|
buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh)
|
||||||
if sellState == 'funded' and buyState == 'empty':
|
if sellState == 'funded' and buyState == 'empty':
|
||||||
return 'sellerFunded' # step1
|
return 'sellerFunded' # step1
|
||||||
elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'):
|
elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'):
|
||||||
|
@ -70,6 +75,7 @@ def seller_check_status(trade):
|
||||||
else:
|
else:
|
||||||
return 'init' # step0
|
return 'init' # step0
|
||||||
|
|
||||||
|
|
||||||
def checkBuyStatus(tradeid):
|
def checkBuyStatus(tradeid):
|
||||||
trade = db.get(tradeid)
|
trade = db.get(tradeid)
|
||||||
status = buyer_check_status(trade)
|
status = buyer_check_status(trade)
|
||||||
|
@ -83,15 +89,17 @@ def checkBuyStatus(tradeid):
|
||||||
input("Type 'enter' to allow this program to send funds on your behalf.")
|
input("Type 'enter' to allow this program to send funds on your behalf.")
|
||||||
print("Trade commitment", trade.commitment)
|
print("Trade commitment", trade.commitment)
|
||||||
# if verify_p2sh(trade):
|
# if verify_p2sh(trade):
|
||||||
fund_tx = fund_contract(trade.buy)
|
fund_tx = protocol.fund_contract(trade.buy)
|
||||||
print("\nYou sent this funding tx: ", fund_tx)
|
print("\nYou sent this funding tx: ", fund_tx)
|
||||||
trade.buy.fund_tx = fund_tx
|
trade.buy.fund_tx = fund_tx
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
elif status == 'sellerRedeemed':
|
elif status == 'sellerRedeemed':
|
||||||
secret = find_secret_from_fundtx(trade.buy.currency, trade.buy.p2sh, trade.buy.fund_tx)
|
secret = protocol.find_secret_from_fundtx(trade.buy.currency,
|
||||||
|
trade.buy.p2sh,
|
||||||
|
trade.buy.fund_tx)
|
||||||
if secret != None:
|
if secret != None:
|
||||||
print("Found secret on blockchain in seller's redeem tx: ", secret)
|
print("Found secret on blockchain in seller's redeem tx: ", secret)
|
||||||
txs = redeem_p2sh(trade.sell, secret)
|
txs = protocol.redeem_p2sh(trade.sell, secret)
|
||||||
if 'redeem_tx' in txs:
|
if 'redeem_tx' in txs:
|
||||||
trade.sell.redeem_tx = txs['redeem_tx']
|
trade.sell.redeem_tx = txs['redeem_tx']
|
||||||
print("Redeem txid: ", trade.sell.redeem_tx)
|
print("Redeem txid: ", trade.sell.redeem_tx)
|
||||||
|
@ -103,14 +111,16 @@ def checkBuyStatus(tradeid):
|
||||||
else:
|
else:
|
||||||
print("Secret not found in redeemtx")
|
print("Secret not found in redeemtx")
|
||||||
|
|
||||||
|
|
||||||
# Import a trade in hex, and save to db
|
# Import a trade in hex, and save to db
|
||||||
def importtrade(tradeid, hexstr=''):
|
def importtrade(tradeid, hexstr=''):
|
||||||
trade = x2s(hexstr)
|
trade = x2s(hexstr)
|
||||||
trade = db.instantiate(trade)
|
trade = db.instantiate(trade)
|
||||||
import_addrs(trade)
|
protocol.import_addrs(trade)
|
||||||
print(trade.toJSON())
|
print(trade.toJSON())
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
|
|
||||||
|
|
||||||
def wormhole_importtrade():
|
def wormhole_importtrade():
|
||||||
res = subprocess.call('wormhole receive', shell=True)
|
res = subprocess.call('wormhole receive', shell=True)
|
||||||
if res == 0:
|
if res == 0:
|
||||||
|
@ -123,9 +133,10 @@ def wormhole_importtrade():
|
||||||
else:
|
else:
|
||||||
print("Importing trade using magic-wormhole failed.")
|
print("Importing trade using magic-wormhole failed.")
|
||||||
|
|
||||||
|
|
||||||
# Export a trade by its tradeid
|
# Export a trade by its tradeid
|
||||||
def exporttrade(tradeid, wormhole=False):
|
def exporttrade(tradeid, wormhole=False):
|
||||||
trade = db.get(tradeid)
|
trade = db.get(tradeid)
|
||||||
hexstr = s2x(trade.toJSON())
|
hexstr = s2x(trade.toJSON())
|
||||||
if wormhole:
|
if wormhole:
|
||||||
tradefile = os.path.join(ROOT_DIR, '.tmp/{0}'.format(tradeid))
|
tradefile = os.path.join(ROOT_DIR, '.tmp/{0}'.format(tradeid))
|
||||||
|
@ -138,20 +149,24 @@ def exporttrade(tradeid, wormhole=False):
|
||||||
print(hexstr)
|
print(hexstr)
|
||||||
return hexstr
|
return hexstr
|
||||||
|
|
||||||
|
|
||||||
def findtrade(tradeid):
|
def findtrade(tradeid):
|
||||||
trade = db.get(tradeid)
|
trade = db.get(tradeid)
|
||||||
print(trade.toJSON())
|
print(trade.toJSON())
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
|
|
||||||
def find_role(contract):
|
def find_role(contract):
|
||||||
# When regtest created both addrs on same machine, role is both.
|
# When regtest created both addrs on same machine, role is both.
|
||||||
if is_myaddr(contract.initiator) and is_myaddr(contract.fulfiller):
|
if protocol.is_myaddr(contract.initiator):
|
||||||
return 'test'
|
if protocol.is_myaddr(contract.fulfiller):
|
||||||
elif is_myaddr(contract.initiator):
|
return 'test'
|
||||||
return 'initiator'
|
else:
|
||||||
|
return 'initiator'
|
||||||
else:
|
else:
|
||||||
return 'fulfiller'
|
return 'fulfiller'
|
||||||
|
|
||||||
|
|
||||||
def checktrade(tradeid):
|
def checktrade(tradeid):
|
||||||
print("In checktrade")
|
print("In checktrade")
|
||||||
trade = db.get(tradeid)
|
trade = db.get(tradeid)
|
||||||
|
@ -170,22 +185,25 @@ def checktrade(tradeid):
|
||||||
role = 'buyer'
|
role = 'buyer'
|
||||||
checkBuyStatus(tradeid)
|
checkBuyStatus(tradeid)
|
||||||
|
|
||||||
|
|
||||||
def newtrade(tradeid, **kwargs):
|
def newtrade(tradeid, **kwargs):
|
||||||
print("Creating new XCAT trade...")
|
print("Creating new XCAT trade...")
|
||||||
erase_trade()
|
erase_trade()
|
||||||
tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf'])
|
tradeid, trade = protocol.initialize_trade(tradeid, conf=kwargs['conf'])
|
||||||
print("Trade", trade)
|
print("Trade", trade)
|
||||||
trade = seller_init(tradeid, trade)
|
trade = protocol.seller_init(tradeid, trade)
|
||||||
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n")
|
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n")
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
|
|
||||||
def listtrades():
|
def listtrades():
|
||||||
print("Trades")
|
print("Trades")
|
||||||
trades = db.dump()
|
trades = db.dump()
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
print("{0}: {1}".format(trade[0], trade[1]))
|
print("{0}: {1}".format(trade[0], trade[1]))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
|
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
|
||||||
description=textwrap.dedent('''\
|
description=textwrap.dedent('''\
|
||||||
|
|
15
xcat/db.py
15
xcat/db.py
|
@ -1,10 +1,10 @@
|
||||||
import plyvel
|
import plyvel
|
||||||
from xcat.utils import *
|
|
||||||
import binascii
|
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
import ast
|
# import binascii
|
||||||
|
# import sys
|
||||||
|
# import ast
|
||||||
from xcat.trades import *
|
from xcat.trades import *
|
||||||
|
from xcat.utils import *
|
||||||
|
|
||||||
db = plyvel.DB('/tmp/xcatDB', create_if_missing=True)
|
db = plyvel.DB('/tmp/xcatDB', create_if_missing=True)
|
||||||
preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
|
preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
|
||||||
|
@ -13,6 +13,7 @@ preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
|
||||||
######## Trades stored by tradeid ###########
|
######## Trades stored by tradeid ###########
|
||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
|
|
||||||
# Takes dict or obj, saves json str as bytes
|
# Takes dict or obj, saves json str as bytes
|
||||||
def create(trade, tradeid):
|
def create(trade, tradeid):
|
||||||
if type(trade) == dict:
|
if type(trade) == dict:
|
||||||
|
@ -21,6 +22,7 @@ def create(trade, tradeid):
|
||||||
trade = trade.toJSON()
|
trade = trade.toJSON()
|
||||||
db.put(b(tradeid), b(trade))
|
db.put(b(tradeid), b(trade))
|
||||||
|
|
||||||
|
|
||||||
# Uses the funding txid as the key to save trade
|
# Uses the funding txid as the key to save trade
|
||||||
def createByFundtx(trade):
|
def createByFundtx(trade):
|
||||||
trade = trade.toJSON()
|
trade = trade.toJSON()
|
||||||
|
@ -29,12 +31,14 @@ def createByFundtx(trade):
|
||||||
txid = jt['sell']['fund_tx']
|
txid = jt['sell']['fund_tx']
|
||||||
db.put(b(txid), b(trade))
|
db.put(b(txid), b(trade))
|
||||||
|
|
||||||
|
|
||||||
def get(tradeid):
|
def get(tradeid):
|
||||||
rawtrade = db.get(b(tradeid))
|
rawtrade = db.get(b(tradeid))
|
||||||
tradestr = str(rawtrade, 'utf-8')
|
tradestr = str(rawtrade, 'utf-8')
|
||||||
trade = instantiate(tradestr)
|
trade = instantiate(tradestr)
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
|
|
||||||
def instantiate(trade):
|
def instantiate(trade):
|
||||||
if type(trade) == str:
|
if type(trade) == str:
|
||||||
tradestr = json.loads(trade)
|
tradestr = json.loads(trade)
|
||||||
|
@ -45,10 +49,12 @@ def instantiate(trade):
|
||||||
###### Preimages stored by tradeid ##########
|
###### Preimages stored by tradeid ##########
|
||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
|
|
||||||
# Stores secret locally in key/value store by tradeid
|
# Stores secret locally in key/value store by tradeid
|
||||||
def save_secret(tradeid, secret):
|
def save_secret(tradeid, secret):
|
||||||
res = preimageDB.put(b(tradeid), b(secret))
|
res = preimageDB.put(b(tradeid), b(secret))
|
||||||
|
|
||||||
|
|
||||||
def get_secret(tradeid):
|
def get_secret(tradeid):
|
||||||
secret = preimageDB.get(b(tradeid))
|
secret = preimageDB.get(b(tradeid))
|
||||||
secret = str(secret, 'utf-8')
|
secret = str(secret, 'utf-8')
|
||||||
|
@ -67,6 +73,7 @@ def dump():
|
||||||
results.append((str(k, 'utf-8'), j))
|
results.append((str(k, 'utf-8'), j))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def print_entries():
|
def print_entries():
|
||||||
it = db.iterator()
|
it = db.iterator()
|
||||||
with db.iterator() as it:
|
with db.iterator() as it:
|
||||||
|
|
425
xcat/protocol.py
425
xcat/protocol.py
|
@ -1,224 +1,245 @@
|
||||||
import json
|
|
||||||
import os, sys
|
|
||||||
from pprint import pprint
|
|
||||||
from xcat.utils import *
|
|
||||||
from xcat.trades import Contract, Trade
|
|
||||||
import xcat.userInput as userInput
|
import xcat.userInput as userInput
|
||||||
import xcat.db as db
|
import xcat.db as db
|
||||||
from xcat.xcatconf import *
|
import xcat.utils as utils
|
||||||
|
from xcat.xcatconf import ADDRS
|
||||||
|
from xcat.trades import Contract, Trade
|
||||||
from xcat.bitcoinRPC import bitcoinProxy
|
from xcat.bitcoinRPC import bitcoinProxy
|
||||||
from xcat.zcashRPC import zcashProxy
|
from xcat.zcashRPC import zcashProxy
|
||||||
|
|
||||||
bitcoinRPC = bitcoinProxy()
|
|
||||||
zcashRPC = zcashProxy()
|
|
||||||
|
|
||||||
def is_myaddr(address):
|
class Protocol():
|
||||||
if address[:1] == 'm':
|
|
||||||
status = bitcoinRPC.validateaddress(address)
|
|
||||||
else:
|
|
||||||
status = zcashRPC.validateaddress(address)
|
|
||||||
status = status['ismine']
|
|
||||||
# print("Address {0} is mine: {1}".format(address, status))
|
|
||||||
return status
|
|
||||||
|
|
||||||
def find_secret_from_fundtx(currency, p2sh, fundtx):
|
def __init__(self):
|
||||||
if currency == 'bitcoin':
|
self.bitcoinRPC = bitcoinProxy()
|
||||||
secret = bitcoinRPC.find_secret(p2sh, fundtx)
|
self.zcashRPC = zcashProxy()
|
||||||
else:
|
|
||||||
secret = zcashRPC.find_secret(p2sh, fundtx)
|
|
||||||
return secret
|
|
||||||
|
|
||||||
def import_addrs(trade):
|
def is_myaddr(self, address):
|
||||||
check_fund_status(trade.sell.currency, trade.sell.p2sh)
|
if address[:1] == 'm':
|
||||||
check_fund_status(trade.buy.currency, trade.buy.p2sh)
|
status = self.bitcoinRPC.validateaddress(address)
|
||||||
|
|
||||||
def check_p2sh(currency, address):
|
|
||||||
if currency == 'bitcoin':
|
|
||||||
print("Checking funds in Bitcoin p2sh")
|
|
||||||
return bitcoinRPC.check_funds(address)
|
|
||||||
else:
|
|
||||||
print("Checking funds in Zcash p2sh")
|
|
||||||
return zcashRPC.check_funds(address)
|
|
||||||
|
|
||||||
def check_fund_status(currency, address):
|
|
||||||
if currency == 'bitcoin':
|
|
||||||
print("Checking funds in Bitcoin p2sh")
|
|
||||||
return bitcoinRPC.get_fund_status(address)
|
|
||||||
else:
|
|
||||||
print("Checking funds in Zcash p2sh")
|
|
||||||
return zcashRPC.get_fund_status(address)
|
|
||||||
|
|
||||||
# TODO: function to calculate appropriate locktimes between chains
|
|
||||||
# def verify_p2sh(trade):
|
|
||||||
# htlc = create_htlc(trade.buy.currency, trade.buy.fulfiller, trade.buy.initiator, trade.commitment, trade.buy.locktime)
|
|
||||||
# buyer_p2sh = htlc['p2sh']
|
|
||||||
# print("Buyer p2sh:", buyer_p2sh)
|
|
||||||
# If the two p2sh match...
|
|
||||||
# if buyer_p2sh == trade.buy.p2sh:
|
|
||||||
# else:
|
|
||||||
# print("Compiled p2sh for htlc does not match what seller sent.")
|
|
||||||
|
|
||||||
def create_htlc(currency, funder, redeemer, commitment, locktime):
|
|
||||||
if currency == 'bitcoin':
|
|
||||||
sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
|
||||||
else:
|
|
||||||
sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
|
||||||
return sell_p2sh
|
|
||||||
|
|
||||||
def fund_htlc(currency, p2sh, amount):
|
|
||||||
if currency == 'bitcoin':
|
|
||||||
txid = bitcoinRPC.fund_htlc(p2sh, amount)
|
|
||||||
else:
|
|
||||||
txid = zcashRPC.fund_htlc(p2sh, amount)
|
|
||||||
return txid
|
|
||||||
|
|
||||||
def fund_contract(contract):
|
|
||||||
txid = fund_htlc(contract.currency, contract.p2sh, contract.amount)
|
|
||||||
return txid
|
|
||||||
|
|
||||||
def fund_sell_contract(trade):
|
|
||||||
sell = trade.sell
|
|
||||||
txid = fund_htlc(sell.currency, sell.p2sh, sell.amount)
|
|
||||||
setattr(trade.sell, 'fund_tx', txid)
|
|
||||||
save(trade)
|
|
||||||
return txid
|
|
||||||
|
|
||||||
def create_sell_p2sh(trade, commitment, locktime):
|
|
||||||
# CREATE SELL CONTRACT
|
|
||||||
sell = trade.sell
|
|
||||||
contract = create_htlc(sell.currency, sell.initiator, sell.fulfiller, commitment, locktime)
|
|
||||||
print("sell contract", contract)
|
|
||||||
setattr(trade.sell, 'p2sh', contract['p2sh'])
|
|
||||||
setattr(trade.sell, 'redeemScript', contract['redeemScript'])
|
|
||||||
setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum'])
|
|
||||||
setattr(trade.buy, 'locktime', contract['locktime'])
|
|
||||||
save(trade)
|
|
||||||
|
|
||||||
def create_buy_p2sh(trade, commitment, locktime):
|
|
||||||
## CREATE BUY CONTRACT
|
|
||||||
buy = trade.buy
|
|
||||||
print("\nNow creating buy contract on the {0} blockchain where you will wait for the buyer to send funds...".format(buy.currency))
|
|
||||||
buy_contract = create_htlc(buy.currency, buy.fulfiller, buy.initiator, commitment, locktime)
|
|
||||||
print("Buy contract", buy_contract)
|
|
||||||
|
|
||||||
setattr(trade.buy, 'p2sh', buy_contract['p2sh'])
|
|
||||||
setattr(trade.buy, 'redeemScript', buy_contract['redeemScript'])
|
|
||||||
setattr(trade.buy, 'redeemblocknum', buy_contract['redeemblocknum'])
|
|
||||||
setattr(trade.buy, 'locktime', buy_contract['locktime'])
|
|
||||||
print("\nNow contact the buyer and tell them to send funds to this p2sh: {0}\n".format(trade.buy.p2sh))
|
|
||||||
|
|
||||||
save(trade)
|
|
||||||
|
|
||||||
def redeem_p2sh(contract, secret):
|
|
||||||
currency = contract.currency
|
|
||||||
if currency == 'bitcoin':
|
|
||||||
res = bitcoinRPC.redeem_contract(contract, secret)
|
|
||||||
else:
|
|
||||||
res = zcashRPC.redeem_contract(contract, secret)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def parse_secret(chain, txid):
|
|
||||||
if chain == 'bitcoin':
|
|
||||||
secret = bitcoinRPC.parse_secret(txid)
|
|
||||||
else:
|
|
||||||
secret = zcashRPC.parse_secret(txid)
|
|
||||||
return secret
|
|
||||||
|
|
||||||
#### Main functions determining user flow from command line
|
|
||||||
def buyer_redeem(trade):
|
|
||||||
userInput.authorize_buyer_redeem(trade)
|
|
||||||
if trade.sell.get_status() == 'redeemed':
|
|
||||||
print("You already redeemed the funds and acquired {0} {1}".format(trade.sell.amount, trade.sell.currency))
|
|
||||||
exit()
|
|
||||||
else:
|
|
||||||
# Buyer redeems seller's funded tx
|
|
||||||
p2sh = trade.sell.p2sh
|
|
||||||
currency = trade.sell.currency
|
|
||||||
# Buy contract is where seller disclosed secret in redeeming
|
|
||||||
if trade.buy.currency == 'bitcoin':
|
|
||||||
secret = bitcoinRPC.parse_secret(trade.buy.redeem_tx)
|
|
||||||
else:
|
else:
|
||||||
secret = zcashRPC.parse_secret(trade.buy.redeem_tx)
|
status = self.zcashRPC.validateaddress(address)
|
||||||
print("Found secret in seller's redeem tx", secret)
|
status = status['ismine']
|
||||||
redeem_tx = redeem_p2sh(trade.sell, secret)
|
# print("Address {0} is mine: {1}".format(address, status))
|
||||||
setattr(trade.sell, 'redeem_tx', redeem_tx)
|
return status
|
||||||
save(trade)
|
|
||||||
exit()
|
|
||||||
|
|
||||||
def seller_redeem_p2sh(trade, secret):
|
def find_secret_from_fundtx(self, currency, p2sh, fundtx):
|
||||||
buy = trade.buy
|
if currency == 'bitcoin':
|
||||||
userInput.authorize_seller_redeem(buy)
|
secret = self.bitcoinRPC.find_secret(p2sh, fundtx)
|
||||||
|
else:
|
||||||
|
secret = self.zcashRPC.find_secret(p2sh, fundtx)
|
||||||
|
return secret
|
||||||
|
|
||||||
if trade.sell.get_status() == 'redeemed':
|
def import_addrs(self, trade):
|
||||||
print("You already redeemed the funds and acquired {0} {1}".format(buy.amount, buy.currency))
|
self.check_fund_status(trade.sell.currency, trade.sell.p2sh)
|
||||||
|
self.check_fund_status(trade.buy.currency, trade.buy.p2sh)
|
||||||
|
|
||||||
|
def check_p2sh(self, currency, address):
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
print("Checking funds in Bitcoin p2sh")
|
||||||
|
return self.bitcoinRPC.check_funds(address)
|
||||||
|
else:
|
||||||
|
print("Checking funds in Zcash p2sh")
|
||||||
|
return self.zcashRPC.check_funds(address)
|
||||||
|
|
||||||
|
def check_fund_status(self, currency, address):
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
print("Checking funds in Bitcoin p2sh")
|
||||||
|
return self.bitcoinRPC.get_fund_status(address)
|
||||||
|
else:
|
||||||
|
print("Checking funds in Zcash p2sh")
|
||||||
|
return self.zcashRPC.get_fund_status(address)
|
||||||
|
|
||||||
|
# TODO: function to calculate appropriate locktimes between chains
|
||||||
|
# def verify_p2sh(trade):
|
||||||
|
# htlc = self.create_htlc(
|
||||||
|
# trade.buy.currency, trade.buy.fulfiller,
|
||||||
|
# trade.buy.initiator, trade.commitment,
|
||||||
|
# trade.buy.locktime)
|
||||||
|
# buyer_p2sh = htlc['p2sh']
|
||||||
|
# print("Buyer p2sh:", buyer_p2sh)
|
||||||
|
# If the two p2sh match...
|
||||||
|
# if buyer_p2sh == trade.buy.p2sh:
|
||||||
|
# else:
|
||||||
|
# print("Compiled p2sh for htlc does not match what seller sent.")
|
||||||
|
|
||||||
|
def create_htlc(self, currency, funder, redeemer, commitment, locktime):
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
sell_p2sh = self.bitcoinRPC.hashtimelockcontract(
|
||||||
|
funder, redeemer, commitment, locktime)
|
||||||
|
else:
|
||||||
|
sell_p2sh = self.zcashRPC.hashtimelockcontract(
|
||||||
|
funder, redeemer, commitment, locktime)
|
||||||
|
return sell_p2sh
|
||||||
|
|
||||||
|
def fund_htlc(self, currency, p2sh, amount):
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
txid = self.bitcoinRPC.fund_htlc(p2sh, amount)
|
||||||
|
else:
|
||||||
|
txid = self.zcashRPC.fund_htlc(p2sh, amount)
|
||||||
|
return txid
|
||||||
|
|
||||||
|
def fund_contract(self, contract):
|
||||||
|
txid = self.fund_htlc(
|
||||||
|
contract.currency, contract.p2sh, contract.amount)
|
||||||
|
return txid
|
||||||
|
|
||||||
|
def fund_sell_contract(self, trade):
|
||||||
|
sell = trade.sell
|
||||||
|
txid = self.fund_htlc(sell.currency, sell.p2sh, sell.amount)
|
||||||
|
setattr(trade.sell, 'fund_tx', txid)
|
||||||
|
utils.save(trade)
|
||||||
|
return txid
|
||||||
|
|
||||||
|
def create_sell_p2sh(self, trade, commitment, locktime):
|
||||||
|
# CREATE SELL CONTRACT
|
||||||
|
sell = trade.sell
|
||||||
|
contract = self.create_htlc(sell.currency, sell.initiator,
|
||||||
|
sell.fulfiller, commitment, locktime)
|
||||||
|
print("sell contract", contract)
|
||||||
|
setattr(trade.sell, 'p2sh', contract['p2sh'])
|
||||||
|
setattr(trade.sell, 'redeemScript', contract['redeemScript'])
|
||||||
|
setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum'])
|
||||||
|
setattr(trade.buy, 'locktime', contract['locktime'])
|
||||||
|
utils.save(trade)
|
||||||
|
|
||||||
|
def create_buy_p2sh(self, trade, commitment, locktime):
|
||||||
|
# CREATE BUY CONTRACT
|
||||||
|
buy = trade.buy
|
||||||
|
print("\nNow creating buy contract on the {0} blockchain where you "
|
||||||
|
"will wait for the buyer to send funds...".format(buy.currency))
|
||||||
|
buy_contract = self.create_htlc(
|
||||||
|
buy.currency, buy.fulfiller, buy.initiator, commitment, locktime)
|
||||||
|
print("Buy contract", buy_contract)
|
||||||
|
|
||||||
|
setattr(trade.buy, 'p2sh', buy_contract['p2sh'])
|
||||||
|
setattr(trade.buy, 'redeemScript', buy_contract['redeemScript'])
|
||||||
|
setattr(trade.buy, 'redeemblocknum', buy_contract['redeemblocknum'])
|
||||||
|
setattr(trade.buy, 'locktime', buy_contract['locktime'])
|
||||||
|
print("\nNow contact the buyer and tell them to send funds to this "
|
||||||
|
"p2sh: {0}\n".format(trade.buy.p2sh))
|
||||||
|
|
||||||
|
utils.save(trade)
|
||||||
|
|
||||||
|
def redeem_p2sh(self, contract, secret):
|
||||||
|
currency = contract.currency
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
res = self.bitcoinRPC.redeem_contract(contract, secret)
|
||||||
|
else:
|
||||||
|
res = self.zcashRPC.redeem_contract(contract, secret)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def parse_secret(self, chain, txid):
|
||||||
|
if chain == 'bitcoin':
|
||||||
|
secret = self.bitcoinRPC.parse_secret(txid)
|
||||||
|
else:
|
||||||
|
secret = self.zcashRPC.parse_secret(txid)
|
||||||
|
return secret
|
||||||
|
|
||||||
|
# Main functions determining user flow from command line
|
||||||
|
def buyer_redeem(self, trade):
|
||||||
|
userInput.authorize_buyer_redeem(trade)
|
||||||
|
if trade.sell.get_status() == 'redeemed':
|
||||||
|
print("You already redeemed the funds and acquired "
|
||||||
|
"{0} {1}".format(trade.sell.amount, trade.sell.currency))
|
||||||
|
exit()
|
||||||
|
else:
|
||||||
|
# Buyer redeems seller's funded tx
|
||||||
|
# p2sh = trade.sell.p2sh
|
||||||
|
# currency = trade.sell.currency
|
||||||
|
# Buy contract is where seller disclosed secret in redeeming
|
||||||
|
if trade.buy.currency == 'bitcoin':
|
||||||
|
secret = self.bitcoinRPC.parse_secret(trade.buy.redeem_tx)
|
||||||
|
else:
|
||||||
|
secret = self.zcashRPC.parse_secret(trade.buy.redeem_tx)
|
||||||
|
print("Found secret in seller's redeem tx", secret)
|
||||||
|
redeem_tx = self.redeem_p2sh(trade.sell, secret)
|
||||||
|
setattr(trade.sell, 'redeem_tx', redeem_tx)
|
||||||
|
utils.save(trade)
|
||||||
exit()
|
exit()
|
||||||
else:
|
|
||||||
# Seller redeems buyer's funded tx (contract in p2sh)
|
|
||||||
txs = redeem_p2sh(trade.buy, secret)
|
|
||||||
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
|
|
||||||
return txs
|
|
||||||
|
|
||||||
def buyer_fulfill(trade):
|
def seller_redeem_p2sh(self, trade, secret):
|
||||||
buy = trade.buy
|
buy = trade.buy
|
||||||
sell = trade.sell
|
userInput.authorize_seller_redeem(buy)
|
||||||
buy_p2sh_balance = check_p2sh(buy.currency, buy.p2sh)
|
|
||||||
sell_p2sh_balance = check_p2sh(sell.currency, sell.p2sh)
|
|
||||||
|
|
||||||
if buy_p2sh_balance == 0:
|
if trade.sell.get_status() == 'redeemed':
|
||||||
userInput.authorize_buyer_fulfill(sell_p2sh_balance, sell.currency, buy_p2sh_balance, buy.currency)
|
print("You already redeemed the funds and acquired "
|
||||||
print("Buy amt:", buy.amount)
|
"{0} {1}".format(buy.amount, buy.currency))
|
||||||
txid = fund_buy_contract(trade)
|
exit()
|
||||||
print("Fund tx txid:", txid)
|
else:
|
||||||
else:
|
# Seller redeems buyer's funded tx (contract in p2sh)
|
||||||
print("It looks like you've already funded the contract to buy {1}, the amount in escrow in the p2sh is {0}.".format(buy_p2sh_balance, buy.currency))
|
txs = self.redeem_p2sh(trade.buy, secret)
|
||||||
print("Please wait for the seller to remove your funds from escrow to complete the trade.")
|
print("You have redeemed "
|
||||||
print_trade('buyer')
|
"{0} {1}!".format(buy.amount, buy.currency))
|
||||||
|
return txs
|
||||||
|
|
||||||
def initialize_trade(tradeid, **kwargs):
|
def buyer_fulfill(self, trade):
|
||||||
trade = Trade()
|
buy = trade.buy
|
||||||
conf = kwargs['conf']
|
sell = trade.sell
|
||||||
if conf == 'cli':
|
buy_p2sh_balance = self.check_p2sh(buy.currency, buy.p2sh)
|
||||||
init_addrs = userInput.get_initiator_addresses()
|
sell_p2sh_balance = self.check_p2sh(sell.currency, sell.p2sh)
|
||||||
fulfill_addrs = userInput.get_fulfiller_addresses()
|
|
||||||
amounts = userInput.get_trade_amounts()
|
|
||||||
print("AMOUNTS", amounts)
|
|
||||||
else:
|
|
||||||
init_addrs = ADDRS[conf]['initiator']
|
|
||||||
fulfill_addrs = ADDRS[conf]['fulfiller']
|
|
||||||
amounts = ADDRS[conf]['amounts']
|
|
||||||
|
|
||||||
sell = amounts['sell']
|
if buy_p2sh_balance == 0:
|
||||||
buy = amounts['buy']
|
userInput.authorize_buyer_fulfill(
|
||||||
sell_currency = sell['currency']
|
sell_p2sh_balance,
|
||||||
buy_currency = buy['currency']
|
sell.currency,
|
||||||
sell['initiator'] = init_addrs[sell_currency]
|
buy_p2sh_balance,
|
||||||
buy['initiator'] = init_addrs[buy_currency]
|
buy.currency)
|
||||||
sell['fulfiller'] = fulfill_addrs[sell_currency]
|
print("Buy amt:", buy.amount)
|
||||||
buy['fulfiller'] = fulfill_addrs[buy_currency]
|
txid = fund_buy_contract(trade)
|
||||||
|
print("Fund tx txid:", txid)
|
||||||
|
else:
|
||||||
|
print("It looks like you've already funded the contract to buy "
|
||||||
|
"{1}, the amount in escrow in the p2sh is "
|
||||||
|
"{0}.".format(buy_p2sh_balance, buy.currency))
|
||||||
|
print("Please wait for the seller to remove your funds from "
|
||||||
|
"escrow to complete the trade.")
|
||||||
|
print_trade('buyer')
|
||||||
|
|
||||||
# initializing contract classes with addresses, currencies, and amounts
|
def initialize_trade(self, tradeid, **kwargs):
|
||||||
trade.sell = Contract(sell)
|
trade = Trade()
|
||||||
trade.buy = Contract(buy)
|
conf = kwargs['conf']
|
||||||
print(trade.sell.__dict__)
|
if conf == 'cli':
|
||||||
print(trade.buy.__dict__)
|
init_addrs = userInput.get_initiator_addresses()
|
||||||
return tradeid, trade
|
fulfill_addrs = userInput.get_fulfiller_addresses()
|
||||||
|
amounts = userInput.get_trade_amounts()
|
||||||
|
print("AMOUNTS", amounts)
|
||||||
|
else:
|
||||||
|
init_addrs = ADDRS[conf]['initiator']
|
||||||
|
fulfill_addrs = ADDRS[conf]['fulfiller']
|
||||||
|
amounts = ADDRS[conf]['amounts']
|
||||||
|
|
||||||
|
sell = amounts['sell']
|
||||||
|
buy = amounts['buy']
|
||||||
|
sell_currency = sell['currency']
|
||||||
|
buy_currency = buy['currency']
|
||||||
|
sell['initiator'] = init_addrs[sell_currency]
|
||||||
|
buy['initiator'] = init_addrs[buy_currency]
|
||||||
|
sell['fulfiller'] = fulfill_addrs[sell_currency]
|
||||||
|
buy['fulfiller'] = fulfill_addrs[buy_currency]
|
||||||
|
|
||||||
def seller_init(tradeid, trade):
|
# initializing contract classes with addresses, currencies, and amounts
|
||||||
secret = generate_password()
|
trade.sell = Contract(sell)
|
||||||
db.save_secret(tradeid, secret)
|
trade.buy = Contract(buy)
|
||||||
print("\nGenerated a secret preimage to lock funds. This will only be stored locally: ", secret)
|
print(trade.sell.__dict__)
|
||||||
|
print(trade.buy.__dict__)
|
||||||
|
return tradeid, trade
|
||||||
|
|
||||||
hash_of_secret = sha256(secret)
|
def seller_init(self, tradeid, trade):
|
||||||
# TODO: Implement locktimes and mock block passage of time
|
secret = utils.generate_password()
|
||||||
sell_locktime = 20
|
db.save_secret(tradeid, secret)
|
||||||
buy_locktime = 10 # Must be more than first tx
|
print("\nGenerated a secret preimage to lock funds. This will only "
|
||||||
print("Creating pay-to-script-hash for sell contract...")
|
"be stored locally: ", secret)
|
||||||
|
|
||||||
# create the p2sh addrs
|
hash_of_secret = utils.sha256(secret)
|
||||||
create_sell_p2sh(trade, hash_of_secret, sell_locktime)
|
# TODO: Implement locktimes and mock block passage of time
|
||||||
create_buy_p2sh(trade, hash_of_secret, buy_locktime)
|
sell_locktime = 20
|
||||||
|
buy_locktime = 10 # Must be more than first tx
|
||||||
|
print("Creating pay-to-script-hash for sell contract...")
|
||||||
|
|
||||||
trade.commitment = b2x(hash_of_secret)
|
# create the p2sh addrs
|
||||||
print("TRADE after seller init", trade.toJSON())
|
self.create_sell_p2sh(trade, hash_of_secret, sell_locktime)
|
||||||
return trade
|
self.create_buy_p2sh(trade, hash_of_secret, buy_locktime)
|
||||||
|
|
||||||
|
trade.commitment = utils.b2x(hash_of_secret)
|
||||||
|
print("TRADE after seller init", trade.toJSON())
|
||||||
|
return trade
|
||||||
|
|
|
@ -1,53 +1,62 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
import unittest.mock as mock
|
||||||
import xcat.cli as cli
|
import xcat.cli as cli
|
||||||
import xcat.db as db
|
from xcat.tests.utils import test_trade
|
||||||
from xcat.tests.utils import mktrade
|
from xcat.trades import Trade
|
||||||
from xcat.trades import Trade, Contract
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleTestCase(unittest.TestCase):
|
class TestCLI(unittest.TestCase):
|
||||||
def setUp(self):
|
|
||||||
self.trade = mktrade()
|
|
||||||
|
|
||||||
def test_exporttrade(self):
|
def test_save_state(self):
|
||||||
self.__class__.hexstr = cli.exporttrade('test')
|
pass
|
||||||
self.assertTrue(int(self.hexstr, 16))
|
|
||||||
|
def test_checkSellStatus(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_buyer_check_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_seller_check_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_checkBuyStatus(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_importtrade(self):
|
def test_importtrade(self):
|
||||||
trade = cli.importtrade('test', self.__class__.hexstr)
|
pass
|
||||||
|
|
||||||
|
def test_wormhole_importtrade(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_exporttrade(self):
|
||||||
|
pass
|
||||||
|
|
||||||
class CliTest(SimpleTestCase):
|
|
||||||
def test_findtrade(self):
|
def test_findtrade(self):
|
||||||
trade = cli.findtrade('test')
|
pass
|
||||||
|
|
||||||
|
def test_find_role(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_checktrade(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_newtrade(self):
|
def test_newtrade(self):
|
||||||
trade = cli.newtrade('new', conf='regtest')
|
pass
|
||||||
self.assertTrue(isinstance(trade, Trade))
|
|
||||||
|
def test_listtrades(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_fundsell(self):
|
def test_fundsell(self):
|
||||||
trade = db.get('new')
|
pass
|
||||||
status = cli.seller_check_status(trade)
|
|
||||||
print("Trade status: {0}\n".format(status))
|
|
||||||
self.assertEqual(status, 'init')
|
|
||||||
fund_tx = cli.fund_sell_contract(trade)
|
|
||||||
print("Sent fund_tx", fund_tx)
|
|
||||||
|
|
||||||
# def test_fundbuy(self):
|
def test_fundbuy(self):
|
||||||
# trade = db.get('new')
|
pass
|
||||||
# status = cli.buyer_check_status(trade)
|
|
||||||
# self.assertEqual(status, 'sellerFunded')
|
def test_seller_redeem(self):
|
||||||
# fund_tx = cli.fund_contract(trade.buy)
|
pass
|
||||||
#
|
|
||||||
# def test_seller_redeem(self):
|
def test_buyer_redeem(self):
|
||||||
# trade = db.get('new')
|
pass
|
||||||
# status = cli.seller_check_status(trade)
|
|
||||||
# self.assertEqual(status, 'buyerFunded')
|
|
||||||
#
|
|
||||||
# def test_buyer_redeem(self):
|
|
||||||
# trade = db.get('new')
|
|
||||||
# status = cli.buyer_check_status(trade)
|
|
||||||
# self.assertEqual(status, 'sellerFunded')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
import xcat.db as db
|
test_trade = {
|
||||||
|
"sell": {
|
||||||
test_trade = {"sell": {"amount": 3.5, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "redeemblocknum": 1066, "currency": "bitcoin", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, "buy": {"amount": 1.2, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "redeemblocknum": 3391, "currency": "zcash", "locktime": 10, "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"}
|
"amount": 3.5,
|
||||||
|
"redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac",
|
||||||
def mktrade():
|
"redeemblocknum": 1066,
|
||||||
db.create(test_trade, 'test')
|
"currency": "bitcoin",
|
||||||
trade = db.get('test')
|
"initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp",
|
||||||
return trade
|
"p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f",
|
||||||
|
"fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z",
|
||||||
|
"fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"},
|
||||||
|
"buy": {
|
||||||
|
"amount": 1.2,
|
||||||
|
"redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac",
|
||||||
|
"redeemblocknum": 3391,
|
||||||
|
"currency": "zcash",
|
||||||
|
"locktime": 10,
|
||||||
|
"initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ",
|
||||||
|
"p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP",
|
||||||
|
"fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"},
|
||||||
|
"commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"}
|
||||||
|
|
Loading…
Reference in New Issue