Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f628b70a5 | ||
|
|
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 |
BIN
electrum.icns
|
Before Width: | Height: | Size: 811 KiB After Width: | Height: | Size: 39 KiB |
@ -78,7 +78,8 @@ class AddressSynchronizer(PrintError):
|
||||
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
|
||||
@ -626,6 +627,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[4]
|
||||
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
|
||||
@ -775,7 +793,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)
|
||||
|
||||
@ -31,9 +31,16 @@ from . import constants
|
||||
from .util import bfh, bh2u
|
||||
from .simple_config import SimpleConfig
|
||||
|
||||
try:
|
||||
import scrypt
|
||||
getPoWHash = lambda x: scrypt.hash(x, x, N=1024, r=1, p=1, buflen=32)
|
||||
except ImportError:
|
||||
util.print_msg("Warning: package scrypt not available; synchronization could be very slow")
|
||||
from .scrypt import scrypt_1024_1_1_80 as getPoWHash
|
||||
|
||||
|
||||
HEADER_SIZE = 80 # bytes
|
||||
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
|
||||
MAX_TARGET = 0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
|
||||
|
||||
class MissingHeader(Exception):
|
||||
@ -254,34 +261,70 @@ class Blockchain(util.PrintError):
|
||||
@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'):
|
||||
raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
|
||||
if constants.net.TESTNET:
|
||||
return
|
||||
bits = cls.target_to_bits(target)
|
||||
#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}")
|
||||
block_hash = int('0x' + _hash, 16)
|
||||
target_val = self.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
|
||||
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):
|
||||
@ -446,30 +489,60 @@ 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:
|
||||
@ -542,7 +615,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:
|
||||
@ -573,6 +646,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 = 8
|
||||
SEGWIT_HRP = "ltc"
|
||||
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 = 2
|
||||
# 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 = 198
|
||||
SEGWIT_HRP = "tltc"
|
||||
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):
|
||||
|
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.3 KiB |
@ -941,6 +941,16 @@ class ElectrumWindow(App):
|
||||
d = LabelDialog(_('Enter description'), text, callback)
|
||||
d.open()
|
||||
|
||||
def flodata_dialog(self, screen):
|
||||
from .uix.dialogs.label_dialog import LabelDialog
|
||||
text = screen.flodata
|
||||
|
||||
def callback(text):
|
||||
screen.flodata = text
|
||||
|
||||
d = LabelDialog(_('Enter FLO data'), text, callback)
|
||||
d.open()
|
||||
|
||||
def amount_dialog(self, screen, show_max):
|
||||
from .uix.dialogs.amount_dialog import AmountDialog
|
||||
amount = screen.amount
|
||||
@ -990,6 +1000,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,)))
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 15 KiB |
@ -177,7 +177,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 +185,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 +198,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 +250,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 +263,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 +382,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()
|
||||
|
||||
@ -11,6 +11,7 @@ SendScreen:
|
||||
address: ''
|
||||
amount: ''
|
||||
message: ''
|
||||
flodata: ''
|
||||
is_pr: False
|
||||
BoxLayout
|
||||
padding: '12dp', '12dp', '12dp', '12dp'
|
||||
@ -38,6 +39,24 @@ SendScreen:
|
||||
CardSeparator:
|
||||
opacity: int(not root.is_pr)
|
||||
color: blue_bottom.foreground_color
|
||||
BoxLayout:
|
||||
id: flodata_selection
|
||||
size_hint: 1, None
|
||||
height: blue_bottom.item_height
|
||||
spacing: '5dp'
|
||||
Image:
|
||||
source: 'atlas://electrum/gui/kivy/theming/light/pen'
|
||||
size_hint: None, None
|
||||
size: '22dp', '22dp'
|
||||
pos_hint: {'center_y': .5}
|
||||
BlueButton:
|
||||
id: flodata
|
||||
text: s.flodata if s.flodata else (_('No FLO data') if root.is_pr else _('FLO data'))
|
||||
disabled: root.is_pr
|
||||
on_release: Clock.schedule_once(lambda dt: app.flodata_dialog(s))
|
||||
CardSeparator:
|
||||
opacity: int(not root.is_pr)
|
||||
color: blue_bottom.foreground_color
|
||||
BoxLayout:
|
||||
size_hint: 1, None
|
||||
height: blue_bottom.item_height
|
||||
|
||||
@ -459,13 +459,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)
|
||||
|
||||
@ -433,8 +433,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'))
|
||||
|
||||
@ -596,11 +596,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_report_bug(self):
|
||||
@ -835,7 +835,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)
|
||||
@ -865,8 +865,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)
|
||||
@ -1096,7 +1096,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)
|
||||
@ -1113,10 +1113,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' \
|
||||
@ -1124,24 +1130,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)
|
||||
@ -1221,7 +1227,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)
|
||||
@ -1236,7 +1242,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)
|
||||
@ -1250,7 +1256,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)
|
||||
@ -1508,6 +1514,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()
|
||||
@ -1532,7 +1539,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'))
|
||||
@ -1543,7 +1550,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)
|
||||
@ -1554,12 +1561,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
|
||||
@ -1792,7 +1799,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)
|
||||
|
||||
@ -114,6 +114,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)
|
||||
|
||||
@ -271,6 +273,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)
|
||||
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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -490,9 +490,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):
|
||||
|
||||
@ -1,409 +1,5 @@
|
||||
{
|
||||
"3smoooajg7qqac2y.onion": {
|
||||
"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": {
|
||||
"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"
|
||||
},
|
||||
"oneweek.duckdns.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"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": {
|
||||
"localhost": {
|
||||
"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)
|
||||
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
|
||||
# Note: The deserialization code originally comes from ABE.
|
||||
|
||||
|
||||
import codecs
|
||||
import struct
|
||||
import traceback
|
||||
import sys
|
||||
@ -642,6 +644,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,6 +685,7 @@ class Transaction:
|
||||
self._inputs = None
|
||||
self._outputs = None # type: List[TxOutput]
|
||||
self.locktime = 0
|
||||
self.flodata = ""
|
||||
self.version = 1
|
||||
# by default we assume this is a partial txn;
|
||||
# this value will get properly set when deserializing
|
||||
@ -782,16 +789,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):
|
||||
def from_io(klass, inputs, outputs, locktime=0, flodata=""):
|
||||
self = klass(None)
|
||||
self._inputs = inputs
|
||||
self._outputs = outputs
|
||||
self.locktime = locktime
|
||||
self.flodata = flodata
|
||||
self.BIP69_sort()
|
||||
return self
|
||||
|
||||
@ -1042,6 +1051,8 @@ 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]
|
||||
@ -1055,11 +1066,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):
|
||||
@ -1081,6 +1098,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)
|
||||
@ -1093,9 +1112,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()
|
||||
|
||||
@ -62,11 +62,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
|
||||
@ -149,7 +149,7 @@ 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
|
||||
@ -715,12 +715,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")
|
||||
raise Exception("Not a FLO URI")
|
||||
address = u.path
|
||||
|
||||
# python for android fails to parse query
|
||||
@ -737,7 +737,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']
|
||||
@ -786,7 +786,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)
|
||||
|
||||
|
||||
@ -917,6 +917,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 the tx
|
||||
|
||||
|
||||
def make_aiohttp_session(proxy: dict, headers=None, timeout=None):
|
||||
|
||||
@ -124,10 +124,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)
|
||||
|
||||
@ -603,7 +603,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
return candidate
|
||||
|
||||
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
|
||||
change_addr=None, is_sweep=False):
|
||||
change_addr=None, is_sweep=False, flodata=None):
|
||||
# check outputs
|
||||
i_max = None
|
||||
for i, o in enumerate(outputs):
|
||||
@ -621,6 +621,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 = []
|
||||
@ -693,6 +696,10 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
|
||||
# Timelock tx to current height.
|
||||
tx.locktime = self.get_local_height()
|
||||
# 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
|
||||
|
||||
@ -962,7 +969,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:
|
||||
|
||||
|
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: 24 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 144 KiB |