mirror of
https://github.com/JoinMarket-Org/joinmarket-clientserver.git
synced 2026-08-15 12:50:57 +02:00
203 lines
6.9 KiB
Python
203 lines
6.9 KiB
Python
#! /usr/bin/env python
|
|
from __future__ import absolute_import, print_function
|
|
|
|
"""
|
|
A sample implementation of a single coinjoin script,
|
|
adapted from `sendpayment.py` in Joinmarket-Org/joinmarket.
|
|
More complex applications can extend from Taker and add
|
|
more features, such as repeated joins. This will also allow
|
|
easier coding of non-CLI interfaces.
|
|
|
|
Other potential customisations of the Taker object instantiation
|
|
include:
|
|
|
|
external_addr=None implies joining to another mixdepth
|
|
in the same wallet.
|
|
|
|
order_chooser can be set to a different custom function that selects
|
|
counterparty offers according to different rules.
|
|
"""
|
|
|
|
import random
|
|
import sys
|
|
import threading
|
|
from optparse import OptionParser
|
|
|
|
import time
|
|
|
|
from client import (Taker, load_program_config,
|
|
JMTakerClientProtocolFactory, start_reactor,
|
|
validate_address, jm_single,
|
|
choose_orders, choose_sweep_orders, pick_order,
|
|
cheapest_order_choose, weighted_order_choose,
|
|
Wallet, BitcoinCoreWallet,
|
|
estimate_tx_fee)
|
|
|
|
from base.support import get_log, debug_dump_object
|
|
|
|
log = get_log()
|
|
|
|
|
|
def check_high_fee(total_fee_pc):
|
|
WARNING_THRESHOLD = 0.02 # 2%
|
|
if total_fee_pc > WARNING_THRESHOLD:
|
|
print('\n'.join(['=' * 60] * 3))
|
|
print('WARNING ' * 6)
|
|
print('\n'.join(['=' * 60] * 1))
|
|
print('OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.')
|
|
print('\n'.join(['=' * 60] * 1))
|
|
print('WARNING ' * 6)
|
|
print('\n'.join(['=' * 60] * 3))
|
|
|
|
|
|
def main():
|
|
parser = OptionParser(
|
|
usage=
|
|
'usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]',
|
|
description='Sends a single payment from a given mixing depth of your '
|
|
+
|
|
'wallet to an given address using coinjoin and then switches off. Also sends from bitcoinqt. '
|
|
+
|
|
'Setting amount to zero will do a sweep, where the entire mix depth is emptied')
|
|
parser.add_option(
|
|
'-f',
|
|
'--txfee',
|
|
action='store',
|
|
type='int',
|
|
dest='txfee',
|
|
default=-1,
|
|
help=
|
|
'number of satoshis per participant to use as the initial estimate ' +
|
|
'for the total transaction fee, default=dynamically estimated, note that this is adjusted '
|
|
+
|
|
'based on the estimated fee calculated after tx construction, based on '
|
|
+ 'policy set in joinmarket.cfg.')
|
|
parser.add_option(
|
|
'-w',
|
|
'--wait-time',
|
|
action='store',
|
|
type='float',
|
|
dest='waittime',
|
|
help='wait time in seconds to allow orders to arrive, default=15',
|
|
default=15)
|
|
parser.add_option(
|
|
'-N',
|
|
'--makercount',
|
|
action='store',
|
|
type='int',
|
|
dest='makercount',
|
|
help='how many makers to coinjoin with, default random from 4 to 6',
|
|
default=random.randint(4, 6))
|
|
parser.add_option('-p',
|
|
'--port',
|
|
type='int',
|
|
dest='daemonport',
|
|
help='port on which joinmarketd is running',
|
|
default='12345')
|
|
parser.add_option(
|
|
'-C',
|
|
'--choose-cheapest',
|
|
action='store_true',
|
|
dest='choosecheapest',
|
|
default=False,
|
|
help=
|
|
'override weightened offers picking and choose cheapest. this might reduce anonymity.')
|
|
parser.add_option(
|
|
'-P',
|
|
'--pick-orders',
|
|
action='store_true',
|
|
dest='pickorders',
|
|
default=False,
|
|
help=
|
|
'manually pick which orders to take. doesn\'t work while sweeping.')
|
|
parser.add_option('-m',
|
|
'--mixdepth',
|
|
action='store',
|
|
type='int',
|
|
dest='mixdepth',
|
|
help='mixing depth to spend from, default=0',
|
|
default=0)
|
|
parser.add_option('-a',
|
|
'--amtmixdepths',
|
|
action='store',
|
|
type='int',
|
|
dest='amtmixdepths',
|
|
help='number of mixdepths in wallet, default 5',
|
|
default=5)
|
|
parser.add_option('-g',
|
|
'--gap-limit',
|
|
type="int",
|
|
action='store',
|
|
dest='gaplimit',
|
|
help='gap limit for wallet, default=6',
|
|
default=6)
|
|
parser.add_option('--yes',
|
|
action='store_true',
|
|
dest='answeryes',
|
|
default=False,
|
|
help='answer yes to everything')
|
|
parser.add_option(
|
|
'--rpcwallet',
|
|
action='store_true',
|
|
dest='userpcwallet',
|
|
default=False,
|
|
help=('Use the Bitcoin Core wallet through json rpc, instead '
|
|
'of the internal joinmarket wallet. Requires '
|
|
'blockchain_source=json-rpc'))
|
|
(options, args) = parser.parse_args()
|
|
|
|
if len(args) < 3:
|
|
parser.error('Needs a wallet, amount and destination address')
|
|
sys.exit(0)
|
|
wallet_name = args[0]
|
|
amount = int(args[1])
|
|
destaddr = args[2]
|
|
|
|
load_program_config()
|
|
jm_single().maker_timeout_sec = 5
|
|
addr_valid, errormsg = validate_address(destaddr)
|
|
if not addr_valid:
|
|
print('ERROR: Address invalid. ' + errormsg)
|
|
return
|
|
|
|
chooseOrdersFunc = None
|
|
if options.pickorders:
|
|
chooseOrdersFunc = pick_order
|
|
if amount == 0:
|
|
print('WARNING: You may have to pick offers multiple times')
|
|
print('WARNING: due to manual offer picking while sweeping')
|
|
elif options.choosecheapest:
|
|
chooseOrdersFunc = cheapest_order_choose
|
|
else: # choose randomly (weighted)
|
|
chooseOrdersFunc = weighted_order_choose
|
|
|
|
# Dynamically estimate a realistic fee if it currently is the default value.
|
|
# At this point we do not know even the number of our own inputs, so
|
|
# we guess conservatively with 2 inputs and 2 outputs each
|
|
if options.txfee == -1:
|
|
options.txfee = max(options.txfee, estimate_tx_fee(2, 2))
|
|
log.debug("Estimated miner/tx fee for each cj participant: " + str(
|
|
options.txfee))
|
|
assert (options.txfee >= 0)
|
|
|
|
log.debug('starting sendpayment')
|
|
|
|
if not options.userpcwallet:
|
|
wallet = Wallet(wallet_name, options.amtmixdepths, options.gaplimit)
|
|
else:
|
|
wallet = BitcoinCoreWallet(fromaccount=wallet_name)
|
|
jm_single().bc_interface.sync_wallet(wallet)
|
|
|
|
taker = Taker(wallet,
|
|
options.mixdepth,
|
|
amount,
|
|
options.makercount,
|
|
order_chooser=chooseOrdersFunc,
|
|
external_addr=destaddr)
|
|
clientfactory = JMTakerClientProtocolFactory(taker)
|
|
start_reactor("localhost", options.daemonport, clientfactory)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
print('done')
|