mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-07-30 03:41:28 +00:00
Fetch and add UTXOs to wallet
This commit is contained in:
@@ -26,7 +26,7 @@ use tower_hyper::{client, util};
|
|||||||
use tower_util::MakeService;
|
use tower_util::MakeService;
|
||||||
use futures::stream::Stream;
|
use futures::stream::Stream;
|
||||||
|
|
||||||
use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TransparentAddress, Utxo, TxFilter, Empty};
|
use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TransparentAddress, TxFilter, Empty};
|
||||||
use crate::grpc_client::client::CompactTxStreamer;
|
use crate::grpc_client::client::CompactTxStreamer;
|
||||||
|
|
||||||
// Used below to return the grpc "Client" type to calling methods
|
// Used below to return the grpc "Client" type to calling methods
|
||||||
@@ -83,8 +83,9 @@ impl LightClient {
|
|||||||
self.wallet.last_scanned_height() as u64
|
self.wallet.last_scanned_height() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_address(&self) -> json::JsonValue{
|
pub fn do_address(&self) -> json::JsonValue {
|
||||||
let addresses = self.wallet.address.iter().map( |ad| {
|
// Collect z addresses
|
||||||
|
let z_addresses = self.wallet.address.iter().map( |ad| {
|
||||||
let address = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &ad);
|
let address = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &ad);
|
||||||
object!{
|
object!{
|
||||||
"address" => address.clone(),
|
"address" => address.clone(),
|
||||||
@@ -93,10 +94,18 @@ impl LightClient {
|
|||||||
}
|
}
|
||||||
}).collect::<Vec<JsonValue>>();
|
}).collect::<Vec<JsonValue>>();
|
||||||
|
|
||||||
|
// Collect t addresses
|
||||||
|
let t_addresses = self.wallet.tkeys.iter().map( |pk| {
|
||||||
|
object!{
|
||||||
|
"address" => LightWallet::address_from_pk(&pk),
|
||||||
|
}
|
||||||
|
}).collect::<Vec<JsonValue>>();
|
||||||
|
|
||||||
object!{
|
object!{
|
||||||
"balance" => self.wallet.balance(None),
|
"balance" => self.wallet.balance(None),
|
||||||
"verified_balance" => self.wallet.verified_balance(None),
|
"verified_balance" => self.wallet.verified_balance(None),
|
||||||
"addresses" => addresses
|
"z_addresses" => z_addresses,
|
||||||
|
"t_addresses" => t_addresses,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +151,7 @@ impl LightClient {
|
|||||||
let mut spent_notes : Vec<JsonValue> = vec![];
|
let mut spent_notes : Vec<JsonValue> = vec![];
|
||||||
let mut pending_notes: Vec<JsonValue> = vec![];
|
let mut pending_notes: Vec<JsonValue> = vec![];
|
||||||
|
|
||||||
|
// Collect Sapling notes
|
||||||
self.wallet.txs.read().unwrap().iter()
|
self.wallet.txs.read().unwrap().iter()
|
||||||
.flat_map( |(txid, wtx)| {
|
.flat_map( |(txid, wtx)| {
|
||||||
wtx.notes.iter().map(move |nd|
|
wtx.notes.iter().map(move |nd|
|
||||||
@@ -166,10 +176,28 @@ impl LightClient {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Collect UTXOs
|
||||||
|
let utxos = self.wallet.txs.read().unwrap().iter()
|
||||||
|
.flat_map( |(_, wtx)| {
|
||||||
|
wtx.utxos.iter().map(move |utxo| {
|
||||||
|
object!{
|
||||||
|
"created_in_block" => wtx.block,
|
||||||
|
"created_in_txid" => format!("{}", utxo.txid),
|
||||||
|
"value" => utxo.value,
|
||||||
|
"is_change" => false, // TODO: Identify notes as change
|
||||||
|
"address" => utxo.address.clone(),
|
||||||
|
"spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)),
|
||||||
|
"unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<JsonValue>>();
|
||||||
|
|
||||||
object!{
|
object!{
|
||||||
"spent_notes" => spent_notes,
|
"spent_notes" => spent_notes,
|
||||||
"unspent_notes" => unspent_notes,
|
"unspent_notes" => unspent_notes,
|
||||||
"pending_notes" => pending_notes
|
"pending_notes" => pending_notes,
|
||||||
|
"utxos" => utxos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,11 +310,14 @@ impl LightClient {
|
|||||||
last_block, bytes_downloaded.load(Ordering::SeqCst) / 1024);
|
last_block, bytes_downloaded.load(Ordering::SeqCst) / 1024);
|
||||||
|
|
||||||
// Fetch UTXOs
|
// Fetch UTXOs
|
||||||
self.fetch_utxos("tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ".to_string(), |utxo| {
|
self.wallet.tkeys.iter()
|
||||||
println!("Got txid {}", utxo.txid);
|
.map( |pk| LightWallet::address_from_pk(&pk))
|
||||||
println!("Got script {}", hex::encode(utxo.script));
|
.for_each( |taddr| {
|
||||||
});
|
let wallet = self.wallet.clone();
|
||||||
|
self.fetch_utxos(taddr, move |utxo| {
|
||||||
|
wallet.scan_utxo(&utxo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Get the Raw transaction for all the wallet transactions
|
// Get the Raw transaction for all the wallet transactions
|
||||||
|
|
||||||
@@ -441,8 +472,10 @@ impl LightClient {
|
|||||||
txid: TxId {0: txid_bytes},
|
txid: TxId {0: txid_bytes},
|
||||||
output_index: b.output_index,
|
output_index: b.output_index,
|
||||||
script: b.script.to_vec(),
|
script: b.script.to_vec(),
|
||||||
height: b.height,
|
height: b.height as i32,
|
||||||
value: b.value,
|
value: b.value,
|
||||||
|
spent: None,
|
||||||
|
unconfirmed_spent: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
c(u);
|
c(u);
|
||||||
|
@@ -13,7 +13,8 @@ use pairing::bls12_381::{Bls12};
|
|||||||
use ff::{PrimeField, PrimeFieldRepr};
|
use ff::{PrimeField, PrimeFieldRepr};
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, encoding::encode_payment_address,
|
constants::testnet::{HRP_SAPLING_PAYMENT_ADDRESS,B58_PUBKEY_ADDRESS_PREFIX,},
|
||||||
|
encoding::encode_payment_address,
|
||||||
proto::compact_formats::CompactBlock, welding_rig::scan_block,
|
proto::compact_formats::CompactBlock, welding_rig::scan_block,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -324,13 +325,17 @@ impl SaplingNoteData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct Utxo {
|
pub struct Utxo {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub txid: TxId,
|
pub txid: TxId,
|
||||||
pub output_index: u64,
|
pub output_index: u64,
|
||||||
pub script: Vec<u8>,
|
pub script: Vec<u8>,
|
||||||
pub value: u64,
|
pub value: u64,
|
||||||
pub height: u64
|
pub height: i32,
|
||||||
|
|
||||||
|
pub spent: Option<TxId>, // If this utxo was confirmed spent
|
||||||
|
pub unconfirmed_spent: Option<TxId>, // If this utxo was spent in a send, but has not yet been confirmed.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WalletTx {
|
pub struct WalletTx {
|
||||||
@@ -340,9 +345,12 @@ pub struct WalletTx {
|
|||||||
// WalletTx in LightWallet::txs)
|
// WalletTx in LightWallet::txs)
|
||||||
pub txid: TxId,
|
pub txid: TxId,
|
||||||
|
|
||||||
// List of all notes recieved in this tx. Note that some of these might be change notes.
|
// List of all notes recieved in this tx. Some of these might be change notes.
|
||||||
pub notes: Vec<SaplingNoteData>,
|
pub notes: Vec<SaplingNoteData>,
|
||||||
|
|
||||||
|
// List of all Utxos recieved in this Tx. Some of these might be change notes
|
||||||
|
pub utxos: Vec<Utxo>,
|
||||||
|
|
||||||
// Total shielded value spent in this Tx. Note that this is the value of notes spent,
|
// Total shielded value spent in this Tx. Note that this is the value of notes spent,
|
||||||
// the change is returned in the notes above. Subtract the two to get the actual value spent.
|
// the change is returned in the notes above. Subtract the two to get the actual value spent.
|
||||||
// Also note that even after subtraction, you might need to account for transparent inputs and outputs
|
// Also note that even after subtraction, you might need to account for transparent inputs and outputs
|
||||||
@@ -374,6 +382,7 @@ impl WalletTx {
|
|||||||
block,
|
block,
|
||||||
txid,
|
txid,
|
||||||
notes,
|
notes,
|
||||||
|
utxos: vec![],
|
||||||
total_shielded_value_spent
|
total_shielded_value_spent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -430,6 +439,9 @@ pub struct LightWallet {
|
|||||||
extfvks: Vec<ExtendedFullViewingKey>,
|
extfvks: Vec<ExtendedFullViewingKey>,
|
||||||
pub address: Vec<PaymentAddress<Bls12>>,
|
pub address: Vec<PaymentAddress<Bls12>>,
|
||||||
|
|
||||||
|
// Transparent keys. TODO: Make it not pub
|
||||||
|
pub tkeys: Vec<secp256k1::SecretKey>,
|
||||||
|
|
||||||
blocks: Arc<RwLock<Vec<BlockData>>>,
|
blocks: Arc<RwLock<Vec<BlockData>>>,
|
||||||
pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
|
pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
|
||||||
}
|
}
|
||||||
@@ -469,6 +481,9 @@ impl LightWallet {
|
|||||||
Language::English).unwrap().entropy());
|
Language::English).unwrap().entropy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Generate transparent addresses from the seed
|
||||||
|
let tpk = secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap();
|
||||||
|
|
||||||
// Derive only the first address
|
// Derive only the first address
|
||||||
// TODO: We need to monitor addresses, and always keep 1 "free" address, so
|
// TODO: We need to monitor addresses, and always keep 1 "free" address, so
|
||||||
// users can import a seed phrase and automatically get all used addresses
|
// users can import a seed phrase and automatically get all used addresses
|
||||||
@@ -479,6 +494,7 @@ impl LightWallet {
|
|||||||
extsks: vec![extsk],
|
extsks: vec![extsk],
|
||||||
extfvks: vec![extfvk],
|
extfvks: vec![extfvk],
|
||||||
address: vec![address],
|
address: vec![address],
|
||||||
|
tkeys: vec![tpk],
|
||||||
blocks: Arc::new(RwLock::new(vec![])),
|
blocks: Arc::new(RwLock::new(vec![])),
|
||||||
txs: Arc::new(RwLock::new(HashMap::new())),
|
txs: Arc::new(RwLock::new(HashMap::new())),
|
||||||
})
|
})
|
||||||
@@ -503,6 +519,9 @@ impl LightWallet {
|
|||||||
let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 )
|
let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 )
|
||||||
.collect::<Vec<PaymentAddress<Bls12>>>();
|
.collect::<Vec<PaymentAddress<Bls12>>>();
|
||||||
|
|
||||||
|
// TODO: Generate transparent addresses from the seed
|
||||||
|
let tpk = secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap();
|
||||||
|
|
||||||
let blocks = Vector::read(&mut reader, |r| BlockData::read(r))?;
|
let blocks = Vector::read(&mut reader, |r| BlockData::read(r))?;
|
||||||
|
|
||||||
let txs_tuples = Vector::read(&mut reader, |r| {
|
let txs_tuples = Vector::read(&mut reader, |r| {
|
||||||
@@ -518,6 +537,7 @@ impl LightWallet {
|
|||||||
extsks: extsks,
|
extsks: extsks,
|
||||||
extfvks: extfvks,
|
extfvks: extfvks,
|
||||||
address: addresses,
|
address: addresses,
|
||||||
|
tkeys: vec![tpk],
|
||||||
blocks: Arc::new(RwLock::new(blocks)),
|
blocks: Arc::new(RwLock::new(blocks)),
|
||||||
txs: Arc::new(RwLock::new(txs))
|
txs: Arc::new(RwLock::new(txs))
|
||||||
})
|
})
|
||||||
@@ -618,6 +638,15 @@ impl LightWallet {
|
|||||||
&extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap())
|
&extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn address_from_pk(pk: &secp256k1::SecretKey) -> String {
|
||||||
|
// Encode into t address
|
||||||
|
let mut hash160 = ripemd160::Ripemd160::new();
|
||||||
|
hash160.input(Sha256::digest(&pk[..].to_vec()));
|
||||||
|
|
||||||
|
// TODO: The taddr version prefix needs to be different for testnet and mainnet
|
||||||
|
hash160.result().to_base58check(&B58_PUBKEY_ADDRESS_PREFIX, &[])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_seed_phrase(&self) -> String {
|
pub fn get_seed_phrase(&self) -> String {
|
||||||
Mnemonic::from_entropy(&self.seed,
|
Mnemonic::from_entropy(&self.seed,
|
||||||
Language::English,
|
Language::English,
|
||||||
@@ -681,6 +710,7 @@ impl LightWallet {
|
|||||||
.sum::<u64>()
|
.sum::<u64>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan the full Tx and update memos for incoming shielded transactions
|
||||||
pub fn scan_full_tx(&self, tx: &Transaction) {
|
pub fn scan_full_tx(&self, tx: &Transaction) {
|
||||||
for output in tx.shielded_outputs.iter() {
|
for output in tx.shielded_outputs.iter() {
|
||||||
|
|
||||||
@@ -710,6 +740,27 @@ impl LightWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scan_utxo(&self, utxo: &Utxo) {
|
||||||
|
// Grab a write lock to the wallet so we can update it.
|
||||||
|
let mut txs = self.txs.write().unwrap();
|
||||||
|
|
||||||
|
// If the Txid doesn't exist, create it
|
||||||
|
if !txs.contains_key(&utxo.txid) {
|
||||||
|
let tx_entry = WalletTx {
|
||||||
|
block: utxo.height,
|
||||||
|
txid: utxo.txid,
|
||||||
|
notes: vec![],
|
||||||
|
utxos: vec![],
|
||||||
|
total_shielded_value_spent: 0
|
||||||
|
};
|
||||||
|
txs.insert(utxo.txid, tx_entry);
|
||||||
|
}
|
||||||
|
let tx_entry = txs.get_mut(&utxo.txid).unwrap();
|
||||||
|
|
||||||
|
// Add the utxo into the Wallet Tx entry
|
||||||
|
tx_entry.utxos.push(utxo.clone());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scan_block(&self, block: &[u8]) -> bool {
|
pub fn scan_block(&self, block: &[u8]) -> bool {
|
||||||
let block: CompactBlock = match parse_from_bytes(block) {
|
let block: CompactBlock = match parse_from_bytes(block) {
|
||||||
Ok(block) => block,
|
Ok(block) => block,
|
||||||
@@ -837,6 +888,7 @@ impl LightWallet {
|
|||||||
block: block_data.height,
|
block: block_data.height,
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
|
utxos: vec![],
|
||||||
total_shielded_value_spent: 0
|
total_shielded_value_spent: 0
|
||||||
};
|
};
|
||||||
txs.insert(tx.txid, tx_entry);
|
txs.insert(tx.txid, tx_entry);
|
||||||
|
Reference in New Issue
Block a user