mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-03 17:29:26 +00:00
Display warning when wallet seedid doesn't match expected.
This commit is contained in:
parent
5a163e0f86
commit
e7afd5e67d
10 changed files with 199 additions and 100 deletions
52
README.md
52
README.md
|
@ -19,54 +19,4 @@ In the future it should be possible to use data from explorers instead of runnin
|
||||||
|
|
||||||
Not ready for real-world use.
|
Not ready for real-world use.
|
||||||
|
|
||||||
Features still required (of many):
|
Discuss development and help with testing in the matrix channel [#basicswap:matrix.org](https://riot.im/app/#/room/#basicswap:matrix.org)
|
||||||
- Cached addresses must be regenerated after use.
|
|
||||||
- Option to lookup data from public explorers / nodes.
|
|
||||||
- Ability to swap coin-types without running nodes for all coin-types
|
|
||||||
- More swap protocols
|
|
||||||
- Method to load mnemonic into Particl.
|
|
||||||
- Load seeds for other wallets from same mnemonic.
|
|
||||||
- COIN must be defined per coin.
|
|
||||||
|
|
||||||
|
|
||||||
## Seller first protocol:
|
|
||||||
|
|
||||||
Seller sends the 1st transaction.
|
|
||||||
|
|
||||||
1. Seller posts offer.
|
|
||||||
- smsg from seller to network
|
|
||||||
coin-from
|
|
||||||
coin-to
|
|
||||||
amount-from
|
|
||||||
rate
|
|
||||||
min-amount
|
|
||||||
time-valid
|
|
||||||
|
|
||||||
2. Buyer posts bid:
|
|
||||||
- smsg from buyer to seller
|
|
||||||
offerid
|
|
||||||
amount
|
|
||||||
proof-of-funds
|
|
||||||
address_to_buyer
|
|
||||||
time-valid
|
|
||||||
|
|
||||||
3. Seller accepts bid:
|
|
||||||
- verifies proof-of-funds
|
|
||||||
- generates secret
|
|
||||||
- submits initiate tx to coin-from network
|
|
||||||
- smsg from seller to buyer
|
|
||||||
txid
|
|
||||||
initiatescript (includes pkhash_to_seller as the pkhash_refund)
|
|
||||||
|
|
||||||
4. Buyer participates:
|
|
||||||
- inspects initiate tx in coin-from network
|
|
||||||
- submits participate tx in coin-to network
|
|
||||||
|
|
||||||
5. Seller redeems:
|
|
||||||
- constructs participatescript
|
|
||||||
- inspects participate tx in coin-to network
|
|
||||||
- redeems from participate tx revealing secret
|
|
||||||
|
|
||||||
6. Buyer redeems:
|
|
||||||
- scans coin-to network for seller-redeem tx
|
|
||||||
- redeems from initiate tx with revealed secret
|
|
||||||
|
|
|
@ -621,19 +621,39 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
if self.coin_clients[c]['connection_type'] == 'rpc':
|
if self.coin_clients[c]['connection_type'] == 'rpc':
|
||||||
self.waitForDaemonRPC(c)
|
self.waitForDaemonRPC(c)
|
||||||
core_version = self.coin_clients[c]['interface'].getDaemonVersion()
|
ci = self.ci(c)
|
||||||
self.log.info('%s Core version %d', chainparams[c]['name'].capitalize(), core_version)
|
core_version = ci.getDaemonVersion()
|
||||||
|
self.log.info('%s Core version %d', ci.coin_name(), core_version)
|
||||||
self.coin_clients[c]['core_version'] = core_version
|
self.coin_clients[c]['core_version'] = core_version
|
||||||
|
|
||||||
if c == Coins.XMR:
|
|
||||||
self.coin_clients[c]['interface'].ensureWalletExists()
|
|
||||||
|
|
||||||
if c == Coins.PART:
|
if c == Coins.PART:
|
||||||
self.coin_clients[c]['have_spent_index'] = self.coin_clients[c]['interface'].haveSpentIndex()
|
self.coin_clients[c]['have_spent_index'] = ci.haveSpentIndex()
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if self.callcoinrpc(c, 'getstakinginfo')['enabled'] is not False:
|
if self.callcoinrpc(c, 'getstakinginfo')['enabled'] is not False:
|
||||||
self.log.warning('%s staking is not disabled.', chainparams[c]['name'].capitalize())
|
self.log.warning('%s staking is not disabled.', ci.coin_name())
|
||||||
|
elif c == Coins.XMR:
|
||||||
|
ci.ensureWalletExists()
|
||||||
|
|
||||||
|
expect_address = self.getStringKV('main_wallet_addr_' + chainparams[c]['name'])
|
||||||
|
self.log.debug('[rm] expect_address %s', expect_address)
|
||||||
|
if expect_address is None:
|
||||||
|
self.log.warning('Can\'t find expected main wallet address for coin {}'.format(ci.coin_name()))
|
||||||
|
else:
|
||||||
|
if expect_address == ci.getMainWalletAddress():
|
||||||
|
ci.setWalletSeedWarning(False)
|
||||||
|
else:
|
||||||
|
self.log.warning('Wallet for coin {} not derived from swap seed.'.format(ci.coin_name()))
|
||||||
|
else:
|
||||||
|
expect_seedid = self.getStringKV('main_wallet_seedid_' + chainparams[c]['name'])
|
||||||
|
self.log.debug('[rm] expect_seedid %s', expect_seedid)
|
||||||
|
if expect_seedid is None:
|
||||||
|
self.log.warning('Can\'t find expected wallet seed id for coin {}'.format(ci.coin_name()))
|
||||||
|
else:
|
||||||
|
if expect_seedid == ci.getWalletSeedID():
|
||||||
|
ci.setWalletSeedWarning(False)
|
||||||
|
else:
|
||||||
|
self.log.warning('Wallet for coin {} not derived from swap seed.'.format(ci.coin_name()))
|
||||||
|
|
||||||
self.initialise()
|
self.initialise()
|
||||||
|
|
||||||
|
@ -702,6 +722,8 @@ class BasicSwap(BaseApp):
|
||||||
raise ValueError('{} chain is still syncing, currently at {}.'.format(self.coin_clients[c]['name'], synced))
|
raise ValueError('{} chain is still syncing, currently at {}.'.format(self.coin_clients[c]['name'], synced))
|
||||||
|
|
||||||
def initialiseWallet(self, coin_type):
|
def initialiseWallet(self, coin_type):
|
||||||
|
if coin_type == Coins.PART:
|
||||||
|
return
|
||||||
ci = self.ci(coin_type)
|
ci = self.ci(coin_type)
|
||||||
self.log.info('Initialising {} wallet.'.format(ci.coin_name()))
|
self.log.info('Initialising {} wallet.'.format(ci.coin_name()))
|
||||||
|
|
||||||
|
@ -709,20 +731,63 @@ class BasicSwap(BaseApp):
|
||||||
key_view = self.getWalletKey(coin_type, 1, for_ed25519=True)
|
key_view = self.getWalletKey(coin_type, 1, for_ed25519=True)
|
||||||
key_spend = self.getWalletKey(coin_type, 2, for_ed25519=True)
|
key_spend = self.getWalletKey(coin_type, 2, for_ed25519=True)
|
||||||
ci.initialiseWallet(key_view, key_spend)
|
ci.initialiseWallet(key_view, key_spend)
|
||||||
|
root_address = ci.getAddressFromKeys(key_view, key_spend)
|
||||||
|
|
||||||
|
key_str = 'main_wallet_addr_' + chainparams[coin_type]['name']
|
||||||
|
self.setStringKV(key_str, root_address)
|
||||||
return
|
return
|
||||||
|
|
||||||
root_key = self.getWalletKey(coin_type, 1)
|
root_key = self.getWalletKey(coin_type, 1)
|
||||||
|
root_hash = ci.getAddressHashFromKey(root_key)
|
||||||
ci.initialiseWallet(root_key)
|
ci.initialiseWallet(root_key)
|
||||||
|
|
||||||
|
key_str = 'main_wallet_seedid_' + chainparams[coin_type]['name']
|
||||||
|
self.setStringKV(key_str, root_hash.hex())
|
||||||
|
|
||||||
def setIntKV(self, str_key, int_val):
|
def setIntKV(self, str_key, int_val):
|
||||||
session = scoped_session(self.session_factory)
|
self.mxDB.acquire()
|
||||||
kv = session.query(DBKVInt).filter_by(key=str_key).first()
|
try:
|
||||||
if not kv:
|
session = scoped_session(self.session_factory)
|
||||||
kv = DBKVInt(key=str_key, value=int_val)
|
kv = session.query(DBKVInt).filter_by(key=str_key).first()
|
||||||
session.add(kv)
|
if not kv:
|
||||||
session.commit()
|
kv = DBKVInt(key=str_key, value=int_val)
|
||||||
session.close()
|
else:
|
||||||
session.remove()
|
kv.value = int_val
|
||||||
|
session.add(kv)
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
session.remove()
|
||||||
|
finally:
|
||||||
|
self.mxDB.release()
|
||||||
|
|
||||||
|
def setStringKV(self, str_key, str_val):
|
||||||
|
self.mxDB.acquire()
|
||||||
|
try:
|
||||||
|
session = scoped_session(self.session_factory)
|
||||||
|
kv = session.query(DBKVString).filter_by(key=str_key).first()
|
||||||
|
if not kv:
|
||||||
|
kv = DBKVString(key=str_key, value=str_val)
|
||||||
|
else:
|
||||||
|
kv.value = str_val
|
||||||
|
session.add(kv)
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
session.remove()
|
||||||
|
finally:
|
||||||
|
self.mxDB.release()
|
||||||
|
|
||||||
|
def getStringKV(self, str_key):
|
||||||
|
self.mxDB.acquire()
|
||||||
|
try:
|
||||||
|
session = scoped_session(self.session_factory)
|
||||||
|
v = session.query(DBKVString).filter_by(key=str_key).first()
|
||||||
|
if not v:
|
||||||
|
return None
|
||||||
|
return v.value
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
session.remove()
|
||||||
|
self.mxDB.release()
|
||||||
|
|
||||||
def activateBid(self, session, bid):
|
def activateBid(self, session, bid):
|
||||||
if bid.bid_id in self.swaps_in_progress:
|
if bid.bid_id in self.swaps_in_progress:
|
||||||
|
@ -756,7 +821,7 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
# TODO process addresspool if bid has previously been abandoned
|
# TODO process addresspool if bid has previously been abandoned
|
||||||
|
|
||||||
def deactivateBid(self, offer, bid):
|
def deactivateBid(self, session, offer, bid):
|
||||||
# Remove from in progress
|
# Remove from in progress
|
||||||
self.log.debug('Removing bid from in-progress: %s', bid.bid_id.hex())
|
self.log.debug('Removing bid from in-progress: %s', bid.bid_id.hex())
|
||||||
self.swaps_in_progress.pop(bid.bid_id, None)
|
self.swaps_in_progress.pop(bid.bid_id, None)
|
||||||
|
@ -1126,25 +1191,8 @@ class BasicSwap(BaseApp):
|
||||||
def cacheNewAddressForCoin(self, coin_type):
|
def cacheNewAddressForCoin(self, coin_type):
|
||||||
self.log.debug('cacheNewAddressForCoin %s', coin_type)
|
self.log.debug('cacheNewAddressForCoin %s', coin_type)
|
||||||
key_str = 'receive_addr_' + chainparams[coin_type]['name']
|
key_str = 'receive_addr_' + chainparams[coin_type]['name']
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
addr = self.getReceiveAddressForCoin(coin_type)
|
addr = self.getReceiveAddressForCoin(coin_type)
|
||||||
self.mxDB.acquire()
|
self.setStringKV(key_str, addr)
|
||||||
try:
|
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
try:
|
|
||||||
kv = session.query(DBKVString).filter_by(key=key_str).first()
|
|
||||||
kv.value = addr
|
|
||||||
except Exception:
|
|
||||||
kv = DBKVString(
|
|
||||||
key=key_str,
|
|
||||||
value=addr
|
|
||||||
)
|
|
||||||
session.add(kv)
|
|
||||||
session.commit()
|
|
||||||
session.close()
|
|
||||||
session.remove()
|
|
||||||
finally:
|
|
||||||
self.mxDB.release()
|
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
def getCachedAddressForCoin(self, coin_type):
|
def getCachedAddressForCoin(self, coin_type):
|
||||||
|
@ -1838,9 +1886,9 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
# Mark bid as abandoned, no further processing will be done
|
# Mark bid as abandoned, no further processing will be done
|
||||||
bid.setState(BidStates.BID_ABANDONED)
|
bid.setState(BidStates.BID_ABANDONED)
|
||||||
|
self.deactivateBid(session, offer, bid)
|
||||||
|
session.add(bid)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
self.deactivateBid(offer, bid)
|
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
session.remove()
|
session.remove()
|
||||||
|
@ -3986,6 +4034,7 @@ class BasicSwap(BaseApp):
|
||||||
self.setBidError(bid_id, bid, str(ex))
|
self.setBidError(bid_id, bid, str(ex))
|
||||||
|
|
||||||
# Update copy of bid in swaps_in_progress
|
# Update copy of bid in swaps_in_progress
|
||||||
|
assert(bid_id in self.swaps_in_progress)
|
||||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||||
|
|
||||||
def processXmrSplitMessage(self, msg):
|
def processXmrSplitMessage(self, msg):
|
||||||
|
@ -4112,7 +4161,7 @@ class BasicSwap(BaseApp):
|
||||||
self.setBidError(bid_id, v[0], str(ex))
|
self.setBidError(bid_id, v[0], str(ex))
|
||||||
|
|
||||||
for bid_id, bid, offer in to_remove:
|
for bid_id, bid, offer in to_remove:
|
||||||
self.deactivateBid(offer, bid)
|
self.deactivateBid(None, offer, bid)
|
||||||
self._last_checked_progress = now
|
self._last_checked_progress = now
|
||||||
|
|
||||||
if now - self._last_checked_watched >= self.check_watched_seconds:
|
if now - self._last_checked_watched >= self.check_watched_seconds:
|
||||||
|
@ -4157,12 +4206,20 @@ class BasicSwap(BaseApp):
|
||||||
if has_changed:
|
if has_changed:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
try:
|
try:
|
||||||
self.saveBidInSession(bid_id, bid, session)
|
activate_bid = False
|
||||||
session.commit()
|
if offer.swap_type == SwapTypes.BUYER_FIRST:
|
||||||
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
|
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
|
||||||
|
activate_bid = True
|
||||||
|
else:
|
||||||
|
raise ValueError('TODO')
|
||||||
|
|
||||||
|
if activate_bid:
|
||||||
self.activateBid(session, bid)
|
self.activateBid(session, bid)
|
||||||
else:
|
else:
|
||||||
self.deactivateBid(offer, bid)
|
self.deactivateBid(session, offer, bid)
|
||||||
|
|
||||||
|
self.saveBidInSession(bid_id, bid, session)
|
||||||
|
session.commit()
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
session.remove()
|
session.remove()
|
||||||
|
@ -4222,8 +4279,9 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
def getWalletInfo(self, coin):
|
def getWalletInfo(self, coin):
|
||||||
|
|
||||||
blockchaininfo = self.coin_clients[coin]['interface'].getBlockchainInfo()
|
ci = self.ci(coin)
|
||||||
walletinfo = self.coin_clients[coin]['interface'].getWalletInfo()
|
blockchaininfo = ci.getBlockchainInfo()
|
||||||
|
walletinfo = ci.getWalletInfo()
|
||||||
|
|
||||||
scale = chainparams[coin]['decimal_places']
|
scale = chainparams[coin]['decimal_places']
|
||||||
rv = {
|
rv = {
|
||||||
|
@ -4234,6 +4292,7 @@ class BasicSwap(BaseApp):
|
||||||
'balance': format_amount(make_int(walletinfo['balance'], scale), scale),
|
'balance': format_amount(make_int(walletinfo['balance'], scale), scale),
|
||||||
'unconfirmed': format_amount(make_int(walletinfo.get('unconfirmed_balance'), scale), scale),
|
'unconfirmed': format_amount(make_int(walletinfo.get('unconfirmed_balance'), scale), scale),
|
||||||
'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)),
|
'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)),
|
||||||
|
'expected_seed': ci.knownWalletSeed(),
|
||||||
}
|
}
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,9 @@ chainparams = {
|
||||||
|
|
||||||
|
|
||||||
class CoinInterface:
|
class CoinInterface:
|
||||||
|
def __init__(self):
|
||||||
|
self._unknown_wallet_seed = True
|
||||||
|
|
||||||
def format_amount(self, amount_int):
|
def format_amount(self, amount_int):
|
||||||
return format_amount(amount_int, self.exp())
|
return format_amount(amount_int, self.exp())
|
||||||
|
|
||||||
|
@ -207,3 +210,9 @@ class CoinInterface:
|
||||||
|
|
||||||
def ticker(self):
|
def ticker(self):
|
||||||
return chainparams[self.coin_type()]['ticker']
|
return chainparams[self.coin_type()]['ticker']
|
||||||
|
|
||||||
|
def setWalletSeedWarning(self, value):
|
||||||
|
self._unknown_wallet_seed = value
|
||||||
|
|
||||||
|
def knownWalletSeed(self):
|
||||||
|
return not self._unknown_wallet_seed
|
||||||
|
|
|
@ -254,6 +254,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||||
'blocks': w['blocks'],
|
'blocks': w['blocks'],
|
||||||
'synced': w['synced'],
|
'synced': w['synced'],
|
||||||
'deposit_address': w['deposit_address'],
|
'deposit_address': w['deposit_address'],
|
||||||
|
'expected_seed': w['expected_seed'],
|
||||||
})
|
})
|
||||||
if float(w['unconfirmed']) > 0.0:
|
if float(w['unconfirmed']) > 0.0:
|
||||||
wallets_formatted[-1]['unconfirmed'] = w['unconfirmed']
|
wallets_formatted[-1]['unconfirmed'] = w['unconfirmed']
|
||||||
|
|
|
@ -118,6 +118,7 @@ class BTCInterface(CoinInterface):
|
||||||
return abs(a - b) < 20
|
return abs(a - b) < 20
|
||||||
|
|
||||||
def __init__(self, coin_settings, network):
|
def __init__(self, coin_settings, network):
|
||||||
|
super().__init__()
|
||||||
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
||||||
self.txoType = CTxOut
|
self.txoType = CTxOut
|
||||||
self._network = network
|
self._network = network
|
||||||
|
@ -145,6 +146,9 @@ class BTCInterface(CoinInterface):
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
return self.rpc_callback('getwalletinfo')
|
return self.rpc_callback('getwalletinfo')
|
||||||
|
|
||||||
|
def getWalletSeedID(self):
|
||||||
|
return self.rpc_callback('getwalletinfo')['hdseedid']
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit):
|
def getNewAddress(self, use_segwit):
|
||||||
args = ['swap_receive']
|
args = ['swap_receive']
|
||||||
if use_segwit:
|
if use_segwit:
|
||||||
|
@ -177,6 +181,10 @@ class BTCInterface(CoinInterface):
|
||||||
def getPubkey(self, privkey):
|
def getPubkey(self, privkey):
|
||||||
return PublicKey.from_secret(privkey).format()
|
return PublicKey.from_secret(privkey).format()
|
||||||
|
|
||||||
|
def getAddressHashFromKey(self, key):
|
||||||
|
pk = self.getPubkey(key)
|
||||||
|
return hash160(pk)
|
||||||
|
|
||||||
def verifyKey(self, k):
|
def verifyKey(self, k):
|
||||||
i = b2i(k)
|
i = b2i(k)
|
||||||
return(i < ep.o and i > 0)
|
return(i < ep.o and i > 0)
|
||||||
|
|
|
@ -33,6 +33,10 @@ class PARTInterface(BTCInterface):
|
||||||
self._network = network
|
self._network = network
|
||||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||||
|
|
||||||
|
def knownWalletSeed(self):
|
||||||
|
# TODO: Double check
|
||||||
|
return True
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit):
|
def getNewAddress(self, use_segwit):
|
||||||
return self.rpc_callback('getnewaddress', ['swap_receive'])
|
return self.rpc_callback('getnewaddress', ['swap_receive'])
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ class XMRInterface(CoinInterface):
|
||||||
return 32
|
return 32
|
||||||
|
|
||||||
def __init__(self, coin_settings, network):
|
def __init__(self, coin_settings, network):
|
||||||
|
super().__init__()
|
||||||
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'])
|
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'])
|
||||||
rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'])
|
rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'])
|
||||||
|
|
||||||
|
@ -154,6 +155,11 @@ class XMRInterface(CoinInterface):
|
||||||
def getPubkey(self, privkey):
|
def getPubkey(self, privkey):
|
||||||
return ed25519_get_pubkey(privkey)
|
return ed25519_get_pubkey(privkey)
|
||||||
|
|
||||||
|
def getAddressFromKeys(self, key_view, key_spend):
|
||||||
|
pk_view = self.getPubkey(key_view)
|
||||||
|
pk_spend = self.getPubkey(key_spend)
|
||||||
|
return xmr_util.encode_address(pk_view, pk_spend)
|
||||||
|
|
||||||
def verifyKey(self, k):
|
def verifyKey(self, k):
|
||||||
i = b2i(k)
|
i = b2i(k)
|
||||||
return(i < edf.l and i > 8)
|
return(i < edf.l and i > 8)
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<tr><td>Balance:</td><td>{{ w.balance }}</td>{% if w.unconfirmed %}<td>Unconfirmed:</td><td>{{ w.unconfirmed }}</td>{% endif %}</tr>
|
<tr><td>Balance:</td><td>{{ w.balance }}</td>{% if w.unconfirmed %}<td>Unconfirmed:</td><td>{{ w.unconfirmed }}</td>{% endif %}</tr>
|
||||||
<tr><td>Blocks:</td><td>{{ w.blocks }}</td></tr>
|
<tr><td>Blocks:</td><td>{{ w.blocks }}</td></tr>
|
||||||
<tr><td>Synced:</td><td>{{ w.synced }}</td></tr>
|
<tr><td>Synced:</td><td>{{ w.synced }}</td></tr>
|
||||||
|
<tr><td>Expected Seed:</td><td>{{ w.expected_seed }}</td></tr>
|
||||||
<tr><td><input type="submit" name="newaddr_{{ w.cid }}" value="Deposit Address"></td><td>{{ w.deposit_address }}</td></tr>
|
<tr><td><input type="submit" name="newaddr_{{ w.cid }}" value="Deposit Address"></td><td>{{ w.deposit_address }}</td></tr>
|
||||||
<tr><td><input type="submit" name="withdraw_{{ w.cid }}" value="Withdraw"></td><td>Amount: <input type="text" name="amt_{{ w.cid }}"></td><td>Address: <input type="text" name="to_{{ w.cid }}"></td><td>Subtract fee: <input type="checkbox" name="subfee_{{ w.cid }}"></td></tr>
|
<tr><td><input type="submit" name="withdraw_{{ w.cid }}" value="Withdraw"></td><td>Amount: <input type="text" name="amt_{{ w.cid }}"></td><td>Address: <input type="text" name="to_{{ w.cid }}"></td><td>Subtract fee: <input type="checkbox" name="subfee_{{ w.cid }}"></td></tr>
|
||||||
<tr><td>Fee Rate:</td><td>{{ w.fee_rate }}</td><td>Est Fee:</td><td>{{ w.est_fee }}</td></tr>
|
<tr><td>Fee Rate:</td><td>{{ w.fee_rate }}</td><td>Est Fee:</td><td>{{ w.est_fee }}</td></tr>
|
||||||
|
@ -32,4 +33,4 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p><a href="/">home</a></p>
|
<p><a href="/">home</a></p>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
53
doc/notes.md
53
doc/notes.md
|
@ -4,3 +4,56 @@
|
||||||
```
|
```
|
||||||
python setup.py test -s tests.basicswap.test_xmr.Test.test_02_leader_recover_a_lock_tx
|
python setup.py test -s tests.basicswap.test_xmr.Test.test_02_leader_recover_a_lock_tx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
Features still required (of many):
|
||||||
|
- Cached addresses must be regenerated after use.
|
||||||
|
- Option to lookup data from public explorers / nodes.
|
||||||
|
- Ability to swap coin-types without running nodes for all coin-types
|
||||||
|
- More swap protocols
|
||||||
|
- Method to load mnemonic into Particl.
|
||||||
|
- Load seeds for other wallets from same mnemonic.
|
||||||
|
- COIN must be defined per coin.
|
||||||
|
|
||||||
|
|
||||||
|
## Seller first protocol:
|
||||||
|
|
||||||
|
Seller sends the 1st transaction.
|
||||||
|
|
||||||
|
1. Seller posts offer.
|
||||||
|
- smsg from seller to network
|
||||||
|
coin-from
|
||||||
|
coin-to
|
||||||
|
amount-from
|
||||||
|
rate
|
||||||
|
min-amount
|
||||||
|
time-valid
|
||||||
|
|
||||||
|
2. Buyer posts bid:
|
||||||
|
- smsg from buyer to seller
|
||||||
|
offerid
|
||||||
|
amount
|
||||||
|
proof-of-funds
|
||||||
|
address_to_buyer
|
||||||
|
time-valid
|
||||||
|
|
||||||
|
3. Seller accepts bid:
|
||||||
|
- verifies proof-of-funds
|
||||||
|
- generates secret
|
||||||
|
- submits initiate tx to coin-from network
|
||||||
|
- smsg from seller to buyer
|
||||||
|
txid
|
||||||
|
initiatescript (includes pkhash_to_seller as the pkhash_refund)
|
||||||
|
|
||||||
|
4. Buyer participates:
|
||||||
|
- inspects initiate tx in coin-from network
|
||||||
|
- submits participate tx in coin-to network
|
||||||
|
|
||||||
|
5. Seller redeems:
|
||||||
|
- constructs participatescript
|
||||||
|
- inspects participate tx in coin-to network
|
||||||
|
- redeems from participate tx revealing secret
|
||||||
|
|
||||||
|
6. Buyer redeems:
|
||||||
|
- scans coin-to network for seller-redeem tx
|
||||||
|
- redeems from initiate tx with revealed secret
|
||||||
|
|
|
@ -178,14 +178,22 @@ class Test(unittest.TestCase):
|
||||||
try:
|
try:
|
||||||
waitForServer(12700)
|
waitForServer(12700)
|
||||||
|
|
||||||
wallets = json.loads(urlopen('http://localhost:12700/json/wallets').read())
|
wallets_0 = json.loads(urlopen('http://localhost:12700/json/wallets').read())
|
||||||
print('[rm] wallets', dumpj(wallets))
|
print('[rm] wallets_0', dumpj(wallets_0))
|
||||||
|
assert(wallets_0['1']['expected_seed'] == True)
|
||||||
|
assert(wallets_0['6']['expected_seed'] == True)
|
||||||
|
|
||||||
waitForServer(12701)
|
waitForServer(12701)
|
||||||
wallets = json.loads(urlopen('http://localhost:12701/json/wallets').read())
|
wallets_1 = json.loads(urlopen('http://localhost:12701/json/wallets').read())
|
||||||
print('[rm] wallets', dumpj(wallets))
|
print('[rm] wallets_1', dumpj(wallets_1))
|
||||||
|
|
||||||
raise ValueError('TODO')
|
assert(wallets_0['1']['expected_seed'] == True)
|
||||||
|
assert(wallets_1['6']['expected_seed'] == True)
|
||||||
|
|
||||||
|
# TODO: Check other coins
|
||||||
|
|
||||||
|
assert(wallets_0['1']['deposit_address'] == wallets_1['1']['deposit_address'])
|
||||||
|
assert(wallets_0['6']['deposit_address'] == wallets_1['6']['deposit_address'])
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue