Compare commits
56 Commits
c725c05947
...
5d39cce124
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d39cce124 | ||
|
|
5975c105d6 | ||
|
|
9ab1aab6f8 | ||
|
|
0de954546a | ||
|
|
92bf409bf0 | ||
|
|
25a460f855 | ||
|
|
52520490c5 | ||
|
|
5313591c28 | ||
|
|
7b8114f865 | ||
|
|
f60d1e7cb6 | ||
|
|
8310195453 | ||
|
|
cdaf874228 | ||
|
|
1a111b98d4 | ||
|
|
0cd2f6e170 | ||
|
|
e89ec0d1d5 | ||
|
|
eb418f1d3e | ||
|
|
e28b8806ac | ||
|
|
501371c001 | ||
|
|
94b7eb3015 | ||
|
|
1e1caf602d | ||
|
|
ed1421da8e | ||
|
|
8f6a1880e9 | ||
|
|
2cda8b266f | ||
|
|
f66ab7ce57 | ||
|
|
89cc1efc41 | ||
|
|
8702ed096b | ||
|
|
091b8bc73f | ||
|
|
cd8be3f586 | ||
|
|
3c0f05ae93 | ||
|
|
300241428c | ||
|
|
64733e93d9 | ||
|
|
dd6e5ae9ac | ||
|
|
34e84858c8 | ||
|
|
ecea61d9ca | ||
|
|
b3a21f53bc | ||
|
|
085cd9919e | ||
|
|
d418976f5e | ||
|
|
91273f7114 | ||
|
|
4b1b6e3adf | ||
|
|
6a2d2fbc3d | ||
|
|
826a56311c | ||
|
|
2ce11fa83b | ||
|
|
4253dd27eb | ||
|
|
f2e93e323a | ||
|
|
9e0777a7b4 | ||
|
|
278a6bcb4d | ||
|
|
66777d4566 | ||
|
|
d8d2f3329b | ||
|
|
1e6edd0abc | ||
|
|
c0daef2657 | ||
|
|
a64228ba2c | ||
|
|
85bae50e62 | ||
|
|
ca30705c69 | ||
|
|
95b7aef579 | ||
|
|
e0d33279de | ||
|
|
f008f2e4dc |
13
AUTHORS
@ -1,3 +1,5 @@
|
||||
Electrum-BTC
|
||||
------------
|
||||
ThomasV - Creator and maintainer.
|
||||
Animazing / Tachikoma - Styled the new GUI. Mac version.
|
||||
Azelphur - GUI stuff.
|
||||
@ -9,4 +11,13 @@ Genjix - Porting pro-mode functionality to lite-gui and worked on server
|
||||
Slush - Work on the server. Designed the original Stratum spec.
|
||||
Julian Toash (Tuxavant) - Various fixes to the client.
|
||||
rdymac - Website and translations.
|
||||
kyuupichan - Miscellaneous.
|
||||
kyuupichan - Miscellaneous.
|
||||
|
||||
|
||||
FLO-Electrum
|
||||
------------
|
||||
vivekteega - Maintainer and remaining stuff
|
||||
Bitspill - Bootstraped the project with core FLO changes
|
||||
Rohit Tripathy - Ideation and problem solving
|
||||
akhil2015 - Flodata and scrypt hashing
|
||||
|
||||
|
||||
@ -140,3 +140,16 @@ yarl==1.3.0 \
|
||||
colorama==0.4.1 \
|
||||
--hash=sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d \
|
||||
--hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48
|
||||
pylibscrypt==1.7.1 \
|
||||
--hash=sha256:7aa9424e211a12106c67ea884ccfe609856289372b900d3702faaf66e87f79ac
|
||||
scrypt==0.8.6 \
|
||||
--hash=sha256:85919f023148cd9fb01d75ad4e3e061928c298fa6249a0cd6cd469c4b947595e \
|
||||
--hash=sha256:4ad7188f2e42dbee2ff1cd72e3da40b170ba41847effbf0d726444f62ae60f3a \
|
||||
--hash=sha256:bc131f74a688fa09993c518ca666a2ebd4268b207e039cbab03a034228140d3e \
|
||||
--hash=sha256:232acdbc3434d2de55def8d5dbf1bc4b9bfc50da7c5741df2a6eebc4e18d3720 \
|
||||
--hash=sha256:971db040d3963ebe4b919a203fe10d7d6659951d3644066314330983dc175ed4 \
|
||||
--hash=sha256:475ac80239b3d788ae71a09c3019ca915e149aaa339adcdd1c9eef121293dc88 \
|
||||
--hash=sha256:18ccbc63d87c6f89b753194194bb37aeaf1abc517e4b989461d115c1d93ce128 \
|
||||
--hash=sha256:c23daecee405cb036845917295c76f8d747fc890158df40cb304b4b3c3640079 \
|
||||
--hash=sha256:f8239b2d47fa1d40bc27efd231dc7083695d10c1c2ac51a99380360741e0362d
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Cython>=0.27
|
||||
trezor[hidapi]>=0.11.0
|
||||
trezor[hidapi]>=0.11.1
|
||||
safet[hidapi]>=0.1.0
|
||||
keepkey
|
||||
btchip-python>=0.1.26
|
||||
|
||||
@ -9,3 +9,4 @@ aiorpcx>=0.9,<0.11
|
||||
aiohttp>=3.3.0
|
||||
aiohttp_socks
|
||||
certifi
|
||||
pylibscrypt==1.7.1
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
version=`git describe --tags --dirty --always`
|
||||
version=`python3 -c "import electrum; print(electrum.version.ELECTRUM_VERSION)"`
|
||||
sig=`./run_electrum -w $SIGNING_WALLET signmessage $SIGNING_ADDRESS $version`
|
||||
echo "{ \"version\":\"$version\", \"signatures\":{ \"$addr\":\"$sig\"}}"
|
||||
echo "{ \"version\":\"$version\", \"signatures\":{ \"$SIGNING_ADDRESS\":\"$sig\"}}"
|
||||
|
||||
@ -73,12 +73,13 @@ class AddressSynchronizer(PrintError):
|
||||
# Verified transactions. txid -> TxMinedInfo. Access with self.lock.
|
||||
verified_tx = storage.get('verified_tx3', {})
|
||||
self.verified_tx = {} # type: Dict[str, TxMinedInfo]
|
||||
for txid, (height, timestamp, txpos, header_hash) in verified_tx.items():
|
||||
for txid, (height, timestamp, txpos, header_hash, flodata) in verified_tx.items():
|
||||
self.verified_tx[txid] = TxMinedInfo(height=height,
|
||||
conf=None,
|
||||
timestamp=timestamp,
|
||||
txpos=txpos,
|
||||
header_hash=header_hash)
|
||||
header_hash=header_hash,
|
||||
flodata=flodata)
|
||||
# Transactions pending verification. txid -> tx_height. Access with self.lock.
|
||||
self.unverified_tx = defaultdict(int)
|
||||
# true when synchronized
|
||||
@ -454,7 +455,7 @@ class AddressSynchronizer(PrintError):
|
||||
verified_tx_to_save = {}
|
||||
for txid, tx_info in self.verified_tx.items():
|
||||
verified_tx_to_save[txid] = (tx_info.height, tx_info.timestamp,
|
||||
tx_info.txpos, tx_info.header_hash)
|
||||
tx_info.txpos, tx_info.header_hash, tx_info.flodata)
|
||||
self.storage.put('verified_tx3', verified_tx_to_save)
|
||||
if write:
|
||||
self.storage.write()
|
||||
@ -645,6 +646,23 @@ class AddressSynchronizer(PrintError):
|
||||
# local transaction
|
||||
return TxMinedInfo(height=TX_HEIGHT_LOCAL, conf=0)
|
||||
|
||||
def get_flodata(self, tx_hash: str):
|
||||
""" Given a transaction, returns flodata """
|
||||
with self.lock:
|
||||
if tx_hash in self.verified_tx:
|
||||
info = self.verified_tx[tx_hash]
|
||||
flodata = info[5]
|
||||
return flodata
|
||||
elif tx_hash in self.unverified_tx:
|
||||
tx = self.transactions.get(tx_hash)
|
||||
flodata = tx.flodata[5:]
|
||||
return flodata
|
||||
else:
|
||||
# local transaction
|
||||
tx = self.transactions.get(tx_hash)
|
||||
flodata = tx.flodata[5:]
|
||||
return flodata
|
||||
|
||||
def set_up_to_date(self, up_to_date):
|
||||
with self.lock:
|
||||
self.up_to_date = up_to_date
|
||||
@ -794,7 +812,7 @@ class AddressSynchronizer(PrintError):
|
||||
|
||||
@with_local_height_cached
|
||||
def get_addr_balance(self, address):
|
||||
"""Return the balance of a bitcoin address:
|
||||
"""Return the balance of a FLO address:
|
||||
confirmed and matured, unconfirmed, unmatured
|
||||
"""
|
||||
received, sent = self.get_addr_io(address)
|
||||
|
||||
@ -124,9 +124,8 @@ class BaseWizard(object):
|
||||
])
|
||||
wallet_kinds = [
|
||||
('standard', _("Standard wallet")),
|
||||
('2fa', _("Wallet with two-factor authentication")),
|
||||
('multisig', _("Multi-signature wallet")),
|
||||
('imported', _("Import Bitcoin addresses or private keys")),
|
||||
('imported', _("Import FLO addresses or private keys")),
|
||||
]
|
||||
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
|
||||
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
|
||||
@ -199,8 +198,8 @@ class BaseWizard(object):
|
||||
|
||||
def import_addresses_or_keys(self):
|
||||
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x)
|
||||
title = _("Import Bitcoin Addresses")
|
||||
message = _("Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.")
|
||||
title = _("Import FLO Addresses")
|
||||
message = _("Enter a list of FLO addresses (this will create a watching-only wallet), or a list of private keys.")
|
||||
self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,
|
||||
is_valid=v, allow_multi=True, show_wif_help=True)
|
||||
|
||||
@ -570,14 +569,10 @@ class BaseWizard(object):
|
||||
title = _('Choose Seed type')
|
||||
if message is None:
|
||||
message = ' '.join([
|
||||
_("The type of addresses used by your wallet will depend on your seed."),
|
||||
_("Segwit wallets use bech32 addresses, defined in BIP173."),
|
||||
_("Please note that websites and other wallets may not support these addresses yet."),
|
||||
_("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.")
|
||||
_("The type of addresses used by your wallet will depend on your seed.")
|
||||
])
|
||||
if choices is None:
|
||||
choices = [
|
||||
('create_segwit_seed', _('Segwit')),
|
||||
('create_standard_seed', _('Legacy')),
|
||||
]
|
||||
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
|
||||
|
||||
@ -31,9 +31,15 @@ from . import constants
|
||||
from .util import bfh, bh2u
|
||||
from .simple_config import SimpleConfig
|
||||
|
||||
try:
|
||||
import pylibscrypt
|
||||
getPoWHash = lambda x: pylibscrypt.scrypt(password=x, salt=x, N=1024, r=1, p=1, olen=32)
|
||||
except ImportError:
|
||||
util.print_msg("Warning: package pylibscrypt not available")
|
||||
|
||||
|
||||
HEADER_SIZE = 80 # bytes
|
||||
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
|
||||
MAX_TARGET = 0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
|
||||
|
||||
class MissingHeader(Exception):
|
||||
@ -147,6 +153,10 @@ def read_blockchains(config: 'SimpleConfig'):
|
||||
instantiate_chain(filename)
|
||||
|
||||
|
||||
def pow_hash_header(header):
|
||||
return hash_encode(getPoWHash(bfh(serialize_header(header))))
|
||||
|
||||
|
||||
def get_best_chain() -> 'Blockchain':
|
||||
return blockchains[constants.net.GENESIS]
|
||||
|
||||
@ -251,9 +261,13 @@ class Blockchain(util.PrintError):
|
||||
p = self.path()
|
||||
self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0
|
||||
|
||||
#def pow_hash_header(header):
|
||||
# return hash_encode(getPoWHash(bfh(serialize_header(header))))
|
||||
|
||||
@classmethod
|
||||
def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
|
||||
_hash = hash_header(header)
|
||||
_powhash = pow_hash_header(header)
|
||||
if expected_header_hash and expected_header_hash != _hash:
|
||||
raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash))
|
||||
if prev_hash != header.get('prev_block_hash'):
|
||||
@ -261,27 +275,63 @@ class Blockchain(util.PrintError):
|
||||
if constants.net.TESTNET:
|
||||
return
|
||||
bits = cls.target_to_bits(target)
|
||||
bits = target
|
||||
if bits != header.get('bits'):
|
||||
raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
|
||||
block_hash_as_num = int.from_bytes(bfh(_hash), byteorder='big')
|
||||
if block_hash_as_num > target:
|
||||
raise Exception(f"insufficient proof of work: {block_hash_as_num} vs target {target}")
|
||||
target_val = cls.bits_to_target(bits)
|
||||
if int('0x' + _powhash, 16) > target_val:
|
||||
raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val))
|
||||
|
||||
def verify_chunk(self, index: int, data: bytes) -> None:
|
||||
num = len(data) // HEADER_SIZE
|
||||
start_height = index * 2016
|
||||
prev_hash = self.get_hash(start_height - 1)
|
||||
target = self.get_target(index-1)
|
||||
current_header = index * 2016
|
||||
print('chunk ' + str(index))
|
||||
prev_hash = self.get_hash(current_header - 1)
|
||||
headerLast = None
|
||||
headerFirst = None
|
||||
capture = None
|
||||
lst = []
|
||||
for i in range(num):
|
||||
height = start_height + i
|
||||
try:
|
||||
expected_header_hash = self.get_hash(height)
|
||||
except MissingHeader:
|
||||
expected_header_hash = None
|
||||
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
|
||||
header = deserialize_header(raw_header, index*2016 + i)
|
||||
self.verify_header(header, prev_hash, target, expected_header_hash)
|
||||
prev_hash = hash_header(header)
|
||||
averaging_interval = self.AveragingInterval(current_header)
|
||||
difficulty_interval = self.DifficultyAdjustmentInterval(current_header)
|
||||
if current_header < 426000:
|
||||
target = self.get_target(current_header - 1, headerLast, headerFirst)
|
||||
try:
|
||||
expected_header_hash = self.get_hash(current_header)
|
||||
except MissingHeader:
|
||||
expected_header_hash = None
|
||||
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
|
||||
header = deserialize_header(raw_header, current_header)
|
||||
self.verify_header(header, prev_hash, target, expected_header_hash)
|
||||
prev_hash = hash_header(header)
|
||||
headerLast = header
|
||||
if current_header == 0:
|
||||
headerFirst = header
|
||||
elif (current_header + averaging_interval + 1) % difficulty_interval == 0:
|
||||
capture = header
|
||||
if current_header != 0 and current_header % difficulty_interval == 0:
|
||||
headerFirst = capture
|
||||
if current_header >= 425993:
|
||||
lst.append(headerLast)
|
||||
current_header = current_header + 1
|
||||
else:
|
||||
if len(lst)>6:
|
||||
headerFirst = lst[0]
|
||||
target = self.get_target(current_header - 1, headerLast, headerFirst)
|
||||
try:
|
||||
expected_header_hash = self.get_hash(current_header)
|
||||
except MissingHeader:
|
||||
expected_header_hash = None
|
||||
raw_header = data[i * HEADER_SIZE: (i + 1) * HEADER_SIZE]
|
||||
header = deserialize_header(raw_header, current_header)
|
||||
self.verify_header(header, prev_hash, target, expected_header_hash)
|
||||
prev_hash = hash_header(header)
|
||||
headerLast = header
|
||||
lst.append(header)
|
||||
if len(lst)>7:
|
||||
lst.pop(0)
|
||||
current_header = current_header + 1
|
||||
|
||||
@with_lock
|
||||
def path(self):
|
||||
@ -451,40 +501,70 @@ class Blockchain(util.PrintError):
|
||||
raise MissingHeader(height)
|
||||
return hash_header(header)
|
||||
|
||||
def get_target(self, index: int) -> int:
|
||||
def get_target(self, index: int, headerLast: dict=None, headerFirst: dict=None) -> int:
|
||||
# compute target from chunk x, used in chunk x+1
|
||||
if constants.net.TESTNET:
|
||||
return 0
|
||||
if index == -1:
|
||||
return MAX_TARGET
|
||||
# The range is first 90 blocks because FLO's block time was 90 blocks when it started
|
||||
if -1 <= index <= 88:
|
||||
return 0x1e0ffff0
|
||||
if index < len(self.checkpoints):
|
||||
h, t = self.checkpoints[index]
|
||||
return t
|
||||
# new target
|
||||
first = self.read_header(index * 2016)
|
||||
last = self.read_header(index * 2016 + 2015)
|
||||
if not first or not last:
|
||||
raise MissingHeader()
|
||||
bits = last.get('bits')
|
||||
target = self.bits_to_target(bits)
|
||||
nActualTimespan = last.get('timestamp') - first.get('timestamp')
|
||||
nTargetTimespan = 14 * 24 * 60 * 60
|
||||
nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
|
||||
nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
|
||||
new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
|
||||
# not any target can be represented in 32 bits:
|
||||
new_target = self.bits_to_target(self.target_to_bits(new_target))
|
||||
return new_target
|
||||
if headerLast is None:
|
||||
headerLast = self.read_header(index)
|
||||
height = headerLast["block_height"]
|
||||
# check if the height passes is in range for retargeting
|
||||
if (height + 1) % self.DifficultyAdjustmentInterval(height + 1) != 0:
|
||||
return int(headerLast["bits"])
|
||||
if headerFirst is None:
|
||||
averagingInterval = self.AveragingInterval(height + 1)
|
||||
blockstogoback = averagingInterval - 1
|
||||
# print("Blocks to go back = " + str(blockstogoback))
|
||||
if (height + 1) != averagingInterval:
|
||||
blockstogoback = averagingInterval
|
||||
firstHeight = height - blockstogoback
|
||||
headerFirst = self.read_header(int(firstHeight))
|
||||
|
||||
firstBlockTime = headerFirst["timestamp"]
|
||||
nMinActualTimespan = int(self.MinActualTimespan(int(headerLast["block_height"]) + 1))
|
||||
|
||||
nMaxActualTimespan = int(self.MaxActualTimespan(int(headerLast["block_height"]) + 1))
|
||||
# Limit adjustment step
|
||||
nActualTimespan = headerLast["timestamp"] - firstBlockTime
|
||||
if nActualTimespan < nMinActualTimespan:
|
||||
nActualTimespan = nMinActualTimespan
|
||||
if nActualTimespan > nMaxActualTimespan:
|
||||
nActualTimespan = nMaxActualTimespan
|
||||
# Retarget
|
||||
bnNewBits = int(headerLast["bits"])
|
||||
bnNew = self.bits_to_target(bnNewBits)
|
||||
bnOld = bnNew
|
||||
# FLO: intermediate uint256 can overflow by 1 bit
|
||||
# const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
|
||||
fShift = bnNew > MAX_TARGET - 1
|
||||
|
||||
if (fShift):
|
||||
bnNew = bnNew >> 1
|
||||
bnNew = bnNew * nActualTimespan
|
||||
bnNew = bnNew / self.TargetTimespan(headerLast["block_height"] + 1)
|
||||
if fShift:
|
||||
bnNew = bnNew << 1
|
||||
if bnNew > MAX_TARGET:
|
||||
bnNew = MAX_TARGET
|
||||
bnNew = self.target_to_bits(int(bnNew))
|
||||
return bnNew
|
||||
|
||||
@classmethod
|
||||
def bits_to_target(cls, bits: int) -> int:
|
||||
bitsN = (bits >> 24) & 0xff
|
||||
if not (0x03 <= bitsN <= 0x1d):
|
||||
raise Exception("First part of bits should be in [0x03, 0x1d]")
|
||||
if not (0x03 <= bitsN <= 0x1e):
|
||||
raise Exception("First part of bits should be in [0x03, 0x1e]")
|
||||
bitsBase = bits & 0xffffff
|
||||
if not (0x8000 <= bitsBase <= 0x7fffff):
|
||||
raise Exception("Second part of bits should be in [0x8000, 0x7fffff]")
|
||||
return bitsBase << (8 * (bitsN-3))
|
||||
return bitsBase << (8 * (bitsN - 3))
|
||||
|
||||
@classmethod
|
||||
def target_to_bits(cls, target: int) -> int:
|
||||
@ -547,7 +627,7 @@ class Blockchain(util.PrintError):
|
||||
if prev_hash != header.get('prev_block_hash'):
|
||||
return False
|
||||
try:
|
||||
target = self.get_target(height // 2016 - 1)
|
||||
target = self.get_target(height - 1)
|
||||
except MissingHeader:
|
||||
return False
|
||||
try:
|
||||
@ -578,6 +658,61 @@ class Blockchain(util.PrintError):
|
||||
cp.append((h, target))
|
||||
return cp
|
||||
|
||||
def AveragingInterval(self, height):
|
||||
# V1
|
||||
if height < constants.net.nHeight_Difficulty_Version2:
|
||||
return constants.net.nAveragingInterval_Version1
|
||||
# V2
|
||||
elif height < constants.net.nHeight_Difficulty_Version3:
|
||||
return constants.net.nAveragingInterval_Version2
|
||||
# V3
|
||||
else:
|
||||
return constants.net.nAveragingInterval_Version3
|
||||
|
||||
def MinActualTimespan(self, height):
|
||||
averagingTargetTimespan = self.AveragingInterval(height) * constants.net.nPowTargetSpacing
|
||||
# V1
|
||||
if height < constants.net.nHeight_Difficulty_Version2:
|
||||
return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version1) / 100)
|
||||
# V2
|
||||
elif height < constants.net.nHeight_Difficulty_Version3:
|
||||
return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version2) / 100)
|
||||
# V3
|
||||
else:
|
||||
return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version3) / 100)
|
||||
|
||||
def MaxActualTimespan(self, height):
|
||||
averagingTargetTimespan = self.AveragingInterval(height) * constants.net.nPowTargetSpacing
|
||||
# V1
|
||||
if height < constants.net.nHeight_Difficulty_Version2:
|
||||
return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version1) / 100)
|
||||
# V2
|
||||
elif height < constants.net.nHeight_Difficulty_Version3:
|
||||
return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version2) / 100)
|
||||
# V3
|
||||
else:
|
||||
return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version3) / 100)
|
||||
|
||||
def TargetTimespan(self, height):
|
||||
# V1
|
||||
if height < constants.net.nHeight_Difficulty_Version2:
|
||||
return constants.net.nTargetTimespan_Version1
|
||||
# V2
|
||||
if height < constants.net.nHeight_Difficulty_Version3:
|
||||
return constants.net.nAveragingInterval_Version2 * constants.net.nPowTargetSpacing
|
||||
# V3
|
||||
return constants.net.nAveragingInterval_Version3 * constants.net.nPowTargetSpacing
|
||||
|
||||
def DifficultyAdjustmentInterval(self, height):
|
||||
# V1
|
||||
if height < constants.net.nHeight_Difficulty_Version2:
|
||||
return constants.net.nInterval_Version1
|
||||
# V2
|
||||
if height < constants.net.nHeight_Difficulty_Version3:
|
||||
return constants.net.nInterval_Version2
|
||||
# V3
|
||||
return constants.net.nInterval_Version3
|
||||
|
||||
|
||||
def check_header(header: dict) -> Optional[Blockchain]:
|
||||
if type(header) is not dict:
|
||||
|
||||
@ -47,59 +47,104 @@ class AbstractNet:
|
||||
class BitcoinMainnet(AbstractNet):
|
||||
|
||||
TESTNET = False
|
||||
WIF_PREFIX = 0x80
|
||||
ADDRTYPE_P2PKH = 0
|
||||
ADDRTYPE_P2SH = 5
|
||||
SEGWIT_HRP = "bc"
|
||||
GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||
WIF_PREFIX = 0xa3
|
||||
ADDRTYPE_P2PKH = 35
|
||||
ADDRTYPE_P2SH = 94
|
||||
SEGWIT_HRP = "flo"
|
||||
GENESIS = "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea"
|
||||
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
||||
DEFAULT_SERVERS = read_json('servers.json', {})
|
||||
CHECKPOINTS = read_json('checkpoints.json', [])
|
||||
|
||||
XPRV_HEADERS = {
|
||||
'standard': 0x0488ade4, # xprv
|
||||
'standard': 0x01343c31, # xprv
|
||||
'p2wpkh-p2sh': 0x049d7878, # yprv
|
||||
'p2wsh-p2sh': 0x0295b005, # Yprv
|
||||
'p2wpkh': 0x04b2430c, # zprv
|
||||
'p2wsh': 0x02aa7a99, # Zprv
|
||||
}
|
||||
XPUB_HEADERS = {
|
||||
'standard': 0x0488b21e, # xpub
|
||||
'standard': 0x0134406b, # xpub
|
||||
'p2wpkh-p2sh': 0x049d7cb2, # ypub
|
||||
'p2wsh-p2sh': 0x0295b43f, # Ypub
|
||||
'p2wpkh': 0x04b24746, # zpub
|
||||
'p2wsh': 0x02aa7ed3, # Zpub
|
||||
}
|
||||
BIP44_COIN_TYPE = 0
|
||||
BIP44_COIN_TYPE = 216
|
||||
# FLO Network constants
|
||||
fPowAllowMinDifficultyBlocks = False
|
||||
fPowNoRetargeting = False
|
||||
nRuleChangeActivationThreshold = 6048 # 75% of 8064
|
||||
nMinerConfirmationWindow = 8064
|
||||
# Difficulty adjustments
|
||||
nPowTargetSpacing = 40 # 40s block time
|
||||
# V1
|
||||
nTargetTimespan_Version1 = 60 * 60
|
||||
nInterval_Version1 = nTargetTimespan_Version1 / nPowTargetSpacing
|
||||
nMaxAdjustUp_Version1 = 75
|
||||
nMaxAdjustDown_Version1 = 300
|
||||
nAveragingInterval_Version1 = nInterval_Version1
|
||||
# V2
|
||||
nHeight_Difficulty_Version2 = 208440
|
||||
nInterval_Version2 = 15
|
||||
nMaxAdjustDown_Version2 = 300
|
||||
nMaxAdjustUp_Version2 = 75
|
||||
nAveragingInterval_Version2 = nInterval_Version2
|
||||
# V3
|
||||
nHeight_Difficulty_Version3 = 426000
|
||||
nInterval_Version3 = 1
|
||||
nMaxAdjustDown_Version3 = 3
|
||||
nMaxAdjustUp_Version3 = 2
|
||||
nAveragingInterval_Version3 = 6
|
||||
|
||||
|
||||
class BitcoinTestnet(AbstractNet):
|
||||
|
||||
TESTNET = True
|
||||
WIF_PREFIX = 0xef
|
||||
ADDRTYPE_P2PKH = 111
|
||||
ADDRTYPE_P2SH = 196
|
||||
SEGWIT_HRP = "tb"
|
||||
GENESIS = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
|
||||
ADDRTYPE_P2PKH = 115
|
||||
ADDRTYPE_P2SH = 58
|
||||
SEGWIT_HRP = "tflo"
|
||||
GENESIS = "9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731"
|
||||
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
|
||||
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
|
||||
CHECKPOINTS = read_json('checkpoints_testnet.json', [])
|
||||
|
||||
XPRV_HEADERS = {
|
||||
'standard': 0x04358394, # tprv
|
||||
'standard': 0x01343c23, # tprv
|
||||
'p2wpkh-p2sh': 0x044a4e28, # uprv
|
||||
'p2wsh-p2sh': 0x024285b5, # Uprv
|
||||
'p2wpkh': 0x045f18bc, # vprv
|
||||
'p2wsh': 0x02575048, # Vprv
|
||||
}
|
||||
XPUB_HEADERS = {
|
||||
'standard': 0x043587cf, # tpub
|
||||
'standard': 0x013440e2, # tpub
|
||||
'p2wpkh-p2sh': 0x044a5262, # upub
|
||||
'p2wsh-p2sh': 0x024289ef, # Upub
|
||||
'p2wpkh': 0x045f1cf6, # vpub
|
||||
'p2wsh': 0x02575483, # Vpub
|
||||
}
|
||||
BIP44_COIN_TYPE = 1
|
||||
#Difficulty adjustments
|
||||
nPowTargetSpacing = 40 # 40 block time
|
||||
# V1
|
||||
nTargetTimespan_Version1 = 60 * 60
|
||||
nInterval_Version1 = nTargetTimespan_Version1 / nPowTargetSpacing;
|
||||
nMaxAdjustUp_Version1 = 75
|
||||
nMaxAdjustDown_Version1 = 300
|
||||
nAveragingInterval_Version1 = nInterval_Version1
|
||||
# V2
|
||||
nHeight_Difficulty_Version2 = 50000
|
||||
nInterval_Version2 = 15
|
||||
nMaxAdjustDown_Version2 = 300
|
||||
nMaxAdjustUp_Version2 = 75
|
||||
nAveragingInterval_Version2 = nInterval_Version2
|
||||
# V3
|
||||
nHeight_Difficulty_Version3 = 60000
|
||||
nInterval_Version3 = 1
|
||||
nMaxAdjustDown_Version3 = 3
|
||||
nMaxAdjustUp_Version3 = 2
|
||||
nAveragingInterval_Version3 = 6
|
||||
|
||||
|
||||
class BitcoinRegtest(BitcoinTestnet):
|
||||
|
||||
@ -1,823 +1,44 @@
|
||||
{
|
||||
"BitFlyer": [
|
||||
"JPY"
|
||||
],
|
||||
"BitPay": [
|
||||
"CoinMarketcap": [
|
||||
"AED",
|
||||
"AFN",
|
||||
"ALL",
|
||||
"AMD",
|
||||
"ANG",
|
||||
"AOA",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"AWG",
|
||||
"AZN",
|
||||
"BAM",
|
||||
"BBD",
|
||||
"BCH",
|
||||
"BDT",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BIF",
|
||||
"BMD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BSD",
|
||||
"BTC",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BZD",
|
||||
"KHR",
|
||||
"CAD",
|
||||
"CDF",
|
||||
"CHF",
|
||||
"CLF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CUP",
|
||||
"CVE",
|
||||
"CZK",
|
||||
"DJF",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EGP",
|
||||
"ETB",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"FKP",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GHS",
|
||||
"GIP",
|
||||
"GMD",
|
||||
"GNF",
|
||||
"GTQ",
|
||||
"GYD",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HTG",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"INR",
|
||||
"IQD",
|
||||
"IRR",
|
||||
"ISK",
|
||||
"JEP",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"INR",
|
||||
"IDR",
|
||||
"IQD",
|
||||
"ILS",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KGS",
|
||||
"KHR",
|
||||
"KMF",
|
||||
"KPW",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LAK",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LRD",
|
||||
"LSL",
|
||||
"LYD",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MGA",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MNT",
|
||||
"MOP",
|
||||
"MRU",
|
||||
"MUR",
|
||||
"MVR",
|
||||
"MWK",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"MZN",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"MXN",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"RWF",
|
||||
"SAR",
|
||||
"SBD",
|
||||
"SCR",
|
||||
"SDG",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SHP",
|
||||
"SLL",
|
||||
"SOS",
|
||||
"SRD",
|
||||
"STN",
|
||||
"SVC",
|
||||
"SYP",
|
||||
"SZL",
|
||||
"THB",
|
||||
"TJS",
|
||||
"TMT",
|
||||
"TND",
|
||||
"TOP",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VEF",
|
||||
"VND",
|
||||
"VUV",
|
||||
"WST",
|
||||
"XAF",
|
||||
"XAG",
|
||||
"XAU",
|
||||
"XCD",
|
||||
"XOF",
|
||||
"XPF",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMW",
|
||||
"ZWL"
|
||||
],
|
||||
"BitStamp": [
|
||||
"USD"
|
||||
],
|
||||
"Bitbank": [
|
||||
"JPY"
|
||||
],
|
||||
"BitcoinAverage": [
|
||||
"AED",
|
||||
"AFN",
|
||||
"ALL",
|
||||
"AMD",
|
||||
"ANG",
|
||||
"AOA",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"AWG",
|
||||
"AZN",
|
||||
"BAM",
|
||||
"BBD",
|
||||
"BDT",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BIF",
|
||||
"BMD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BSD",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BYN",
|
||||
"BZD",
|
||||
"CAD",
|
||||
"CDF",
|
||||
"CHF",
|
||||
"CLF",
|
||||
"CLP",
|
||||
"CNH",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CUC",
|
||||
"CUP",
|
||||
"CVE",
|
||||
"CZK",
|
||||
"DJF",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EGP",
|
||||
"ERN",
|
||||
"ETB",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"FKP",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GGP",
|
||||
"GHS",
|
||||
"GIP",
|
||||
"GMD",
|
||||
"GNF",
|
||||
"GTQ",
|
||||
"GYD",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HTG",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"IMP",
|
||||
"INR",
|
||||
"IQD",
|
||||
"IRR",
|
||||
"ISK",
|
||||
"JEP",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KGS",
|
||||
"KHR",
|
||||
"KMF",
|
||||
"KPW",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LAK",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LRD",
|
||||
"LSL",
|
||||
"LYD",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MGA",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MNT",
|
||||
"MOP",
|
||||
"MRO",
|
||||
"MUR",
|
||||
"MVR",
|
||||
"MWK",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"MZN",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"RWF",
|
||||
"SAR",
|
||||
"SBD",
|
||||
"SCR",
|
||||
"SDG",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SHP",
|
||||
"SLL",
|
||||
"SOS",
|
||||
"SRD",
|
||||
"SSP",
|
||||
"STD",
|
||||
"SVC",
|
||||
"SYP",
|
||||
"SZL",
|
||||
"THB",
|
||||
"TJS",
|
||||
"TMT",
|
||||
"TND",
|
||||
"TOP",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VEF",
|
||||
"VND",
|
||||
"VUV",
|
||||
"WST",
|
||||
"XAF",
|
||||
"XAG",
|
||||
"XAU",
|
||||
"XCD",
|
||||
"XDR",
|
||||
"XOF",
|
||||
"XPD",
|
||||
"XPF",
|
||||
"XPT",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMW",
|
||||
"ZWL"
|
||||
],
|
||||
"Bitcointoyou": [
|
||||
"BRL"
|
||||
],
|
||||
"BitcoinVenezuela": [
|
||||
"ARS",
|
||||
"EUR",
|
||||
"USD",
|
||||
"VEF"
|
||||
],
|
||||
"Bitmarket": [
|
||||
"PLN"
|
||||
],
|
||||
"Bitso": [
|
||||
"MXN"
|
||||
],
|
||||
"Bitvalor": [
|
||||
"BRL"
|
||||
],
|
||||
"BlockchainInfo": [
|
||||
"AUD",
|
||||
"BRL",
|
||||
"CAD",
|
||||
"CHF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"DKK",
|
||||
"EUR",
|
||||
"GBP",
|
||||
"HKD",
|
||||
"INR",
|
||||
"ISK",
|
||||
"JPY",
|
||||
"KRW",
|
||||
"NZD",
|
||||
"PLN",
|
||||
"RUB",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"THB",
|
||||
"TWD",
|
||||
"USD"
|
||||
],
|
||||
"CoinDesk": [
|
||||
"AED",
|
||||
"AFN",
|
||||
"ALL",
|
||||
"AMD",
|
||||
"ANG",
|
||||
"AOA",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"AWG",
|
||||
"AZN",
|
||||
"BAM",
|
||||
"BBD",
|
||||
"BDT",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BIF",
|
||||
"BMD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BSD",
|
||||
"BTC",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BYR",
|
||||
"BZD",
|
||||
"CAD",
|
||||
"CDF",
|
||||
"CHF",
|
||||
"CLF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CUP",
|
||||
"CVE",
|
||||
"CZK",
|
||||
"DJF",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EEK",
|
||||
"EGP",
|
||||
"ERN",
|
||||
"ETB",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"FKP",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GHS",
|
||||
"GIP",
|
||||
"GMD",
|
||||
"GNF",
|
||||
"GTQ",
|
||||
"GYD",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HTG",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"INR",
|
||||
"IQD",
|
||||
"IRR",
|
||||
"ISK",
|
||||
"JEP",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KGS",
|
||||
"KHR",
|
||||
"KMF",
|
||||
"KPW",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LAK",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LRD",
|
||||
"LSL",
|
||||
"LTL",
|
||||
"LVL",
|
||||
"LYD",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MGA",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MNT",
|
||||
"MOP",
|
||||
"MRO",
|
||||
"MTL",
|
||||
"MUR",
|
||||
"MVR",
|
||||
"MWK",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"MZN",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"RWF",
|
||||
"SAR",
|
||||
"SBD",
|
||||
"SCR",
|
||||
"SDG",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SHP",
|
||||
"SLL",
|
||||
"SOS",
|
||||
"SRD",
|
||||
"STD",
|
||||
"SVC",
|
||||
"SYP",
|
||||
"SZL",
|
||||
"THB",
|
||||
"TJS",
|
||||
"TMT",
|
||||
"TND",
|
||||
"TOP",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VEF",
|
||||
"VND",
|
||||
"VUV",
|
||||
"WST",
|
||||
"XAF",
|
||||
"XAG",
|
||||
"XAU",
|
||||
"XBT",
|
||||
"XCD",
|
||||
"XDR",
|
||||
"XOF",
|
||||
"XPF",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMK",
|
||||
"ZMW",
|
||||
"ZWL"
|
||||
],
|
||||
"Coinbase": [
|
||||
"AED",
|
||||
"AFN",
|
||||
"ALL",
|
||||
"AMD",
|
||||
"ANG",
|
||||
"AOA",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"AWG",
|
||||
"AZN",
|
||||
"BAM",
|
||||
"BBD",
|
||||
"BCH",
|
||||
"BDT",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BIF",
|
||||
"BMD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BSD",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BYN",
|
||||
"BYR",
|
||||
"BZD",
|
||||
"CAD",
|
||||
"CDF",
|
||||
"CHF",
|
||||
"CLF",
|
||||
"CLP",
|
||||
"CNH",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CUC",
|
||||
"CVE",
|
||||
"CZK",
|
||||
"DJF",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EEK",
|
||||
"EGP",
|
||||
"ERN",
|
||||
"ETB",
|
||||
"ETH",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"FKP",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GGP",
|
||||
"GHS",
|
||||
"GIP",
|
||||
"GMD",
|
||||
"GNF",
|
||||
"GTQ",
|
||||
"GYD",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HTG",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"IMP",
|
||||
"INR",
|
||||
"IQD",
|
||||
"ISK",
|
||||
"JEP",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KGS",
|
||||
"KHR",
|
||||
"KMF",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LAK",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LRD",
|
||||
"LSL",
|
||||
"LTC",
|
||||
"LTL",
|
||||
"LVL",
|
||||
"LYD",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MGA",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MNT",
|
||||
"MOP",
|
||||
"MRO",
|
||||
"MTL",
|
||||
"MUR",
|
||||
"MVR",
|
||||
"MWK",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"MZN",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"RWF",
|
||||
"SAR",
|
||||
"SBD",
|
||||
"SCR",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SHP",
|
||||
"SLL",
|
||||
"SOS",
|
||||
"SRD",
|
||||
"SSP",
|
||||
"STD",
|
||||
"SVC",
|
||||
"SZL",
|
||||
"THB",
|
||||
"TJS",
|
||||
"TMT",
|
||||
"TND",
|
||||
"TOP",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VEF",
|
||||
"VND",
|
||||
"VUV",
|
||||
"WST",
|
||||
"XAF",
|
||||
"XAG",
|
||||
"XAU",
|
||||
"XCD",
|
||||
"XDR",
|
||||
"XOF",
|
||||
"XPD",
|
||||
"XPF",
|
||||
"XPT",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMK",
|
||||
"ZMW",
|
||||
"ZWL"
|
||||
],
|
||||
"Foxbit": [
|
||||
"BRL"
|
||||
],
|
||||
"Kraken": [
|
||||
"CAD",
|
||||
"EUR",
|
||||
"GBP",
|
||||
"JPY",
|
||||
"USD"
|
||||
],
|
||||
"LocalBitcoins": [
|
||||
"AED",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"BDT",
|
||||
"BHD",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BWP",
|
||||
"BYN",
|
||||
"CAD",
|
||||
"CHF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CZK",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"EGP",
|
||||
"ETH",
|
||||
"EUR",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GHS",
|
||||
"HKD",
|
||||
"HRK",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"INR",
|
||||
"IRR",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KRW",
|
||||
"KZT",
|
||||
"LKR",
|
||||
"LTC",
|
||||
"MAD",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"NGN",
|
||||
"NOK",
|
||||
"NZD",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"RON",
|
||||
"RSD",
|
||||
"GBP",
|
||||
"QAR",
|
||||
"RUB",
|
||||
"SAR",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SZL",
|
||||
"SEK",
|
||||
"CHF",
|
||||
"THB",
|
||||
"TRY",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"VEF",
|
||||
"VND",
|
||||
"XAF",
|
||||
"XRP",
|
||||
"ZAR",
|
||||
"ZMW"
|
||||
],
|
||||
"MercadoBitcoin": [
|
||||
"BRL"
|
||||
],
|
||||
"NegocieCoins": [
|
||||
"BRL"
|
||||
],
|
||||
"TheRockTrading": [
|
||||
"EUR"
|
||||
],
|
||||
"WEX": [
|
||||
"EUR",
|
||||
"RUB",
|
||||
"USD"
|
||||
],
|
||||
"itBit": []
|
||||
}
|
||||
"VND"
|
||||
]
|
||||
}
|
||||
@ -280,6 +280,14 @@ class CoinDesk(ExchangeBase):
|
||||
return json['bpi']
|
||||
|
||||
|
||||
class CoinMarketcap(ExchangeBase):
|
||||
|
||||
async def get_rates(self, ccy):
|
||||
json = await self.get_json('pro-api.coinmarketcap.com','/v1/cryptocurrency/quotes/latest?symbol=FLO&CMC_PRO_API_KEY=194ee8a1-5a58-4f3e-ba07-a1d6bc633210&convert=%s' % ccy)
|
||||
result = {ccy: Decimal(json['data']['FLO']['quote'][ccy]['price'])}
|
||||
return result
|
||||
|
||||
|
||||
class Coinsecure(ExchangeBase):
|
||||
|
||||
async def get_rates(self, ccy):
|
||||
@ -520,7 +528,7 @@ class FxThread(ThreadJob):
|
||||
return self.config.get("currency", "EUR")
|
||||
|
||||
def config_exchange(self):
|
||||
return self.config.get('use_exchange', 'BitcoinAverage')
|
||||
return self.config.get('use_exchange', 'CoinMarketcap')
|
||||
|
||||
def show_history(self):
|
||||
return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
|
||||
@ -605,4 +613,4 @@ class FxThread(ThreadJob):
|
||||
def timestamp_rate(self, timestamp):
|
||||
from .util import timestamp_to_datetime
|
||||
date = timestamp_to_datetime(timestamp)
|
||||
return self.history_rate(date)
|
||||
return self.history_rate(date)
|
||||
|
Before Width: | Height: | Size: 811 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.3 KiB |
@ -151,6 +151,20 @@
|
||||
data: ''
|
||||
text: ' '.join(map(''.join, zip(*[iter(self.data)]*4))) if self.data else ''
|
||||
|
||||
<FLODataLabel>
|
||||
font_size: '6pt'
|
||||
name: ''
|
||||
data: ''
|
||||
text: self.data
|
||||
touched: False
|
||||
padding: '10dp', '10dp'
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: .3, .3, .3
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<InfoBubble>
|
||||
size_hint: None, None
|
||||
width: '270dp' if root.fs else min(self.width, dp(270))
|
||||
@ -193,6 +207,18 @@
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<FLODataBlueBottom@BoxLayout>
|
||||
item_height: dp(60)
|
||||
foreground_color: .843, .914, .972, 1
|
||||
padding: '12dp', 0
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0.192, .498, 0.745, 1
|
||||
BorderImage:
|
||||
source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
|
||||
<AddressFilter@GridLayout>
|
||||
item_height: dp(42)
|
||||
@ -247,6 +273,36 @@
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<FLODataButton@Button>:
|
||||
background_color: 1, .585, .878, 0
|
||||
halign: 'left'
|
||||
text_size: (self.width-10, None)
|
||||
size_hint: 1, 1
|
||||
default_text: ''
|
||||
text: self.default_text
|
||||
text_color: self.foreground_color
|
||||
disabled_color: 1, 1, 1, 1
|
||||
foreground_color: 1, 1, 1, 1
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<ClickableLabel@ButtonBehavior+Label>:
|
||||
padding: '10dp', '10dp'
|
||||
text_color: self.foreground_color
|
||||
disabled_color: 1, 1, 1, 1
|
||||
foreground_color: 1, 1, 1, 1
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
|
||||
<BlueButton@Button>:
|
||||
background_color: 1, .585, .878, 0
|
||||
halign: 'left'
|
||||
@ -266,6 +322,17 @@
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<ClickableInput@ButtonBehavior+TextInput>:
|
||||
readonly: True
|
||||
keyboard_mode: 'managed'
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 1, 0, 0, .3
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
|
||||
<AddressButton@Button>:
|
||||
background_color: 1, .585, .878, 0
|
||||
halign: 'center'
|
||||
@ -449,6 +516,7 @@ BoxLayout:
|
||||
ActionOvrButton:
|
||||
name: 'network'
|
||||
text: _('Network')
|
||||
text: _('Network')
|
||||
ActionOvrButton:
|
||||
name: 'settings'
|
||||
text: _('Settings')
|
||||
|
||||
@ -40,7 +40,7 @@ from kivy.lang import Builder
|
||||
from .uix.dialogs.installwizard import InstallWizard
|
||||
from .uix.dialogs import InfoBubble, crash_reporter
|
||||
from .uix.dialogs import OutputList, OutputItem
|
||||
from .uix.dialogs import TopLabel, RefLabel
|
||||
from .uix.dialogs import TopLabel, RefLabel, FLODataLabel
|
||||
|
||||
#from kivy.core.window import Window
|
||||
#Window.softinput_mode = 'below_target'
|
||||
@ -160,7 +160,7 @@ class ElectrumWindow(App):
|
||||
self.send_screen.set_URI(uri)
|
||||
|
||||
def on_new_intent(self, intent):
|
||||
if intent.getScheme() != 'bitcoin':
|
||||
if intent.getScheme() != 'flo':
|
||||
return
|
||||
uri = intent.getDataString()
|
||||
self.set_URI(uri)
|
||||
@ -953,6 +953,16 @@ class ElectrumWindow(App):
|
||||
d = LabelDialog(_('Enter description'), text, callback)
|
||||
d.open()
|
||||
|
||||
def flodata_dialog(self, screen):
|
||||
from .uix.dialogs.flodata_dialog import FLODataDialog
|
||||
text = screen.flodata
|
||||
|
||||
def callback(text):
|
||||
screen.flodata = text
|
||||
|
||||
d = FLODataDialog(_('Enter FLO data'), text, callback)
|
||||
d.open()
|
||||
|
||||
def amount_dialog(self, screen, show_max):
|
||||
from .uix.dialogs.amount_dialog import AmountDialog
|
||||
amount = screen.amount
|
||||
@ -1002,6 +1012,7 @@ class ElectrumWindow(App):
|
||||
def on_fee(self, event, *arg):
|
||||
self.fee_status = self.electrum_config.get_fee_status()
|
||||
|
||||
|
||||
def protected(self, msg, f, args):
|
||||
if self.wallet.has_password():
|
||||
on_success = lambda pw: f(*(args + (pw,)))
|
||||
|
||||
@ -165,20 +165,20 @@ class ScannerAndroid(NFCBase):
|
||||
def create_AAR(self):
|
||||
'''Create the record responsible for linking our application to the tag.
|
||||
'''
|
||||
return NdefRecord.createApplicationRecord(JString("org.electrum.kivy"))
|
||||
return NdefRecord.createApplicationRecord(JString("org.electrum_flo.kivy"))
|
||||
|
||||
def create_TNF_EXTERNAL(self, data):
|
||||
'''Create our actual payload record.
|
||||
'''
|
||||
if BUILDVERSION >= 14:
|
||||
domain = "org.electrum"
|
||||
domain = "org.electrum_flo"
|
||||
stype = "externalType"
|
||||
extRecord = NdefRecord.createExternal(domain, stype, data)
|
||||
else:
|
||||
# Creating the NdefRecord manually:
|
||||
extRecord = NdefRecord(
|
||||
NdefRecord.TNF_EXTERNAL_TYPE,
|
||||
"org.electrum:externalType",
|
||||
"org.electrum_flo:externalType",
|
||||
'',
|
||||
data)
|
||||
return extRecord
|
||||
@ -213,7 +213,7 @@ class ScannerAndroid(NFCBase):
|
||||
# Create record
|
||||
ndef_record = NdefRecord(
|
||||
NdefRecord.TNF_MIME_MEDIA,
|
||||
'org.electrum.kivy', '', data)
|
||||
'org.electrum_flo.kivy', '', data)
|
||||
|
||||
# Create message
|
||||
ndef_message = NdefMessage([ndef_record])
|
||||
|
||||
BIN
electrum/gui/kivy/theming/light-1.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
electrum/gui/kivy/theming/light/flodata.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 15 KiB |
BIN
electrum/gui/kivy/theming/light/page-pen.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
@ -1,13 +1,13 @@
|
||||
[app]
|
||||
|
||||
# (str) Title of your application
|
||||
title = Electrum
|
||||
title = FLO-Electrum
|
||||
|
||||
# (str) Package name
|
||||
package.name = Electrum
|
||||
|
||||
# (str) Package domain (needed for android/ios packaging)
|
||||
package.domain = org.electrum
|
||||
package.domain = org.electrum_flo
|
||||
|
||||
# (str) Source code where the main.py live
|
||||
source.dir = .
|
||||
|
||||
@ -218,3 +218,6 @@ class TopLabel(Factory.Label):
|
||||
|
||||
class RefLabel(TopLabel):
|
||||
pass
|
||||
|
||||
class FLODataLabel(TopLabel):
|
||||
pass
|
||||
|
||||
65
electrum/gui/kivy/uix/dialogs/flodata_dialog.py
Normal file
@ -0,0 +1,65 @@
|
||||
from kivy.app import App
|
||||
from kivy.factory import Factory
|
||||
from kivy.properties import ObjectProperty
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.properties import NumericProperty
|
||||
|
||||
Builder.load_string('''
|
||||
<FLODataDialog@Popup>
|
||||
id: popup
|
||||
title: ''
|
||||
size_hint: 0.8, 0.5
|
||||
pos_hint: {'top':0.9}
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
Widget:
|
||||
size_hint: 1, 0.1
|
||||
LimitedInput:
|
||||
id:input
|
||||
padding: '5dp'
|
||||
size_hint: 1, 1
|
||||
height: '27dp'
|
||||
max_characters: 1022
|
||||
pos_hint: {'center_y':.5}
|
||||
text:''
|
||||
multiline: True
|
||||
background_normal: 'atlas://electrum/gui/kivy/theming/light/tab_btn'
|
||||
background_active: 'atlas://electrum/gui/kivy/theming/light/tab_btn'
|
||||
hint_text_color: self.foreground_color
|
||||
foreground_color: 1, 1, 1, 1
|
||||
font_size: '16dp'
|
||||
focus: True
|
||||
Widget:
|
||||
size_hint: 1, 0.2
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint: 1, 0.5
|
||||
Button:
|
||||
text: 'Cancel'
|
||||
size_hint: 0.5, None
|
||||
height: '48dp'
|
||||
on_release: popup.dismiss()
|
||||
Button:
|
||||
text: 'OK'
|
||||
size_hint: 0.5, None
|
||||
height: '48dp'
|
||||
on_release:
|
||||
root.callback(input.text)
|
||||
popup.dismiss()
|
||||
''')
|
||||
|
||||
class LimitedInput(TextInput):
|
||||
max_characters = NumericProperty(0)
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
if len(self.text) > self.max_characters and self.max_characters > 0:
|
||||
substring = ""
|
||||
TextInput.insert_text(self, substring, from_undo)
|
||||
|
||||
class FLODataDialog(Factory.Popup):
|
||||
|
||||
def __init__(self, title, text, callback):
|
||||
Factory.Popup.__init__(self)
|
||||
self.ids.input.text = text
|
||||
self.callback = callback
|
||||
self.title = title
|
||||
@ -26,6 +26,7 @@ Builder.load_string('''
|
||||
status_str: ''
|
||||
description: ''
|
||||
outputs_str: ''
|
||||
flodata: ''
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
ScrollView:
|
||||
@ -66,6 +67,11 @@ Builder.load_string('''
|
||||
text: _('Outputs') + ':'
|
||||
OutputList:
|
||||
id: output_list
|
||||
TopLabel:
|
||||
text: _('FLO data') + ':'
|
||||
FLODataLabel:
|
||||
data: root.flodata
|
||||
name: _('FLO data')
|
||||
Widget:
|
||||
size_hint: 1, 0.1
|
||||
|
||||
@ -109,6 +115,7 @@ class TxDialog(Factory.Popup):
|
||||
def update(self):
|
||||
format_amount = self.app.format_amount_and_units
|
||||
tx_hash, self.status_str, self.description, self.can_broadcast, self.can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
|
||||
self.flodata = self.wallet.get_flodata(tx_hash)
|
||||
self.tx_hash = tx_hash or ''
|
||||
if timestamp:
|
||||
self.date_label = _('Date')
|
||||
|
||||
@ -137,6 +137,7 @@ class HistoryScreen(CScreen):
|
||||
status, status_str = self.app.wallet.get_tx_status(tx_hash, tx_mined_status)
|
||||
icon = "atlas://electrum/gui/kivy/theming/light/" + TX_ICONS[status]
|
||||
label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
|
||||
flodata = self.app.wallet.get_flodata(tx_hash)
|
||||
ri = {}
|
||||
ri['screen'] = self
|
||||
ri['tx_hash'] = tx_hash
|
||||
@ -144,6 +145,7 @@ class HistoryScreen(CScreen):
|
||||
ri['date'] = status_str
|
||||
ri['message'] = label
|
||||
ri['confirmations'] = tx_mined_status.conf
|
||||
ri['flodata'] = flodata
|
||||
if value is not None:
|
||||
ri['is_mine'] = value < 0
|
||||
if value < 0: value = - value
|
||||
@ -177,7 +179,7 @@ class SendScreen(CScreen):
|
||||
try:
|
||||
uri = electrum.util.parse_URI(text, self.app.on_pr)
|
||||
except:
|
||||
self.app.show_info(_("Not a Bitcoin URI"))
|
||||
self.app.show_info(_("Not a FLO URI"))
|
||||
return
|
||||
amount = uri.get('amount')
|
||||
self.screen.address = uri.get('address', '')
|
||||
@ -185,6 +187,7 @@ class SendScreen(CScreen):
|
||||
self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
|
||||
self.payment_request = None
|
||||
self.screen.is_pr = False
|
||||
self.screen.flodata = uri.get('flodata', '')
|
||||
|
||||
def update(self):
|
||||
if self.app.wallet and self.payment_request_queued:
|
||||
@ -197,6 +200,7 @@ class SendScreen(CScreen):
|
||||
self.screen.address = ''
|
||||
self.payment_request = None
|
||||
self.screen.is_pr = False
|
||||
self.screen.flodata = ''
|
||||
|
||||
def set_request(self, pr):
|
||||
self.screen.address = pr.get_requestor()
|
||||
@ -248,10 +252,10 @@ class SendScreen(CScreen):
|
||||
else:
|
||||
address = str(self.screen.address)
|
||||
if not address:
|
||||
self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a Bitcoin address or a payment request'))
|
||||
self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a FLO address or a payment request'))
|
||||
return
|
||||
if not bitcoin.is_address(address):
|
||||
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
|
||||
self.app.show_error(_('Invalid FLO Address') + ':\n' + address)
|
||||
return
|
||||
try:
|
||||
amount = self.app.get_amount(self.screen.amount)
|
||||
@ -261,19 +265,20 @@ class SendScreen(CScreen):
|
||||
outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
|
||||
message = self.screen.message
|
||||
amount = sum(map(lambda x:x[2], outputs))
|
||||
flodata = str(self.screen.flodata)
|
||||
if self.app.electrum_config.get('use_rbf'):
|
||||
from .dialogs.question import Question
|
||||
d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b))
|
||||
d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b, flodata))
|
||||
d.open()
|
||||
else:
|
||||
self._do_send(amount, message, outputs, False)
|
||||
self._do_send(amount, message, outputs, False, flodata)
|
||||
|
||||
def _do_send(self, amount, message, outputs, rbf):
|
||||
def _do_send(self, amount, message, outputs, rbf, flodata):
|
||||
# make unsigned transaction
|
||||
config = self.app.electrum_config
|
||||
coins = self.app.wallet.get_spendable_coins(None, config)
|
||||
try:
|
||||
tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)
|
||||
tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None, flodata=flodata)
|
||||
except NotEnoughFunds:
|
||||
self.app.show_error(_("Not enough funds"))
|
||||
return
|
||||
@ -379,7 +384,7 @@ class ReceiveScreen(CScreen):
|
||||
|
||||
def do_share(self):
|
||||
uri = self.get_URI()
|
||||
self.app.do_share(uri, _("Share Bitcoin Request"))
|
||||
self.app.do_share(uri, _("Share FLO Request"))
|
||||
|
||||
def do_copy(self):
|
||||
uri = self.get_URI()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#:import VERSION electrum.version.ELECTRUM_VERSION
|
||||
|
||||
Popup:
|
||||
title: _("About Electrum")
|
||||
title: _("About FLO-Electrum")
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
spacing: '10dp'
|
||||
@ -26,19 +26,25 @@ Popup:
|
||||
size_hint_x: 0.4
|
||||
TopLabel:
|
||||
markup: True
|
||||
text: '[color=6666ff][ref=x]https://electrum.org[/ref][/color]'
|
||||
text: '[color=6666ff][ref=x]Ranchi Mall Github[/ref][/color]'
|
||||
on_ref_press:
|
||||
import webbrowser
|
||||
webbrowser.open("https://electrum.org")
|
||||
webbrowser.open("https://github.com/ranchimall/flo-electrum")
|
||||
size_hint_x: 0.6
|
||||
TopLabel:
|
||||
text: _('Developers')
|
||||
text: _('FLO-Electrum Developers')
|
||||
size_hint_x: 0.4
|
||||
TopLabel:
|
||||
text: '\n'.join(['Vivek Teega', 'Bitspill', 'Rohit Tripathy', 'Akhil Bharti'])
|
||||
size_hint_x: 0.6
|
||||
TopLabel:
|
||||
text: _('BTC-Electrum Developers')
|
||||
size_hint_x: 0.4
|
||||
TopLabel:
|
||||
text: '\n'.join(['Thomas Voegtlin', 'Neil Booth', 'Akshay Arora'])
|
||||
size_hint_x: 0.6
|
||||
TopLabel:
|
||||
text: _('Distributed by Electrum Technologies GmbH')
|
||||
text: _('Developed by Ranchi Mall FLO Blockchain Contract')
|
||||
padding: '0dp', '20dp'
|
||||
Widget:
|
||||
size_hint: None, 0.5
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
confirmations: 0
|
||||
date: ''
|
||||
quote_text: ''
|
||||
flodata: ''
|
||||
Image:
|
||||
id: icon
|
||||
source: root.icon
|
||||
@ -46,12 +47,17 @@
|
||||
font_size: '14sp'
|
||||
shorten: True
|
||||
text: root.date + ' ' + root.message
|
||||
CardLabel:
|
||||
color: .799, .799, .799, 1
|
||||
font_size: '14sp'
|
||||
shorten: True
|
||||
text: root.flodata if root.flodata else ''
|
||||
Widget
|
||||
|
||||
<HistoryRecycleView>:
|
||||
viewclass: 'HistoryItem'
|
||||
RecycleBoxLayout:
|
||||
default_size: None, dp(56)
|
||||
default_size: None, dp(70)
|
||||
default_size_hint: 1, None
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
@ -63,15 +69,24 @@ HistoryScreen:
|
||||
content: history_container
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
Button:
|
||||
background_color: 0, 0, 0, 0
|
||||
text: app.fiat_balance if app.is_fiat else app.balance
|
||||
markup: True
|
||||
color: .9, .9, .9, 1
|
||||
font_size: '30dp'
|
||||
bold: True
|
||||
RelativeLayout:
|
||||
size_hint: 1, 0.25
|
||||
on_release: app.is_fiat = not app.is_fiat if app.fx.is_enabled() else False
|
||||
Button:
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
||||
background_color: 0, 0, 0, 0
|
||||
text: app.fiat_balance if app.is_fiat else app.balance
|
||||
markup: True
|
||||
color: .9, .9, .9, 1
|
||||
font_size: '30dp'
|
||||
bold: True
|
||||
on_release: app.is_fiat = not app.is_fiat if app.fx.is_enabled() else False
|
||||
Label:
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.17}
|
||||
background_color: 0, 0, 0, 0
|
||||
markup: True
|
||||
color: .699, .699, .699, 1
|
||||
font_size: '14dp'
|
||||
text: "blocks : %d" % app.num_blocks
|
||||
HistoryRecycleView:
|
||||
id: history_container
|
||||
scroll_type: ['bars', 'content']
|
||||
|
||||
@ -11,6 +11,7 @@ SendScreen:
|
||||
address: ''
|
||||
amount: ''
|
||||
message: ''
|
||||
flodata: ''
|
||||
is_pr: False
|
||||
BoxLayout
|
||||
padding: '12dp', '12dp', '12dp', '12dp'
|
||||
@ -90,6 +91,28 @@ SendScreen:
|
||||
default_text: _('Fee')
|
||||
text: app.fee_status
|
||||
on_release: Clock.schedule_once(lambda dt: app.fee_dialog(s, True))
|
||||
FLODataBlueBottom:
|
||||
item_height: dp(60)
|
||||
spacing: '5dp'
|
||||
size_hint: 1, None
|
||||
height: self.minimum_height
|
||||
BoxLayout:
|
||||
size_hint: 1, None
|
||||
height: dp(84)
|
||||
spacing: '5dp'
|
||||
Image:
|
||||
source: 'atlas://electrum/gui/kivy/theming/light/page-pen'
|
||||
opacity: 0.7
|
||||
size_hint: None, None
|
||||
size: '22dp', '22dp'
|
||||
pos_hint: {'center_y': .5}
|
||||
ScrollView:
|
||||
ClickableLabel:
|
||||
text: s.flodata if s.flodata else (_('No FLO data') if root.is_pr else _('FLO data'))
|
||||
text_size: (self.width, None) if s.flodata else (self.width,self.height/2)
|
||||
size_hint_y: None if s.flodata else 1
|
||||
height: max(dp(82), self.texture_size[1])
|
||||
on_release: Clock.schedule_once(lambda dt: app.flodata_dialog(s))
|
||||
BoxLayout:
|
||||
size_hint: 1, None
|
||||
height: '48dp'
|
||||
@ -125,3 +148,4 @@ SendScreen:
|
||||
size_hint: 1, 1
|
||||
|
||||
|
||||
|
||||
|
||||
@ -57,6 +57,8 @@ except:
|
||||
print_error("qt/history_list: could not import electrum.plot. This feature needs matplotlib to be installed.")
|
||||
plot_history = None
|
||||
|
||||
import pdb
|
||||
|
||||
# note: this list needs to be kept in sync with another in kivy
|
||||
TX_ICONS = [
|
||||
"unconfirmed.png",
|
||||
@ -81,6 +83,7 @@ class HistoryColumns(IntEnum):
|
||||
FIAT_ACQ_PRICE = 6
|
||||
FIAT_CAP_GAINS = 7
|
||||
TXID = 8
|
||||
FLO_DATA = 9
|
||||
|
||||
class HistorySortModel(QSortFilterProxyModel):
|
||||
def lessThan(self, source_left: QModelIndex, source_right: QModelIndex):
|
||||
@ -132,6 +135,7 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
conf = tx_item['confirmations']
|
||||
txpos = tx_item['txpos_in_block'] or 0
|
||||
height = tx_item['height']
|
||||
flodata = self.parent.wallet.get_flodata(tx_hash)
|
||||
try:
|
||||
status, status_str = self.tx_status_cache[tx_hash]
|
||||
except KeyError:
|
||||
@ -155,6 +159,7 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
HistoryColumns.FIAT_CAP_GAINS:
|
||||
tx_item['capital_gain'].value if 'capital_gain' in tx_item else None,
|
||||
HistoryColumns.TXID: tx_hash,
|
||||
HistoryColumns.FLO_DATA: flodata,
|
||||
}
|
||||
return QVariant(d[col])
|
||||
if role not in (Qt.DisplayRole, Qt.EditRole):
|
||||
@ -204,6 +209,8 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
return QVariant(self.parent.fx.format_fiat(cg))
|
||||
elif col == HistoryColumns.TXID:
|
||||
return QVariant(tx_hash)
|
||||
elif col == HistoryColumns.FLO_DATA:
|
||||
return QVariant(flodata)
|
||||
return QVariant()
|
||||
|
||||
def parent(self, index: QModelIndex):
|
||||
@ -336,6 +343,7 @@ class HistoryModel(QAbstractItemModel, PrintError):
|
||||
HistoryColumns.FIAT_ACQ_PRICE: fiat_acq_title,
|
||||
HistoryColumns.FIAT_CAP_GAINS: fiat_cg_title,
|
||||
HistoryColumns.TXID: 'TXID',
|
||||
HistoryColumns.FLO_DATA: _('FLO Data'),
|
||||
}[section]
|
||||
|
||||
def flags(self, idx):
|
||||
@ -482,13 +490,13 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||
grid = QGridLayout()
|
||||
grid.addWidget(QLabel(_("Start")), 0, 0)
|
||||
grid.addWidget(QLabel(self.format_date(start_date)), 0, 1)
|
||||
grid.addWidget(QLabel(str(h.get('start_fiat_value')) + '/BTC'), 0, 2)
|
||||
grid.addWidget(QLabel(str(h.get('start_fiat_value')) + '/FLO'), 0, 2)
|
||||
grid.addWidget(QLabel(_("Initial balance")), 1, 0)
|
||||
grid.addWidget(QLabel(format_amount(h['start_balance'])), 1, 1)
|
||||
grid.addWidget(QLabel(str(h.get('start_fiat_balance'))), 1, 2)
|
||||
grid.addWidget(QLabel(_("End")), 2, 0)
|
||||
grid.addWidget(QLabel(self.format_date(end_date)), 2, 1)
|
||||
grid.addWidget(QLabel(str(h.get('end_fiat_value')) + '/BTC'), 2, 2)
|
||||
grid.addWidget(QLabel(str(h.get('end_fiat_value')) + '/FLO'), 2, 2)
|
||||
grid.addWidget(QLabel(_("Final balance")), 4, 0)
|
||||
grid.addWidget(QLabel(format_amount(h['end_balance'])), 4, 1)
|
||||
grid.addWidget(QLabel(str(h.get('end_fiat_balance'))), 4, 2)
|
||||
|
||||
72445
electrum/gui/qt/icons_rc.py
Normal file
@ -466,8 +466,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
if self.wallet.is_watching_only():
|
||||
msg = ' '.join([
|
||||
_("This wallet is watching-only."),
|
||||
_("This means you will not be able to spend Bitcoins with it."),
|
||||
_("Make sure you own the seed phrase or the private keys, before you request Bitcoins to be sent to this wallet.")
|
||||
_("This means you will not be able to spend FLO with it."),
|
||||
_("Make sure you own the seed phrase or the private keys, before you request FLO to be sent to this wallet.")
|
||||
])
|
||||
self.show_warning(msg, title=_('Information'))
|
||||
|
||||
@ -630,11 +630,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
def show_about(self):
|
||||
QMessageBox.about(self, "Electrum",
|
||||
(_("Version")+" %s" % ELECTRUM_VERSION + "\n\n" +
|
||||
_("Electrum's focus is speed, with low resource usage and simplifying Bitcoin.") + " " +
|
||||
_("Electrum's focus is speed, with low resource usage and simplifying FLO.") + " " +
|
||||
_("You do not need to perform regular backups, because your wallet can be "
|
||||
"recovered from a secret phrase that you can memorize or write on paper.") + " " +
|
||||
_("Startup times are instant because it operates in conjunction with high-performance "
|
||||
"servers that handle the most complicated parts of the Bitcoin system.") + "\n\n" +
|
||||
"servers that handle the most complicated parts of the FLO system.") + "\n\n" +
|
||||
_("Uses icons from the Icons8 icon pack (icons8.com).")))
|
||||
|
||||
def show_update_check(self, version=None):
|
||||
@ -872,7 +872,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.receive_address_e = ButtonsLineEdit()
|
||||
self.receive_address_e.addCopyButton(self.app)
|
||||
self.receive_address_e.setReadOnly(True)
|
||||
msg = _('Bitcoin address where the payment should be received. Note that each payment request uses a different Bitcoin address.')
|
||||
msg = _('FLO address where the payment should be received. Note that each payment request uses a different Bitcoin address.')
|
||||
self.receive_address_label = HelpLabel(_('Receiving address'), msg)
|
||||
self.receive_address_e.textChanged.connect(self.update_receive_qr)
|
||||
self.receive_address_e.setFocusPolicy(Qt.ClickFocus)
|
||||
@ -902,8 +902,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
msg = ' '.join([
|
||||
_('Expiration date of your request.'),
|
||||
_('This information is seen by the recipient if you send them a signed payment request.'),
|
||||
_('Expired requests have to be deleted manually from your list, in order to free the corresponding Bitcoin addresses.'),
|
||||
_('The bitcoin address never expires and will always be part of this electrum wallet.'),
|
||||
_('Expired requests have to be deleted manually from your list, in order to free the corresponding FLO addresses.'),
|
||||
_('The FLO address never expires and will always be part of this electrum wallet.'),
|
||||
])
|
||||
grid.addWidget(HelpLabel(_('Request expires'), msg), 3, 0)
|
||||
grid.addWidget(self.expires_combo, 3, 1)
|
||||
@ -1133,7 +1133,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||
self.payto_e = PayToEdit(self)
|
||||
msg = _('Recipient of the funds.') + '\n\n'\
|
||||
+ _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')
|
||||
+ _('You may enter a FLO address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')
|
||||
payto_label = HelpLabel(_('Pay to'), msg)
|
||||
grid.addWidget(payto_label, 1, 0)
|
||||
grid.addWidget(self.payto_e, 1, 1, 1, -1)
|
||||
@ -1150,10 +1150,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.message_e = MyLineEdit()
|
||||
grid.addWidget(self.message_e, 2, 1, 1, -1)
|
||||
|
||||
msg = _('This is where you write the FLO Data for the transaction')
|
||||
flodata_label = HelpLabel(_('FLO Data'), msg)
|
||||
grid.addWidget(flodata_label, 3, 0)
|
||||
self.message_tx_e = MyLineEdit()
|
||||
grid.addWidget(self.message_tx_e, 3, 1, 1, -1)
|
||||
|
||||
self.from_label = QLabel(_('From'))
|
||||
grid.addWidget(self.from_label, 3, 0)
|
||||
grid.addWidget(self.from_label, 4, 0)
|
||||
self.from_list = FromList(self, self.from_list_menu)
|
||||
grid.addWidget(self.from_list, 3, 1, 1, -1)
|
||||
grid.addWidget(self.from_list, 4, 1, 1, -1)
|
||||
self.set_pay_from([])
|
||||
|
||||
msg = _('Amount to be sent.') + '\n\n' \
|
||||
@ -1161,24 +1167,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
+ _('Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') + '\n\n' \
|
||||
+ _('Keyboard shortcut: type "!" to send all your coins.')
|
||||
amount_label = HelpLabel(_('Amount'), msg)
|
||||
grid.addWidget(amount_label, 4, 0)
|
||||
grid.addWidget(self.amount_e, 4, 1)
|
||||
grid.addWidget(amount_label, 5, 0)
|
||||
grid.addWidget(self.amount_e, 5, 1)
|
||||
|
||||
self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')
|
||||
if not self.fx or not self.fx.is_enabled():
|
||||
self.fiat_send_e.setVisible(False)
|
||||
grid.addWidget(self.fiat_send_e, 4, 2)
|
||||
grid.addWidget(self.fiat_send_e, 5, 2)
|
||||
self.amount_e.frozen.connect(
|
||||
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
|
||||
|
||||
self.max_button = EnterButton(_("Max"), self.spend_max)
|
||||
self.max_button.setFixedWidth(140)
|
||||
grid.addWidget(self.max_button, 4, 3)
|
||||
grid.addWidget(self.max_button, 5, 3)
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addStretch(1)
|
||||
grid.addLayout(hbox, 4, 4)
|
||||
grid.addLayout(hbox, 5, 4)
|
||||
|
||||
msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
|
||||
msg = _('FLO transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
|
||||
+ _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
|
||||
+ _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
|
||||
self.fee_e_label = HelpLabel(_('Fee'), msg)
|
||||
@ -1258,7 +1264,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
vbox_feelabel = QVBoxLayout()
|
||||
vbox_feelabel.addWidget(self.fee_e_label)
|
||||
vbox_feelabel.addStretch(1)
|
||||
grid.addLayout(vbox_feelabel, 5, 0)
|
||||
grid.addLayout(vbox_feelabel, 6, 0)
|
||||
|
||||
self.fee_adv_controls = QWidget()
|
||||
hbox = QHBoxLayout(self.fee_adv_controls)
|
||||
@ -1273,7 +1279,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
vbox_feecontrol.addWidget(self.fee_adv_controls)
|
||||
vbox_feecontrol.addWidget(self.fee_slider)
|
||||
|
||||
grid.addLayout(vbox_feecontrol, 5, 1, 1, -1)
|
||||
grid.addLayout(vbox_feecontrol, 6, 1, 1, -1)
|
||||
|
||||
if not self.config.get('show_fee', False):
|
||||
self.fee_adv_controls.setVisible(False)
|
||||
@ -1287,7 +1293,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
buttons.addWidget(self.clear_button)
|
||||
buttons.addWidget(self.preview_button)
|
||||
buttons.addWidget(self.send_button)
|
||||
grid.addLayout(buttons, 6, 1, 1, 3)
|
||||
grid.addLayout(buttons, 7, 1, 1, 3)
|
||||
|
||||
self.amount_e.shortcut.connect(self.spend_max)
|
||||
self.payto_e.textChanged.connect(self.update_fee)
|
||||
@ -1545,6 +1551,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.show_error(_('Payment request has expired'))
|
||||
return
|
||||
label = self.message_e.text()
|
||||
flodata = self.message_tx_e.text()
|
||||
|
||||
if self.payment_request:
|
||||
outputs = self.payment_request.get_outputs()
|
||||
@ -1569,7 +1576,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
|
||||
for o in outputs:
|
||||
if o.address is None:
|
||||
self.show_error(_('Bitcoin Address is None'))
|
||||
self.show_error(_('FLO Address is None'))
|
||||
return
|
||||
if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
|
||||
self.show_error(_('Invalid Bitcoin Address'))
|
||||
@ -1580,7 +1587,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
|
||||
fee_estimator = self.get_send_fee_estimator()
|
||||
coins = self.get_coins()
|
||||
return outputs, fee_estimator, label, coins
|
||||
return outputs, fee_estimator, label, coins, flodata
|
||||
|
||||
def do_preview(self):
|
||||
self.do_send(preview = True)
|
||||
@ -1591,12 +1598,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
r = self.read_send_tab()
|
||||
if not r:
|
||||
return
|
||||
outputs, fee_estimator, tx_desc, coins = r
|
||||
outputs, fee_estimator, tx_desc, coins, flodata = r
|
||||
try:
|
||||
is_sweep = bool(self.tx_external_keypairs)
|
||||
tx = self.wallet.make_unsigned_transaction(
|
||||
coins, outputs, self.config, fixed_fee=fee_estimator,
|
||||
is_sweep=is_sweep)
|
||||
is_sweep=is_sweep, flodata=flodata)
|
||||
except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
|
||||
self.show_message(str(e))
|
||||
return
|
||||
@ -1833,7 +1840,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||
self.not_enough_funds = False
|
||||
self.payment_request = None
|
||||
self.payto_e.is_pr = False
|
||||
for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,
|
||||
for e in [self.payto_e, self.message_e, self.message_tx_e, self.amount_e, self.fiat_send_e,
|
||||
self.fee_e, self.feerate_e]:
|
||||
e.setText('')
|
||||
e.setFrozen(False)
|
||||
|
||||
@ -117,6 +117,8 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
vbox.addWidget(self.size_label)
|
||||
self.fee_label = QLabel()
|
||||
vbox.addWidget(self.fee_label)
|
||||
self.flodata_label = QLabel()
|
||||
vbox.addWidget(self.flodata_label)
|
||||
|
||||
self.add_io(vbox)
|
||||
|
||||
@ -274,6 +276,7 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
self.amount_label.setText(amount_str)
|
||||
self.fee_label.setText(fee_str)
|
||||
self.size_label.setText(size_str)
|
||||
self.flodata_label.setText("FLO data: \"" + self.tx.flodata + str("\""))
|
||||
run_hook('transaction_dialog_update', self)
|
||||
|
||||
def add_io(self, vbox):
|
||||
|
||||
@ -337,7 +337,7 @@ class ElectrumGui:
|
||||
|
||||
def do_send(self):
|
||||
if not is_address(self.str_recipient):
|
||||
self.show_message(_('Invalid Bitcoin address'))
|
||||
self.show_message(_('Invalid FLO address'))
|
||||
return
|
||||
try:
|
||||
amount = int(Decimal(self.str_amount) * COIN)
|
||||
|
||||
@ -780,7 +780,7 @@ class Network(PrintError):
|
||||
try:
|
||||
return await func(self, *args, **kwargs)
|
||||
except aiorpcx.jsonrpc.CodeMessageError as e:
|
||||
raise UntrustedServerReturnedError(original_exception=e)
|
||||
raise UntrustedServerReturnedError(original_exception=e) from e
|
||||
return wrapper
|
||||
|
||||
@best_effort_reliable
|
||||
@ -1188,24 +1188,21 @@ class Network(PrintError):
|
||||
return parse_servers(await session.send_request('server.peers.subscribe'))
|
||||
|
||||
async def send_multiple_requests(self, servers: List[str], method: str, params: Sequence):
|
||||
num_connecting = len(self.connecting)
|
||||
for server in servers:
|
||||
self._start_interface(server)
|
||||
# sleep a bit
|
||||
for _ in range(10):
|
||||
if len(self.connecting) < num_connecting:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
responses = dict()
|
||||
async def get_response(iface: Interface):
|
||||
async def get_response(server):
|
||||
interface = Interface(self, server, self.proxy)
|
||||
timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent)
|
||||
try:
|
||||
res = await iface.session.send_request(method, params, timeout=10)
|
||||
await asyncio.wait_for(interface.ready, timeout)
|
||||
except BaseException as e:
|
||||
await interface.close()
|
||||
return
|
||||
try:
|
||||
res = await interface.session.send_request(method, params, timeout=10)
|
||||
except Exception as e:
|
||||
res = e
|
||||
responses[iface.server] = res
|
||||
responses[interface.server] = res
|
||||
async with TaskGroup() as group:
|
||||
for server in servers:
|
||||
interface = self.interfaces.get(server)
|
||||
if interface:
|
||||
await group.spawn(get_response(interface))
|
||||
await group.spawn(get_response(server))
|
||||
return responses
|
||||
|
||||
@ -37,7 +37,7 @@ def plot_history(history):
|
||||
plt.subplots_adjust(bottom=0.2)
|
||||
plt.xticks( rotation=25 )
|
||||
ax = plt.gca()
|
||||
plt.ylabel('BTC')
|
||||
plt.ylabel('FLO')
|
||||
plt.xlabel('Month')
|
||||
xfmt = md.DateFormatter('%Y-%m-%d')
|
||||
ax.xaxis.set_major_formatter(xfmt)
|
||||
|
||||
@ -496,9 +496,9 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||
# or it is not pairable
|
||||
raise DeviceUnpairableError(
|
||||
_('Electrum cannot pair with your {}.\n\n'
|
||||
'Before you request bitcoins to be sent to addresses in this '
|
||||
'Before you request FLO to be sent to addresses in this '
|
||||
'wallet, ensure you can pair with your device, or that you have '
|
||||
'its seed (and passphrase, if any). Otherwise all bitcoins you '
|
||||
'its seed (and passphrase, if any). Otherwise all FLO you '
|
||||
'receive will be unspendable.').format(plugin.device))
|
||||
|
||||
def unpaired_device_infos(self, handler, plugin: 'HW_PluginBase', devices=None,
|
||||
|
||||
@ -154,7 +154,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
||||
return client
|
||||
|
||||
def get_coin_name(self):
|
||||
return "Testnet" if constants.net.TESTNET else "Bitcoin"
|
||||
return "Testnet" if constants.net.TESTNET else "Florincoin"
|
||||
|
||||
def initialize_device(self, device_id, wizard, handler):
|
||||
# Initialization method
|
||||
|
||||
@ -30,7 +30,7 @@ except ImportError:
|
||||
|
||||
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
|
||||
' https://www.ledgerwallet.com'
|
||||
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcoin" app) too old for Segwit support. Please update at') + \
|
||||
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Florincoin" app) too old for Segwit support. Please update at') + \
|
||||
' https://www.ledgerwallet.com'
|
||||
MULTI_OUTPUT_SUPPORT = '1.1.4'
|
||||
SEGWIT_SUPPORT = '1.1.10'
|
||||
|
||||
@ -159,7 +159,7 @@ class SafeTPlugin(HW_PluginBase):
|
||||
return client
|
||||
|
||||
def get_coin_name(self):
|
||||
return "Testnet" if constants.net.TESTNET else "Bitcoin"
|
||||
return "Testnet" if constants.net.TESTNET else "Florincoin"
|
||||
|
||||
def initialize_device(self, device_id, wizard, handler):
|
||||
# Initialization method
|
||||
|
||||
@ -160,7 +160,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
return client
|
||||
|
||||
def get_coin_name(self):
|
||||
return "Testnet" if constants.net.TESTNET else "Bitcoin"
|
||||
return "Testnet" if constants.net.TESTNET else "Florincoin"
|
||||
|
||||
def initialize_device(self, device_id, wizard, handler):
|
||||
# Initialization method
|
||||
|
||||
40
electrum/scripts/quick_start.py
Normal file
@ -0,0 +1,40 @@
|
||||
import os
|
||||
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum import constants
|
||||
from electrum.daemon import Daemon
|
||||
from electrum.storage import WalletStorage
|
||||
from electrum.wallet import Wallet
|
||||
from electrum.commands import Commands
|
||||
|
||||
|
||||
config = SimpleConfig({"testnet": True}) # to use ~/.electrum/testnet as datadir
|
||||
constants.set_testnet() # to set testnet magic bytes
|
||||
daemon = Daemon(config, listen_jsonrpc=False)
|
||||
network = daemon.network
|
||||
assert network.asyncio_loop.is_running()
|
||||
|
||||
command_runner = Commands(config, wallet=None, network=network)
|
||||
|
||||
# get wallet on disk
|
||||
wallet_dir = os.path.dirname(config.get_wallet_path())
|
||||
wallet_path = os.path.join(wallet_dir, "test_wallet")
|
||||
if not os.path.exists(wallet_path):
|
||||
config.set_key('wallet_path', wallet_path)
|
||||
command_runner.create(segwit=True)
|
||||
|
||||
# open wallet
|
||||
storage = WalletStorage(wallet_path)
|
||||
wallet = Wallet(storage)
|
||||
wallet.start_network(network)
|
||||
|
||||
# you can use ~CLI commands by accessing command_runner
|
||||
command_runner.wallet = wallet
|
||||
print("balance", command_runner.getbalance())
|
||||
print("addr", command_runner.getunusedaddress())
|
||||
print("gettx", command_runner.gettransaction("bd3a700b2822e10a034d110c11a596ee7481732533eb6aca7f9ca02911c70a4f"))
|
||||
|
||||
# but you might as well interact with the underlying methods directly
|
||||
print("balance", wallet.get_balance())
|
||||
print("addr", wallet.get_unused_address())
|
||||
print("gettx", network.run_from_another_thread(network.get_transaction("bd3a700b2822e10a034d110c11a596ee7481732533eb6aca7f9ca02911c70a4f")))
|
||||
@ -1,403 +1,17 @@
|
||||
{
|
||||
"3smoooajg7qqac2y.onion": {
|
||||
"ranchimall.duckdns.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"81-7-10-251.blue.kundencontroller.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"E-X.not.fyi": {
|
||||
"ranchimall1.duckdns.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"MEADS.hopto.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"VPS.hsmiths.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"b.ooze.cc": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"bauerjda5hnedjam.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"bauerjhejlv6di7s.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"bitcoin.corgi.party": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"bitcoin3nqy3db7c.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"bitcoins.sk": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"btc.cihar.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"btc.smsys.me": {
|
||||
"pruning": "-",
|
||||
"s": "995",
|
||||
"version": "1.4"
|
||||
},
|
||||
"btc.xskyx.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"cashyes.zapto.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"currentlane.lovebitco.in": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"daedalus.bauerj.eu": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"dedi.jochen-hoenicke.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"dragon085.startdedicated.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"e-1.claudioboxx.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"e.keff.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"elec.luggs.co": {
|
||||
"pruning": "-",
|
||||
"s": "443",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum-server.ninja": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum-unlimited.criptolayer.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.eff.ro": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.festivaldelhumor.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.hsmiths.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.leblancnet.us": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.mindspot.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.qtornado.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.taborsky.cz": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum.villocq.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum2.eff.ro": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum2.villocq.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrum3.hachre.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumx.bot.nu": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumx.ddns.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumx.ftp.sh": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumx.ml": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumx.nmdps.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumx.soon.it": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electrumxhqdsmlu.onion": {
|
||||
"pruning": "-",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"elx01.knas.systems": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"enode.duckdns.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"erbium1.sytes.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"fedaykin.goip.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"fn.48.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50003",
|
||||
"version": "1.4"
|
||||
},
|
||||
"helicarrier.bauerj.eu": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"hsmiths4fyqlw5xw.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"hsmiths5mjk6uijs.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"icarus.tetradrachm.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"kirsche.emzy.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"luggscoqbymhvnkp.onion": {
|
||||
"pruning": "-",
|
||||
"t": "80",
|
||||
"version": "1.4"
|
||||
},
|
||||
"ndnd.selfhost.eu": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"ndndword5lpb7eex.onion": {
|
||||
"pruning": "-",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"orannis.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"ozahtqwp25chjdjd.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"qtornadoklbgdyww.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"rbx.curalle.ovh": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"s7clinmo4cazmhul.onion": {
|
||||
"pruning": "-",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"tardis.bauerj.eu": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"technetium.network": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"tomscryptos.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"ulrichard.ch": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"us.electrum.be": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"vmd27610.contaboserver.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"vmd30612.contaboserver.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"wsw6tua3xl24gsmi264zaep6seppjyrkyucpsmuxnjzyt3f3j6swshad.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"xray587.startdedicated.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4"
|
||||
},
|
||||
"yuio.top": {
|
||||
"snowflake.oip.fun": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
|
||||
@ -202,7 +202,7 @@ class SimpleConfig(PrintError):
|
||||
base_unit = self.user_config.get('base_unit')
|
||||
if isinstance(base_unit, str):
|
||||
self._set_key_in_user_config('base_unit', None)
|
||||
map_ = {'btc':8, 'mbtc':5, 'ubtc':2, 'bits':2, 'sat':0}
|
||||
map_ = {'flo':8, 'mflo':5, 'uflo':2, 'bits':2, 'sat':0}
|
||||
decimal_point = map_.get(base_unit.lower())
|
||||
self._set_key_in_user_config('decimal_point', decimal_point)
|
||||
|
||||
|
||||
@ -32,12 +32,16 @@ from aiorpcx import TaskGroup, run_in_thread
|
||||
from .transaction import Transaction
|
||||
from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer
|
||||
from .bitcoin import address_to_scripthash, is_address
|
||||
from .network import UntrustedServerReturnedError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .network import Network
|
||||
from .address_synchronizer import AddressSynchronizer
|
||||
|
||||
|
||||
class SynchronizerFailure(Exception): pass
|
||||
|
||||
|
||||
def history_status(h):
|
||||
if not h:
|
||||
return None
|
||||
@ -78,7 +82,7 @@ class SynchronizerBase(NetworkJobOnDefaultServer):
|
||||
asyncio.run_coroutine_threadsafe(self._add_address(addr), self.asyncio_loop)
|
||||
|
||||
async def _add_address(self, addr: str):
|
||||
if not is_address(addr): raise ValueError(f"invalid bitcoin address {addr}")
|
||||
if not is_address(addr): raise ValueError(f"invalid FLO address {addr}")
|
||||
if addr in self.requested_addrs: return
|
||||
self.requested_addrs.add(addr)
|
||||
await self.add_queue.put(addr)
|
||||
@ -165,7 +169,7 @@ class Synchronizer(SynchronizerBase):
|
||||
# Remove request; this allows up_to_date to be True
|
||||
self.requested_histories.pop(addr)
|
||||
|
||||
async def _request_missing_txs(self, hist):
|
||||
async def _request_missing_txs(self, hist, *, allow_server_not_finding_tx=False):
|
||||
# "hist" is a list of [tx_hash, tx_height] lists
|
||||
transaction_hashes = []
|
||||
for tx_hash, tx_height in hist:
|
||||
@ -179,24 +183,33 @@ class Synchronizer(SynchronizerBase):
|
||||
if not transaction_hashes: return
|
||||
async with TaskGroup() as group:
|
||||
for tx_hash in transaction_hashes:
|
||||
await group.spawn(self._get_transaction, tx_hash)
|
||||
await group.spawn(self._get_transaction(tx_hash, allow_server_not_finding_tx=allow_server_not_finding_tx))
|
||||
|
||||
async def _get_transaction(self, tx_hash):
|
||||
result = await self.network.get_transaction(tx_hash)
|
||||
async def _get_transaction(self, tx_hash, *, allow_server_not_finding_tx=False):
|
||||
try:
|
||||
result = await self.network.get_transaction(tx_hash)
|
||||
except UntrustedServerReturnedError as e:
|
||||
# most likely, "No such mempool or blockchain transaction"
|
||||
if allow_server_not_finding_tx:
|
||||
self.requested_tx.pop(tx_hash)
|
||||
return
|
||||
else:
|
||||
raise
|
||||
tx = Transaction(result)
|
||||
try:
|
||||
tx.deserialize()
|
||||
except Exception:
|
||||
self.print_msg("cannot deserialize transaction, skipping", tx_hash)
|
||||
return
|
||||
tx.deserialize() # see if raises
|
||||
except Exception as e:
|
||||
# possible scenarios:
|
||||
# 1: server is sending garbage
|
||||
# 2: there is a bug in the deserialization code
|
||||
# 3: there was a segwit-like upgrade that changed the tx structure
|
||||
# that we don't know about
|
||||
raise SynchronizerFailure(f"cannot deserialize transaction {tx_hash}") from e
|
||||
if tx_hash != tx.txid():
|
||||
self.print_error("received tx does not match expected txid ({} != {})"
|
||||
.format(tx_hash, tx.txid()))
|
||||
return
|
||||
raise SynchronizerFailure(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})")
|
||||
tx_height = self.requested_tx.pop(tx_hash)
|
||||
self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
|
||||
self.print_error("received tx %s height: %d bytes: %d" %
|
||||
(tx_hash, tx_height, len(tx.raw)))
|
||||
self.print_error(f"received tx {tx_hash} height: {tx_height} bytes: {len(tx.raw)}")
|
||||
# callbacks
|
||||
self.wallet.network.trigger_callback('new_transaction', self.wallet, tx)
|
||||
|
||||
@ -207,7 +220,7 @@ class Synchronizer(SynchronizerBase):
|
||||
# Old electrum servers returned ['*'] when all history for the address
|
||||
# was pruned. This no longer happens but may remain in old wallets.
|
||||
if history == ['*']: continue
|
||||
await self._request_missing_txs(history)
|
||||
await self._request_missing_txs(history, allow_server_not_finding_tx=True)
|
||||
# add addresses to bootstrap
|
||||
for addr in self.wallet.get_addresses():
|
||||
await self._add_address(addr)
|
||||
|
||||
@ -41,6 +41,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_PUBKEY, TYPE_SCRIPT, hash_160,
|
||||
push_script, int_to_hex, push_script, b58_address_to_hash160)
|
||||
from .crypto import sha256d
|
||||
from .keystore import xpubkey_to_address, xpubkey_to_pubkey
|
||||
import codecs
|
||||
|
||||
|
||||
NO_SIGNATURE = 'ff'
|
||||
@ -68,12 +69,10 @@ class TxOutput(NamedTuple):
|
||||
address: str
|
||||
value: Union[int, str] # str when the output is set to max: '!'
|
||||
|
||||
|
||||
class TxOutputForUI(NamedTuple):
|
||||
address: str
|
||||
value: int
|
||||
|
||||
|
||||
class TxOutputHwInfo(NamedTuple):
|
||||
address_index: Tuple
|
||||
sorted_xpubs: Iterable[str]
|
||||
@ -125,7 +124,7 @@ class BCDataStream(object):
|
||||
self.read_cursor += length
|
||||
return result
|
||||
except IndexError:
|
||||
raise SerializationError("attempt to read past end of buffer")
|
||||
raise SerializationError("attempt to read past end of buffer") from None
|
||||
|
||||
def can_read_more(self) -> bool:
|
||||
if not self.input:
|
||||
@ -159,8 +158,8 @@ class BCDataStream(object):
|
||||
elif size == 255:
|
||||
size = self._read_num('<Q')
|
||||
return size
|
||||
except IndexError:
|
||||
raise SerializationError("attempt to read past end of buffer")
|
||||
except IndexError as e:
|
||||
raise SerializationError("attempt to read past end of buffer") from e
|
||||
|
||||
def write_compact_size(self, size):
|
||||
if size < 0:
|
||||
@ -182,7 +181,7 @@ class BCDataStream(object):
|
||||
(i,) = struct.unpack_from(format, self.input, self.read_cursor)
|
||||
self.read_cursor += struct.calcsize(format)
|
||||
except Exception as e:
|
||||
raise SerializationError(e)
|
||||
raise SerializationError(e) from e
|
||||
return i
|
||||
|
||||
def _write_num(self, format, num):
|
||||
@ -642,6 +641,10 @@ def deserialize(raw: str, force_full_parse=False) -> dict:
|
||||
txin = d['inputs'][i]
|
||||
parse_witness(vds, txin, full_parse=full_parse)
|
||||
d['lockTime'] = vds.read_uint32()
|
||||
if d['version'] >= 2:
|
||||
d['flodata'] = vds.read_string()
|
||||
else:
|
||||
d['flodata'] = ""
|
||||
if vds.can_read_more():
|
||||
raise SerializationError('extra junk at the end')
|
||||
return d
|
||||
@ -679,7 +682,8 @@ class Transaction:
|
||||
self._inputs = None
|
||||
self._outputs = None # type: List[TxOutput]
|
||||
self.locktime = 0
|
||||
self.version = 2
|
||||
self.flodata = ""
|
||||
self.version = 1
|
||||
# by default we assume this is a partial txn;
|
||||
# this value will get properly set when deserializing
|
||||
self.is_partial_originally = True
|
||||
@ -782,16 +786,18 @@ class Transaction:
|
||||
self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
|
||||
self.locktime = d['lockTime']
|
||||
self.version = d['version']
|
||||
self.flodata = d['flodata']
|
||||
self.is_partial_originally = d['partial']
|
||||
self._segwit_ser = d['segwit_ser']
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_io(klass, inputs, outputs, locktime=0, version=None):
|
||||
def from_io(klass, inputs, outputs, locktime=0, version=None, flodata=""):
|
||||
self = klass(None)
|
||||
self._inputs = inputs
|
||||
self._outputs = outputs
|
||||
self.locktime = locktime
|
||||
self.flodata = flodata
|
||||
if version is not None:
|
||||
self.version = version
|
||||
self.BIP69_sort()
|
||||
@ -1044,6 +1050,7 @@ class Transaction:
|
||||
nVersion = int_to_hex(self.version, 4)
|
||||
nHashType = int_to_hex(1, 4)
|
||||
nLocktime = int_to_hex(self.locktime, 4)
|
||||
nFloData = var_int(len(self.flodata)) + str(codecs.encode(bytes(self.flodata, 'utf-8'), 'hex_codec'),'utf-8')
|
||||
inputs = self.inputs()
|
||||
outputs = self.outputs()
|
||||
txin = inputs[i]
|
||||
@ -1057,11 +1064,17 @@ class Transaction:
|
||||
scriptCode = var_int(len(preimage_script) // 2) + preimage_script
|
||||
amount = int_to_hex(txin['value'], 8)
|
||||
nSequence = int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)
|
||||
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
|
||||
if self.version >= 2:
|
||||
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nFloData + nHashType
|
||||
else:
|
||||
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
|
||||
else:
|
||||
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs))
|
||||
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
|
||||
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
||||
if self.version >= 2:
|
||||
preimage = nVersion + txins + txouts + nLocktime + nFloData + nHashType
|
||||
else:
|
||||
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
||||
return preimage
|
||||
|
||||
def is_segwit(self, guess_for_address=False):
|
||||
@ -1083,6 +1096,8 @@ class Transaction:
|
||||
self.deserialize()
|
||||
nVersion = int_to_hex(self.version, 4)
|
||||
nLocktime = int_to_hex(self.locktime, 4)
|
||||
nFloData = var_int(len(self.flodata)) + str(codecs.encode(bytes(self.flodata, 'utf-8'), 'hex_codec'),
|
||||
'utf-8')
|
||||
inputs = self.inputs()
|
||||
outputs = self.outputs()
|
||||
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs)
|
||||
@ -1095,9 +1110,15 @@ class Transaction:
|
||||
marker = '00'
|
||||
flag = '01'
|
||||
witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)
|
||||
return nVersion + marker + flag + txins + txouts + witness + nLocktime
|
||||
if self.version >= 2:
|
||||
return nVersion + marker + flag + txins + txouts + witness + nLocktime + nFloData
|
||||
else:
|
||||
return nVersion + marker + flag + txins + txouts + witness + nLocktime
|
||||
else:
|
||||
return nVersion + txins + txouts + nLocktime
|
||||
if self.version >= 2:
|
||||
return nVersion + txins + txouts + nLocktime + nFloData
|
||||
else:
|
||||
return nVersion + txins + txouts + nLocktime
|
||||
|
||||
def txid(self):
|
||||
self.deserialize()
|
||||
|
||||
@ -60,12 +60,11 @@ def inv_dict(d):
|
||||
|
||||
ca_path = certifi.where()
|
||||
|
||||
|
||||
base_units = {'BTC':8, 'mBTC':5, 'bits':2, 'sat':0}
|
||||
base_units = {'FLO':8, 'mFLO':5, 'bits':2, 'sat':0}
|
||||
base_units_inverse = inv_dict(base_units)
|
||||
base_units_list = ['BTC', 'mBTC', 'bits', 'sat'] # list(dict) does not guarantee order
|
||||
base_units_list = ['FLO', 'mFLO', 'bits', 'sat'] # list(dict) does not guarantee order
|
||||
|
||||
DECIMAL_POINT_DEFAULT = 5 # mBTC
|
||||
DECIMAL_POINT_DEFAULT = 5 # mFLO
|
||||
|
||||
|
||||
class UnknownBaseUnit(Exception): pass
|
||||
@ -148,7 +147,13 @@ class Satoshis(object):
|
||||
return 'Satoshis(%d)'%self.value
|
||||
|
||||
def __str__(self):
|
||||
return format_satoshis(self.value) + " BTC"
|
||||
return format_satoshis(self.value) + " FLO"
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other.value
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other.value
|
||||
@ -483,11 +488,11 @@ def user_dir():
|
||||
if 'ANDROID_DATA' in os.environ:
|
||||
return android_data_dir()
|
||||
elif os.name == 'posix':
|
||||
return os.path.join(os.environ["HOME"], ".electrum")
|
||||
return os.path.join(os.environ["HOME"], ".electrum-flo")
|
||||
elif "APPDATA" in os.environ:
|
||||
return os.path.join(os.environ["APPDATA"], "Electrum")
|
||||
return os.path.join(os.environ["APPDATA"], "Electrum-FLO")
|
||||
elif "LOCALAPPDATA" in os.environ:
|
||||
return os.path.join(os.environ["LOCALAPPDATA"], "Electrum")
|
||||
return os.path.join(os.environ["LOCALAPPDATA"], "Electrum-FLO")
|
||||
else:
|
||||
#raise Exception("No home directory found in environment variables.")
|
||||
return
|
||||
@ -646,56 +651,14 @@ def time_difference(distance_in_time, include_seconds):
|
||||
return "over %d years" % (round(distance_in_minutes / 525600))
|
||||
|
||||
mainnet_block_explorers = {
|
||||
'Bitupper Explorer': ('https://bitupper.com/en/explorer/bitcoin/',
|
||||
{'tx': 'transactions/', 'addr': 'addresses/'}),
|
||||
'Biteasy.com': ('https://www.biteasy.com/blockchain/',
|
||||
{'tx': 'transactions/', 'addr': 'addresses/'}),
|
||||
'Bitflyer.jp': ('https://chainflyer.bitflyer.jp/',
|
||||
{'tx': 'Transaction/', 'addr': 'Address/'}),
|
||||
'Blockchain.info': ('https://blockchain.info/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'blockchainbdgpzk.onion': ('https://blockchainbdgpzk.onion/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'Blockr.io': ('https://btc.blockr.io/',
|
||||
{'tx': 'tx/info/', 'addr': 'address/info/'}),
|
||||
'Blockstream.info': ('https://blockstream.info/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'Blocktrail.com': ('https://www.blocktrail.com/BTC/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'BTC.com': ('https://chain.btc.com/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'Chain.so': ('https://www.chain.so/',
|
||||
{'tx': 'tx/BTC/', 'addr': 'address/BTC/'}),
|
||||
'Insight.is': ('https://insight.bitpay.com/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'TradeBlock.com': ('https://tradeblock.com/blockchain/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'BlockCypher.com': ('https://live.blockcypher.com/btc/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'Blockchair.com': ('https://blockchair.com/bitcoin/',
|
||||
{'tx': 'transaction/', 'addr': 'address/'}),
|
||||
'blockonomics.co': ('https://www.blockonomics.co/',
|
||||
{'tx': 'api/tx?txid=', 'addr': '#/search?q='}),
|
||||
'OXT.me': ('https://oxt.me/',
|
||||
{'tx': 'transaction/', 'addr': 'address/'}),
|
||||
'smartbit.com.au': ('https://www.smartbit.com.au/',
|
||||
'florincoin.info': ('https://florincoin.info/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'system default': ('blockchain:/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
}
|
||||
|
||||
testnet_block_explorers = {
|
||||
'Blocktrail.com': ('https://www.blocktrail.com/tBTC/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'BlockCypher.com': ('https://live.blockcypher.com/btc-testnet/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'Blockchain.info': ('https://testnet.blockchain.info/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'Blockstream.info': ('https://blockstream.info/testnet/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'BTC.com': ('https://tchain.btc.com/',
|
||||
{'tx': '', 'addr': ''}),
|
||||
'smartbit.com.au': ('https://testnet.smartbit.com.au/',
|
||||
'Florincoin.info': ('https://testnet.florincoin.info/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
'system default': ('blockchain://000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943/',
|
||||
{'tx': 'tx/', 'addr': 'address/'}),
|
||||
@ -707,7 +670,7 @@ def block_explorer_info():
|
||||
|
||||
def block_explorer(config: 'SimpleConfig') -> str:
|
||||
from . import constants
|
||||
default_ = 'Blockstream.info'
|
||||
default_ = 'florincoin.info'
|
||||
be_key = config.get('block_explorer', default_)
|
||||
be = block_explorer_info().get(be_key)
|
||||
return be_key if be is not None else default_
|
||||
@ -736,12 +699,12 @@ def parse_URI(uri: str, on_pr: Callable=None) -> dict:
|
||||
|
||||
if ':' not in uri:
|
||||
if not bitcoin.is_address(uri):
|
||||
raise Exception("Not a bitcoin address")
|
||||
raise Exception("Not a FLO address")
|
||||
return {'address': uri}
|
||||
|
||||
u = urllib.parse.urlparse(uri)
|
||||
if u.scheme != 'bitcoin':
|
||||
raise Exception("Not a bitcoin URI")
|
||||
if u.scheme != 'flo':
|
||||
raise Exception("Not a FLO URI")
|
||||
address = u.path
|
||||
|
||||
# python for android fails to parse query
|
||||
@ -758,7 +721,7 @@ def parse_URI(uri: str, on_pr: Callable=None) -> dict:
|
||||
out = {k: v[0] for k, v in pq.items()}
|
||||
if address:
|
||||
if not bitcoin.is_address(address):
|
||||
raise Exception("Invalid bitcoin address:" + address)
|
||||
raise Exception("Invalid FLO address:" + address)
|
||||
out['address'] = address
|
||||
if 'amount' in out:
|
||||
am = out['amount']
|
||||
@ -807,7 +770,7 @@ def create_URI(addr, amount, message):
|
||||
query.append('amount=%s'%format_satoshis_plain(amount))
|
||||
if message:
|
||||
query.append('message=%s'%urllib.parse.quote(message))
|
||||
p = urllib.parse.ParseResult(scheme='bitcoin', netloc='', path=addr, params='', query='&'.join(query), fragment='')
|
||||
p = urllib.parse.ParseResult(scheme='flo', netloc='', path=addr, params='', query='&'.join(query), fragment='')
|
||||
return urllib.parse.urlunparse(p)
|
||||
|
||||
|
||||
@ -938,6 +901,7 @@ class TxMinedInfo(NamedTuple):
|
||||
timestamp: Optional[int] = None # timestamp of block that mined tx
|
||||
txpos: Optional[int] = None # position of tx in serialized block
|
||||
header_hash: Optional[str] = None # hash of block that mined tx
|
||||
flodata: Optional[str] = None # flodata of that mined tx
|
||||
|
||||
|
||||
def make_aiohttp_session(proxy: dict, headers=None, timeout=None):
|
||||
@ -946,7 +910,6 @@ def make_aiohttp_session(proxy: dict, headers=None, timeout=None):
|
||||
if timeout is None:
|
||||
timeout = aiohttp.ClientTimeout(total=10)
|
||||
ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_path)
|
||||
|
||||
if proxy:
|
||||
connector = SocksConnector(
|
||||
socks_ver=SocksVer.SOCKS5 if proxy['mode'] == 'socks5' else SocksVer.SOCKS4,
|
||||
|
||||
@ -130,10 +130,12 @@ class SPV(NetworkJobOnDefaultServer):
|
||||
except KeyError: pass
|
||||
self.print_error("verified %s" % tx_hash)
|
||||
header_hash = hash_header(header)
|
||||
flodata = self.wallet.get_flodata(tx_hash)
|
||||
tx_info = TxMinedInfo(height=tx_height,
|
||||
timestamp=header.get('timestamp'),
|
||||
txpos=pos,
|
||||
header_hash=header_hash)
|
||||
header_hash=header_hash,
|
||||
flodata=flodata)
|
||||
self.wallet.add_verified_tx(tx_hash, tx_info)
|
||||
if self.is_up_to_date() and self.wallet.is_up_to_date():
|
||||
self.wallet.save_verified_tx(write=True)
|
||||
|
||||
@ -636,8 +636,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
return tx
|
||||
return candidate
|
||||
|
||||
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
|
||||
change_addr=None, is_sweep=False):
|
||||
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, change_addr=None, is_sweep=False, flodata=None):
|
||||
# check outputs
|
||||
i_max = None
|
||||
for i, o in enumerate(outputs):
|
||||
@ -655,6 +654,9 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
for item in coins:
|
||||
self.add_input_info(item)
|
||||
|
||||
if flodata is None:
|
||||
flodata=''
|
||||
|
||||
# change address
|
||||
# if we leave it empty, coin_chooser will set it
|
||||
change_addrs = []
|
||||
@ -727,6 +729,10 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
|
||||
# Timelock tx to current height.
|
||||
tx.locktime = get_locktime_for_new_transaction(self.network)
|
||||
# Transactions with transaction comments/floData are version 2
|
||||
if flodata != "":
|
||||
tx.version = 2
|
||||
tx.flodata = "text:" + flodata
|
||||
run_hook('make_unsigned_transaction', self, tx)
|
||||
return tx
|
||||
|
||||
@ -998,7 +1004,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
if not r:
|
||||
return
|
||||
out = copy.copy(r)
|
||||
out['URI'] = 'bitcoin:' + addr + '?amount=' + format_satoshis(out.get('amount'))
|
||||
out['URI'] = 'flo:' + addr + '?amount=' + format_satoshis(out.get('amount'))
|
||||
status, conf = self.get_request_status(addr)
|
||||
out['status'] = status
|
||||
if conf is not None:
|
||||
|
||||
@ -26,6 +26,15 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
MIN_PYTHON_VERSION = "3.6.1" # FIXME duplicated from setup.py
|
||||
_min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split("."))))
|
||||
|
||||
|
||||
if sys.version_info[:3] < _min_python_version_tuple:
|
||||
sys.exit("Error: Electrum requires Python version >= %s..." % MIN_PYTHON_VERSION)
|
||||
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
is_bundle = getattr(sys, 'frozen', False)
|
||||
is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop"))
|
||||
|
||||
2
setup.py
@ -17,7 +17,7 @@ _min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split("."))))
|
||||
|
||||
|
||||
if sys.version_info[:3] < _min_python_version_tuple:
|
||||
sys.exit("Error: Electrum requires Python version >= {}...".format(MIN_PYTHON_VERSION))
|
||||
sys.exit("Error: Electrum requires Python version >= %s..." % MIN_PYTHON_VERSION)
|
||||
|
||||
with open('contrib/requirements/requirements.txt') as f:
|
||||
requirements = f.read().splitlines()
|
||||
|
||||