price API
This commit is contained in:
parent
a0104484e1
commit
c99794214f
29
src/db.rs
29
src/db.rs
|
@ -1,7 +1,9 @@
|
||||||
use crate::chain::{Nf, NfRef};
|
use crate::chain::{Nf, NfRef};
|
||||||
|
use crate::prices::Quote;
|
||||||
use crate::taddr::{derive_tkeys, BIP44_PATH};
|
use crate::taddr::{derive_tkeys, BIP44_PATH};
|
||||||
use crate::transaction::{Contact, TransactionInfo};
|
use crate::transaction::{Contact, TransactionInfo};
|
||||||
use crate::{CTree, Witness, NETWORK};
|
use crate::{CTree, Witness, NETWORK};
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
use rusqlite::{params, Connection, OptionalExtension, NO_PARAMS};
|
use rusqlite::{params, Connection, OptionalExtension, NO_PARAMS};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||||
|
@ -9,7 +11,6 @@ use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
||||||
use zcash_primitives::merkle_tree::IncrementalWitness;
|
use zcash_primitives::merkle_tree::IncrementalWitness;
|
||||||
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
|
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
|
||||||
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
|
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
|
|
||||||
mod migration;
|
mod migration;
|
||||||
|
|
||||||
|
@ -660,17 +661,35 @@ impl DbAdapter {
|
||||||
Ok(timestamps)
|
Ok(timestamps)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_historical_prices(&mut self, prices: Vec<(i64, f64)>, currency: &str) -> anyhow::Result<()> {
|
pub fn store_historical_prices(
|
||||||
|
&mut self,
|
||||||
|
prices: &[Quote],
|
||||||
|
currency: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let db_transaction = self.connection.transaction()?;
|
let db_transaction = self.connection.transaction()?;
|
||||||
{
|
{
|
||||||
let mut statement = db_transaction.prepare("INSERT INTO historical_prices(timestamp, price, currency) VALUES (?1, ?2, ?3)")?;
|
let mut statement = db_transaction.prepare(
|
||||||
for (ts, px) in prices {
|
"INSERT INTO historical_prices(timestamp, price, currency) VALUES (?1, ?2, ?3)",
|
||||||
statement.execute(params![ts, px, currency])?;
|
)?;
|
||||||
|
for q in prices {
|
||||||
|
statement.execute(params![q.timestamp, q.price, currency])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db_transaction.commit()?;
|
db_transaction.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_latest_quote(&self, currency: &str) -> anyhow::Result<Option<Quote>> {
|
||||||
|
let quote = self.connection.query_row(
|
||||||
|
"SELECT timestamp, price FROM historical_prices WHERE currency = ?1 ORDER BY timestamp DESC",
|
||||||
|
params![currency],
|
||||||
|
|row| {
|
||||||
|
let timestamp: i64 = row.get(0)?;
|
||||||
|
let price: f64 = row.get(1)?;
|
||||||
|
Ok(Quote { timestamp, price })
|
||||||
|
}).optional()?;
|
||||||
|
Ok(quote)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -110,8 +110,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if version < 2 {
|
if version < 2 {
|
||||||
connection
|
connection.execute("ALTER TABLE received_notes ADD excluded BOOL", NO_PARAMS)?;
|
||||||
.execute("ALTER TABLE received_notes ADD excluded BOOL", NO_PARAMS)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if version < 3 {
|
if version < 3 {
|
||||||
|
@ -136,8 +135,14 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_schema_version(&connection, 4)?;
|
if version < 5 {
|
||||||
|
connection.execute(
|
||||||
|
"DELETE FROM historical_prices",
|
||||||
|
NO_PARAMS,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_schema_version(&connection, 5)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/lib.rs
18
src/lib.rs
|
@ -1,9 +1,9 @@
|
||||||
#[path = "generated/cash.z.wallet.sdk.rpc.rs"]
|
#[path = "generated/cash.z.wallet.sdk.rpc.rs"]
|
||||||
pub mod lw_rpc;
|
pub mod lw_rpc;
|
||||||
|
|
||||||
#[cfg(feature="ycash")]
|
#[cfg(feature = "ycash")]
|
||||||
mod coin {
|
mod coin {
|
||||||
use zcash_primitives::consensus::{Network, BranchId};
|
use zcash_primitives::consensus::{BranchId, Network};
|
||||||
|
|
||||||
pub const NETWORK: Network = Network::YCashMainNetwork;
|
pub const NETWORK: Network = Network::YCashMainNetwork;
|
||||||
pub const TICKER: &str = "ycash";
|
pub const TICKER: &str = "ycash";
|
||||||
|
@ -12,9 +12,9 @@ mod coin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature="ycash"))]
|
#[cfg(not(feature = "ycash"))]
|
||||||
mod coin {
|
mod coin {
|
||||||
use zcash_primitives::consensus::{Network, BranchId, BlockHeight};
|
use zcash_primitives::consensus::{BlockHeight, BranchId, Network};
|
||||||
|
|
||||||
pub const NETWORK: Network = Network::MainNetwork;
|
pub const NETWORK: Network = Network::MainNetwork;
|
||||||
pub const TICKER: &str = "zcash";
|
pub const TICKER: &str = "zcash";
|
||||||
|
@ -23,7 +23,7 @@ mod coin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use coin::{NETWORK, TICKER, get_branch};
|
pub use coin::{get_branch, NETWORK, TICKER};
|
||||||
|
|
||||||
// Mainnet
|
// Mainnet
|
||||||
// pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
|
// pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
|
||||||
|
@ -43,13 +43,13 @@ mod db;
|
||||||
mod hash;
|
mod hash;
|
||||||
mod key;
|
mod key;
|
||||||
mod mempool;
|
mod mempool;
|
||||||
|
mod pay;
|
||||||
|
mod prices;
|
||||||
mod print;
|
mod print;
|
||||||
mod scan;
|
mod scan;
|
||||||
mod taddr;
|
mod taddr;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
mod pay;
|
|
||||||
mod wallet;
|
mod wallet;
|
||||||
mod prices;
|
|
||||||
|
|
||||||
pub use crate::builder::advance_tree;
|
pub use crate::builder::advance_tree;
|
||||||
pub use crate::chain::{
|
pub use crate::chain::{
|
||||||
|
@ -59,11 +59,11 @@ pub use crate::chain::{
|
||||||
pub use crate::commitment::{CTree, Witness};
|
pub use crate::commitment::{CTree, Witness};
|
||||||
pub use crate::db::DbAdapter;
|
pub use crate::db::DbAdapter;
|
||||||
pub use crate::hash::pedersen_hash;
|
pub use crate::hash::pedersen_hash;
|
||||||
pub use crate::key::{is_valid_key, decode_key};
|
pub use crate::key::{decode_key, is_valid_key};
|
||||||
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||||
pub use crate::lw_rpc::*;
|
pub use crate::lw_rpc::*;
|
||||||
pub use crate::mempool::MemPool;
|
pub use crate::mempool::MemPool;
|
||||||
|
pub use crate::pay::{broadcast_tx, sign_offline_tx, Tx};
|
||||||
pub use crate::print::*;
|
pub use crate::print::*;
|
||||||
pub use crate::scan::{latest_height, scan_all, sync_async};
|
pub use crate::scan::{latest_height, scan_all, sync_async};
|
||||||
pub use crate::wallet::{Wallet, WalletBalance};
|
pub use crate::wallet::{Wallet, WalletBalance};
|
||||||
pub use crate::pay::{sign_offline_tx, broadcast_tx, Tx};
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use sync::{decode_key, Tx, sign_offline_tx, NETWORK};
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use sync::{decode_key, sign_offline_tx, Tx, NETWORK};
|
||||||
use zcash_client_backend::encoding::decode_extended_spending_key;
|
use zcash_client_backend::encoding::decode_extended_spending_key;
|
||||||
use zcash_primitives::consensus::Parameters;
|
use zcash_primitives::consensus::Parameters;
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let opts: SignArgs = SignArgs::parse();
|
let opts: SignArgs = SignArgs::parse();
|
||||||
let sk = sk.unwrap();
|
let sk = sk.unwrap();
|
||||||
let sk = decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)?.unwrap();
|
let sk =
|
||||||
|
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)?.unwrap();
|
||||||
|
|
||||||
let file_name = opts.tx_filename;
|
let file_name = opts.tx_filename;
|
||||||
let mut file = File::open(file_name)?;
|
let mut file = File::open(file_name)?;
|
||||||
|
|
|
@ -57,13 +57,7 @@ async fn test() -> anyhow::Result<()> {
|
||||||
// println!("TXID = {}", tx_id);
|
// println!("TXID = {}", tx_id);
|
||||||
|
|
||||||
let tx = wallet
|
let tx = wallet
|
||||||
.prepare_payment(
|
.prepare_payment(1, &address, 50000, "test memo", 0, 2)
|
||||||
1,
|
|
||||||
&address,
|
|
||||||
50000,
|
|
||||||
"test memo",
|
|
||||||
0,
|
|
||||||
2)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("TX = {}", tx);
|
println!("TX = {}", tx);
|
||||||
|
|
153
src/pay.rs
153
src/pay.rs
|
@ -1,22 +1,24 @@
|
||||||
use crate::{NETWORK, connect_lightwalletd, RawTransaction, get_latest_height};
|
|
||||||
use zcash_primitives::zip32::{ExtendedSpendingKey, ExtendedFullViewingKey};
|
|
||||||
use zcash_primitives::sapling::{Diversifier, Rseed, Node};
|
|
||||||
use zcash_primitives::transaction::components::Amount;
|
|
||||||
use zcash_primitives::sapling::keys::OutgoingViewingKey;
|
|
||||||
use zcash_primitives::memo::MemoBytes;
|
|
||||||
use zcash_client_backend::encoding::{encode_extended_full_viewing_key, decode_extended_full_viewing_key};
|
|
||||||
use zcash_primitives::consensus::{Parameters, Network, BlockHeight, BranchId};
|
|
||||||
use zcash_primitives::transaction::builder::Builder;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use jubjub::Fr;
|
|
||||||
use zcash_primitives::merkle_tree::IncrementalWitness;
|
|
||||||
use zcash_client_backend::address::RecipientAddress;
|
|
||||||
use crate::db::SpendableNote;
|
use crate::db::SpendableNote;
|
||||||
use crate::wallet::Recipient;
|
use crate::wallet::Recipient;
|
||||||
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
|
use crate::{connect_lightwalletd, get_latest_height, RawTransaction, NETWORK};
|
||||||
use serde::{Serialize, Deserialize};
|
use jubjub::Fr;
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use rand::rngs::OsRng;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
|
use zcash_client_backend::address::RecipientAddress;
|
||||||
|
use zcash_client_backend::encoding::{
|
||||||
|
decode_extended_full_viewing_key, encode_extended_full_viewing_key,
|
||||||
|
};
|
||||||
|
use zcash_primitives::consensus::{BlockHeight, BranchId, Network, Parameters};
|
||||||
|
use zcash_primitives::memo::MemoBytes;
|
||||||
|
use zcash_primitives::merkle_tree::IncrementalWitness;
|
||||||
|
use zcash_primitives::sapling::keys::OutgoingViewingKey;
|
||||||
|
use zcash_primitives::sapling::{Diversifier, Node, Rseed};
|
||||||
|
use zcash_primitives::transaction::builder::Builder;
|
||||||
|
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
|
||||||
|
use zcash_primitives::transaction::components::Amount;
|
||||||
|
use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
|
||||||
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Tx {
|
pub struct Tx {
|
||||||
|
@ -60,10 +62,16 @@ pub trait TxBuilder {
|
||||||
fvk: &ExtendedFullViewingKey,
|
fvk: &ExtendedFullViewingKey,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
rseed: &[u8],
|
rseed: &[u8],
|
||||||
witness: &[u8]
|
witness: &[u8],
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()>;
|
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()>;
|
||||||
fn add_z_output(&mut self, address: &str, ovk: &OutgoingViewingKey, amount: Amount, memo: &MemoBytes) -> anyhow::Result<()>;
|
fn add_z_output(
|
||||||
|
&mut self,
|
||||||
|
address: &str,
|
||||||
|
ovk: &OutgoingViewingKey,
|
||||||
|
amount: Amount,
|
||||||
|
memo: &MemoBytes,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ColdTxBuilder {
|
pub struct ColdTxBuilder {
|
||||||
|
@ -79,10 +87,21 @@ impl ColdTxBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxBuilder for ColdTxBuilder {
|
impl TxBuilder for ColdTxBuilder {
|
||||||
fn add_input(&mut self, _skey: Option<ExtendedSpendingKey>, diversifier: &Diversifier, fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], witness: &[u8]) -> anyhow::Result<()> {
|
fn add_input(
|
||||||
|
&mut self,
|
||||||
|
_skey: Option<ExtendedSpendingKey>,
|
||||||
|
diversifier: &Diversifier,
|
||||||
|
fvk: &ExtendedFullViewingKey,
|
||||||
|
amount: Amount,
|
||||||
|
rseed: &[u8],
|
||||||
|
witness: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let tx_in = TxIn {
|
let tx_in = TxIn {
|
||||||
diversifier: hex::encode(diversifier.0),
|
diversifier: hex::encode(diversifier.0),
|
||||||
fvk: encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk),
|
fvk: encode_extended_full_viewing_key(
|
||||||
|
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||||
|
&fvk,
|
||||||
|
),
|
||||||
amount: u64::from(amount),
|
amount: u64::from(amount),
|
||||||
rseed: hex::encode(rseed),
|
rseed: hex::encode(rseed),
|
||||||
witness: hex::encode(witness),
|
witness: hex::encode(witness),
|
||||||
|
@ -102,7 +121,13 @@ impl TxBuilder for ColdTxBuilder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_z_output(&mut self, address: &str, ovk: &OutgoingViewingKey, amount: Amount, memo: &MemoBytes) -> anyhow::Result<()> {
|
fn add_z_output(
|
||||||
|
&mut self,
|
||||||
|
address: &str,
|
||||||
|
ovk: &OutgoingViewingKey,
|
||||||
|
amount: Amount,
|
||||||
|
memo: &MemoBytes,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let tx_out = TxOut {
|
let tx_out = TxOut {
|
||||||
addr: address.to_string(),
|
addr: address.to_string(),
|
||||||
amount: u64::from(amount),
|
amount: u64::from(amount),
|
||||||
|
@ -115,12 +140,22 @@ impl TxBuilder for ColdTxBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxBuilder for Builder<'_, Network, OsRng> {
|
impl TxBuilder for Builder<'_, Network, OsRng> {
|
||||||
fn add_input(&mut self, skey: Option<ExtendedSpendingKey>, diversifier: &Diversifier, fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], witness: &[u8]) -> anyhow::Result<()> {
|
fn add_input(
|
||||||
|
&mut self,
|
||||||
|
skey: Option<ExtendedSpendingKey>,
|
||||||
|
diversifier: &Diversifier,
|
||||||
|
fvk: &ExtendedFullViewingKey,
|
||||||
|
amount: Amount,
|
||||||
|
rseed: &[u8],
|
||||||
|
witness: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let pa = fvk.fvk.vk.to_payment_address(diversifier.clone()).unwrap();
|
let pa = fvk.fvk.vk.to_payment_address(diversifier.clone()).unwrap();
|
||||||
let mut rseed_bytes = [0u8; 32];
|
let mut rseed_bytes = [0u8; 32];
|
||||||
rseed_bytes.copy_from_slice(rseed);
|
rseed_bytes.copy_from_slice(rseed);
|
||||||
let fr = Fr::from_bytes(&rseed_bytes).unwrap();
|
let fr = Fr::from_bytes(&rseed_bytes).unwrap();
|
||||||
let note = pa.create_note(u64::from(amount), Rseed::BeforeZip212(fr)).unwrap();
|
let note = pa
|
||||||
|
.create_note(u64::from(amount), Rseed::BeforeZip212(fr))
|
||||||
|
.unwrap();
|
||||||
let witness = IncrementalWitness::<Node>::read(&*witness).unwrap();
|
let witness = IncrementalWitness::<Node>::read(&*witness).unwrap();
|
||||||
let merkle_path = witness.path().unwrap();
|
let merkle_path = witness.path().unwrap();
|
||||||
self.add_sapling_spend(skey.unwrap(), diversifier.clone(), note, merkle_path)?;
|
self.add_sapling_spend(skey.unwrap(), diversifier.clone(), note, merkle_path)?;
|
||||||
|
@ -128,28 +163,38 @@ impl TxBuilder for Builder<'_, Network, OsRng> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()> {
|
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()> {
|
||||||
let to_addr = RecipientAddress::decode(&NETWORK, address).ok_or(anyhow::anyhow!("Not a valid address"))?;
|
let to_addr = RecipientAddress::decode(&NETWORK, address)
|
||||||
|
.ok_or(anyhow::anyhow!("Not a valid address"))?;
|
||||||
if let RecipientAddress::Transparent(t_address) = to_addr {
|
if let RecipientAddress::Transparent(t_address) = to_addr {
|
||||||
self.add_transparent_output(&t_address, amount)?;
|
self.add_transparent_output(&t_address, amount)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_z_output(&mut self, address: &str, ovk: &OutgoingViewingKey, amount: Amount, memo: &MemoBytes) -> anyhow::Result<()> {
|
fn add_z_output(
|
||||||
let to_addr = RecipientAddress::decode(&NETWORK, address).ok_or(anyhow::anyhow!("Not a valid address"))?;
|
&mut self,
|
||||||
|
address: &str,
|
||||||
|
ovk: &OutgoingViewingKey,
|
||||||
|
amount: Amount,
|
||||||
|
memo: &MemoBytes,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let to_addr = RecipientAddress::decode(&NETWORK, address)
|
||||||
|
.ok_or(anyhow::anyhow!("Not a valid address"))?;
|
||||||
if let RecipientAddress::Shielded(pa) = to_addr {
|
if let RecipientAddress::Shielded(pa) = to_addr {
|
||||||
self.add_sapling_output(
|
self.add_sapling_output(Some(ovk.clone()), pa.clone(), amount, Some(memo.clone()))?;
|
||||||
Some(ovk.clone()),
|
|
||||||
pa.clone(),
|
|
||||||
amount,
|
|
||||||
Some(memo.clone()),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_tx<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKey>, notes: &[SpendableNote], target_amount: Amount, fvk: &ExtendedFullViewingKey, recipients: &[Recipient]) -> anyhow::Result<Vec<u32>> {
|
pub fn prepare_tx<B: TxBuilder>(
|
||||||
|
builder: &mut B,
|
||||||
|
skey: Option<ExtendedSpendingKey>,
|
||||||
|
notes: &[SpendableNote],
|
||||||
|
target_amount: Amount,
|
||||||
|
fvk: &ExtendedFullViewingKey,
|
||||||
|
recipients: &[Recipient],
|
||||||
|
) -> anyhow::Result<Vec<u32>> {
|
||||||
let mut amount = target_amount;
|
let mut amount = target_amount;
|
||||||
amount += DEFAULT_FEE;
|
amount += DEFAULT_FEE;
|
||||||
let target_amount_with_fee = amount;
|
let target_amount_with_fee = amount;
|
||||||
|
@ -157,13 +202,13 @@ pub fn prepare_tx<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKe
|
||||||
for n in notes.iter() {
|
for n in notes.iter() {
|
||||||
if amount.is_positive() {
|
if amount.is_positive() {
|
||||||
let a = amount.min(
|
let a = amount.min(
|
||||||
Amount::from_u64(n.note.value)
|
Amount::from_u64(n.note.value).map_err(|_| anyhow::anyhow!("Invalid amount"))?,
|
||||||
.map_err(|_| anyhow::anyhow!("Invalid amount"))?,
|
|
||||||
);
|
);
|
||||||
amount -= a;
|
amount -= a;
|
||||||
let mut witness_bytes: Vec<u8> = vec![];
|
let mut witness_bytes: Vec<u8> = vec![];
|
||||||
n.witness.write(&mut witness_bytes)?;
|
n.witness.write(&mut witness_bytes)?;
|
||||||
if let Rseed::BeforeZip212(rseed) = n.note.rseed { // rseed are stored as pre-zip212
|
if let Rseed::BeforeZip212(rseed) = n.note.rseed {
|
||||||
|
// rseed are stored as pre-zip212
|
||||||
builder.add_input(
|
builder.add_input(
|
||||||
skey.clone(),
|
skey.clone(),
|
||||||
&n.diversifier,
|
&n.diversifier,
|
||||||
|
@ -179,10 +224,10 @@ pub fn prepare_tx<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKe
|
||||||
if amount.is_positive() {
|
if amount.is_positive() {
|
||||||
log::info!("Not enough balance");
|
log::info!("Not enough balance");
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Not enough balance, need {} zats, missing {} zats",
|
"Not enough balance, need {} zats, missing {} zats",
|
||||||
u64::from(target_amount_with_fee),
|
u64::from(target_amount_with_fee),
|
||||||
u64::from(amount)
|
u64::from(amount)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Preparing tx");
|
log::info!("Preparing tx");
|
||||||
|
@ -197,16 +242,9 @@ pub fn prepare_tx<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKe
|
||||||
log::info!("Sapling output: {}", r.amount);
|
log::info!("Sapling output: {}", r.amount);
|
||||||
let memo_bytes = hex::decode(&r.memo).unwrap();
|
let memo_bytes = hex::decode(&r.memo).unwrap();
|
||||||
let memo = MemoBytes::from_bytes(&memo_bytes)?;
|
let memo = MemoBytes::from_bytes(&memo_bytes)?;
|
||||||
builder.add_z_output(
|
builder.add_z_output(&r.address, ovk, amount, &memo)
|
||||||
&r.address,
|
|
||||||
ovk,
|
|
||||||
amount,
|
|
||||||
&memo,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
RecipientAddress::Transparent(_address) => {
|
|
||||||
builder.add_t_output(&r.address, amount)
|
|
||||||
}
|
}
|
||||||
|
RecipientAddress::Transparent(_address) => builder.add_t_output(&r.address, amount),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,12 +258,18 @@ pub fn sign_offline_tx(tx: &Tx, sk: &ExtendedSpendingKey) -> anyhow::Result<Vec<
|
||||||
let mut diversifier = [0u8; 11];
|
let mut diversifier = [0u8; 11];
|
||||||
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
|
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
|
||||||
let diversifier = Diversifier(diversifier);
|
let diversifier = Diversifier(diversifier);
|
||||||
let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &txin.fvk)?.unwrap();
|
let fvk = decode_extended_full_viewing_key(
|
||||||
|
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||||
|
&txin.fvk,
|
||||||
|
)?
|
||||||
|
.unwrap();
|
||||||
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
|
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
|
||||||
let mut rseed_bytes = [0u8; 32];
|
let mut rseed_bytes = [0u8; 32];
|
||||||
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes)?;
|
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes)?;
|
||||||
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
|
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
|
||||||
let note = pa.create_note(txin.amount, Rseed::BeforeZip212(rseed)).unwrap();
|
let note = pa
|
||||||
|
.create_note(txin.amount, Rseed::BeforeZip212(rseed))
|
||||||
|
.unwrap();
|
||||||
let w = hex::decode(&txin.witness)?;
|
let w = hex::decode(&txin.witness)?;
|
||||||
let witness = IncrementalWitness::<Node>::read(&*w)?;
|
let witness = IncrementalWitness::<Node>::read(&*w)?;
|
||||||
let merkle_path = witness.path().unwrap();
|
let merkle_path = witness.path().unwrap();
|
||||||
|
@ -264,10 +308,13 @@ pub fn sign_offline_tx(tx: &Tx, sk: &ExtendedSpendingKey) -> anyhow::Result<Vec<
|
||||||
pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result<String> {
|
pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result<String> {
|
||||||
let mut client = connect_lightwalletd(ld_url).await?;
|
let mut client = connect_lightwalletd(ld_url).await?;
|
||||||
let latest_height = get_latest_height(&mut client).await?;
|
let latest_height = get_latest_height(&mut client).await?;
|
||||||
let raw_tx = RawTransaction {
|
let raw_tx = RawTransaction {
|
||||||
data: tx.to_vec(),
|
data: tx.to_vec(),
|
||||||
height: latest_height as u64,
|
height: latest_height as u64,
|
||||||
};
|
};
|
||||||
let rep = client.send_transaction(Request::new(raw_tx)).await?.into_inner();
|
let rep = client
|
||||||
|
.send_transaction(Request::new(raw_tx))
|
||||||
|
.await?
|
||||||
|
.into_inner();
|
||||||
Ok(rep.error_message)
|
Ok(rep.error_message)
|
||||||
}
|
}
|
||||||
|
|
109
src/prices.rs
109
src/prices.rs
|
@ -1,53 +1,88 @@
|
||||||
|
use crate::{DbAdapter, TICKER};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::TICKER;
|
|
||||||
|
|
||||||
const DAY_SEC: i64 = 24*3600;
|
const DAY_SEC: i64 = 24 * 3600;
|
||||||
|
|
||||||
pub async fn retrieve_historical_prices(timestamps: &[i64], currency: &str) -> anyhow::Result<Vec<(i64, f64)>> {
|
#[derive(Debug)]
|
||||||
if timestamps.is_empty() { return Ok(Vec::new()); }
|
pub struct Quote {
|
||||||
let mut timestamps_map: HashMap<i64, Option<f64>> = HashMap::new();
|
pub timestamp: i64,
|
||||||
for ts in timestamps {
|
pub price: f64,
|
||||||
timestamps_map.insert(*ts, None);
|
}
|
||||||
}
|
|
||||||
let client = reqwest::Client::new();
|
pub async fn fetch_historical_prices(
|
||||||
let start = timestamps.first().unwrap();
|
now: i64,
|
||||||
let end = timestamps.last().unwrap() + DAY_SEC;
|
days: u32,
|
||||||
let url = format!("https://api.coingecko.com/api/v3/coins/{}/market_chart/range", TICKER);
|
currency: &str,
|
||||||
let params = [("from", start.to_string()), ("to", end.to_string()), ("vs_currency", currency.to_string())];
|
db: &DbAdapter,
|
||||||
let req = client.get(url).query(¶ms);
|
) -> anyhow::Result<Vec<Quote>> {
|
||||||
let res = req.send().await?;
|
let today = now / DAY_SEC;
|
||||||
let r: serde_json::Value = res.json().await?;
|
let from_day = today - days as i64;
|
||||||
let prices = r["prices"].as_array().unwrap();
|
let latest_quote = db.get_latest_quote(currency)?;
|
||||||
for p in prices.iter() {
|
let latest_day = if let Some(latest_quote) = latest_quote {
|
||||||
let p = p.as_array().unwrap();
|
latest_quote.timestamp / DAY_SEC
|
||||||
let ts = p[0].as_i64().unwrap() / 1000;
|
} else {
|
||||||
let px = p[1].as_f64().unwrap();
|
0
|
||||||
// rounded to daily
|
};
|
||||||
let date = NaiveDateTime::from_timestamp(ts, 0).date().and_hms(0, 0, 0);
|
let latest_day = latest_day.max(from_day);
|
||||||
let ts = date.timestamp();
|
|
||||||
if let Some(None) = timestamps_map.get(&ts) {
|
let mut quotes: Vec<Quote> = vec![];
|
||||||
timestamps_map.insert(ts, Some(px));
|
if latest_day < today {
|
||||||
|
let from = (latest_day + 1) * DAY_SEC;
|
||||||
|
let to = today * DAY_SEC;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let url = format!(
|
||||||
|
"https://api.coingecko.com/api/v3/coins/{}/market_chart/range",
|
||||||
|
TICKER
|
||||||
|
);
|
||||||
|
let params = [
|
||||||
|
("from", from.to_string()),
|
||||||
|
("to", to.to_string()),
|
||||||
|
("vs_currency", currency.to_string()),
|
||||||
|
];
|
||||||
|
let req = client.get(url).query(¶ms);
|
||||||
|
let res = req.send().await?;
|
||||||
|
let r: serde_json::Value = res.json().await?;
|
||||||
|
let prices = r["prices"].as_array().unwrap();
|
||||||
|
let mut prev_timestamp = 0i64;
|
||||||
|
for p in prices.iter() {
|
||||||
|
let p = p.as_array().unwrap();
|
||||||
|
let ts = p[0].as_i64().unwrap() / 1000;
|
||||||
|
let price = p[1].as_f64().unwrap();
|
||||||
|
// rounded to daily
|
||||||
|
let date = NaiveDateTime::from_timestamp(ts, 0).date().and_hms(0, 0, 0);
|
||||||
|
let timestamp = date.timestamp();
|
||||||
|
if timestamp != prev_timestamp {
|
||||||
|
let quote = Quote { timestamp, price };
|
||||||
|
quotes.push(quote);
|
||||||
|
}
|
||||||
|
prev_timestamp = timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let prices: Vec<_> = timestamps_map.iter().map(|(k, v)| {
|
|
||||||
(*k, v.expect(&format!("missing price for ts {}", *k)))
|
Ok(quotes)
|
||||||
}).collect();
|
|
||||||
Ok(prices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::DbAdapter;
|
|
||||||
use crate::db::DEFAULT_DB_PATH;
|
use crate::db::DEFAULT_DB_PATH;
|
||||||
use crate::prices::retrieve_historical_prices;
|
use crate::prices::fetch_historical_prices;
|
||||||
|
use crate::DbAdapter;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test() {
|
async fn test_fetch_quotes() {
|
||||||
let currency = "EUR";
|
let currency = "EUR";
|
||||||
let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||||
let ts = db.get_missing_prices_timestamp("USD").unwrap();
|
let now = SystemTime::now()
|
||||||
let prices = retrieve_historical_prices(&ts, currency).await.unwrap();
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
db.store_historical_prices(prices, currency).unwrap();
|
.unwrap()
|
||||||
|
.as_secs() as i64;
|
||||||
|
let quotes = fetch_historical_prices(now, 365, currency, &db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
for q in quotes.iter() {
|
||||||
|
println!("{:?}", q);
|
||||||
|
}
|
||||||
|
db.store_historical_prices("es, currency).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
11
src/scan.rs
11
src/scan.rs
|
@ -184,10 +184,13 @@ pub async fn sync_async(
|
||||||
b.compact_block.time,
|
b.compact_block.time,
|
||||||
n.tx_index as u32,
|
n.tx_index as u32,
|
||||||
)?;
|
)?;
|
||||||
my_tx_ids.insert(TxIdHeight {
|
my_tx_ids.insert(
|
||||||
height: n.height,
|
TxIdHeight {
|
||||||
index: n.tx_index as u32,
|
height: n.height,
|
||||||
}, id_tx);
|
index: n.tx_index as u32,
|
||||||
|
},
|
||||||
|
id_tx,
|
||||||
|
);
|
||||||
let id_note = db.store_received_note(
|
let id_note = db.store_received_note(
|
||||||
&ReceivedNote {
|
&ReceivedNote {
|
||||||
account: n.account,
|
account: n.account,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::chain::send_transaction;
|
use crate::chain::send_transaction;
|
||||||
use crate::{connect_lightwalletd, get_latest_height, AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, NETWORK, get_branch};
|
use crate::{
|
||||||
|
connect_lightwalletd, get_branch, get_latest_height, AddressList, CompactTxStreamerClient,
|
||||||
|
DbAdapter, GetAddressUtxosArg, NETWORK,
|
||||||
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bip39::{Language, Mnemonic, Seed};
|
use bip39::{Language, Mnemonic, Seed};
|
||||||
use ripemd160::{Digest, Ripemd160};
|
use ripemd160::{Digest, Ripemd160};
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter, NETWORK};
|
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter, NETWORK};
|
||||||
|
use futures::StreamExt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::mpsc::SyncSender;
|
||||||
use tonic::transport::Channel;
|
use tonic::transport::Channel;
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
use zcash_client_backend::encoding::{
|
use zcash_client_backend::encoding::{
|
||||||
|
@ -13,9 +16,6 @@ use zcash_primitives::sapling::note_encryption::{
|
||||||
};
|
};
|
||||||
use zcash_primitives::transaction::Transaction;
|
use zcash_primitives::transaction::Transaction;
|
||||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||||
use futures::StreamExt;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::sync::mpsc::SyncSender;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TransactionInfo {
|
pub struct TransactionInfo {
|
||||||
|
@ -167,7 +167,18 @@ pub async fn retrieve_tx_info(
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = tokio_stream::iter(decode_tx_params).for_each_concurrent(None, |mut p| async move {
|
let res = tokio_stream::iter(decode_tx_params).for_each_concurrent(None, |mut p| async move {
|
||||||
if let Ok(tx_info) = decode_transaction(&mut p.client, p.nf_map, p.id_tx, p.account, &p.fvk, &p.tx_hash, p.height, p.index).await {
|
if let Ok(tx_info) = decode_transaction(
|
||||||
|
&mut p.client,
|
||||||
|
p.nf_map,
|
||||||
|
p.id_tx,
|
||||||
|
p.account,
|
||||||
|
&p.fvk,
|
||||||
|
&p.tx_hash,
|
||||||
|
p.height,
|
||||||
|
p.index,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
p.tx.send(tx_info).unwrap();
|
p.tx.send(tx_info).unwrap();
|
||||||
drop(p.tx);
|
drop(p.tx);
|
||||||
}
|
}
|
||||||
|
@ -177,7 +188,12 @@ pub async fn retrieve_tx_info(
|
||||||
let mut contacts: Vec<Contact> = vec![];
|
let mut contacts: Vec<Contact> = vec![];
|
||||||
while let Ok(tx_info) = rx.recv() {
|
while let Ok(tx_info) = rx.recv() {
|
||||||
if !tx_info.address.is_empty() && !tx_info.memo.is_empty() {
|
if !tx_info.address.is_empty() && !tx_info.memo.is_empty() {
|
||||||
if let Some(contact) = decode_contact(tx_info.account, tx_info.index, &tx_info.address, &tx_info.memo)? {
|
if let Some(contact) = decode_contact(
|
||||||
|
tx_info.account,
|
||||||
|
tx_info.index,
|
||||||
|
&tx_info.address,
|
||||||
|
&tx_info.memo,
|
||||||
|
)? {
|
||||||
contacts.push(contact);
|
contacts.push(contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,7 +215,12 @@ pub async fn retrieve_tx_info(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_contact(account: u32, index: u32, address: &str, memo: &str) -> anyhow::Result<Option<Contact>> {
|
fn decode_contact(
|
||||||
|
account: u32,
|
||||||
|
index: u32,
|
||||||
|
address: &str,
|
||||||
|
memo: &str,
|
||||||
|
) -> anyhow::Result<Option<Contact>> {
|
||||||
let res = if let Some(memo_line) = memo.lines().next() {
|
let res = if let Some(memo_line) = memo.lines().next() {
|
||||||
let name = memo_line.strip_prefix("Contact:");
|
let name = memo_line.strip_prefix("Contact:");
|
||||||
name.map(|name| Contact {
|
name.map(|name| Contact {
|
||||||
|
@ -242,9 +263,10 @@ mod tests {
|
||||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tx_info = decode_transaction(&mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1)
|
let tx_info =
|
||||||
.await
|
decode_transaction(&mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1)
|
||||||
.unwrap();
|
.await
|
||||||
|
.unwrap();
|
||||||
println!("{:?}", tx_info);
|
println!("{:?}", tx_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
100
src/wallet.rs
100
src/wallet.rs
|
@ -1,12 +1,21 @@
|
||||||
use crate::chain::send_transaction;
|
use crate::chain::send_transaction;
|
||||||
|
use crate::db::SpendableNote;
|
||||||
use crate::key::{decode_key, is_valid_key};
|
use crate::key::{decode_key, is_valid_key};
|
||||||
|
use crate::pay::prepare_tx;
|
||||||
|
use crate::pay::{ColdTxBuilder, Tx};
|
||||||
|
use crate::prices::fetch_historical_prices;
|
||||||
use crate::scan::ProgressCallback;
|
use crate::scan::ProgressCallback;
|
||||||
use crate::taddr::{get_taddr_balance, shield_taddr};
|
use crate::taddr::{get_taddr_balance, shield_taddr};
|
||||||
use crate::{connect_lightwalletd, get_latest_height, BlockId, CTree, DbAdapter, NETWORK, get_branch};
|
use crate::{
|
||||||
|
connect_lightwalletd, get_branch, get_latest_height, BlockId, CTree, DbAdapter, NETWORK,
|
||||||
|
};
|
||||||
use bip39::{Language, Mnemonic};
|
use bip39::{Language, Mnemonic};
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
|
@ -16,19 +25,12 @@ use zcash_client_backend::encoding::{
|
||||||
};
|
};
|
||||||
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
|
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
|
||||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||||
|
use zcash_primitives::memo::{Memo, MemoBytes};
|
||||||
use zcash_primitives::transaction::builder::{Builder, Progress};
|
use zcash_primitives::transaction::builder::{Builder, Progress};
|
||||||
use zcash_primitives::transaction::components::amount::MAX_MONEY;
|
use zcash_primitives::transaction::components::amount::MAX_MONEY;
|
||||||
use zcash_primitives::transaction::components::Amount;
|
use zcash_primitives::transaction::components::Amount;
|
||||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
use serde::Deserialize;
|
|
||||||
use crate::pay::prepare_tx;
|
|
||||||
use zcash_primitives::memo::{Memo, MemoBytes};
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use crate::db::SpendableNote;
|
|
||||||
use crate::pay::{ColdTxBuilder, Tx};
|
|
||||||
use crate::prices::retrieve_historical_prices;
|
|
||||||
|
|
||||||
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
|
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
|
||||||
|
|
||||||
|
@ -133,7 +135,7 @@ impl Wallet {
|
||||||
progress_callback,
|
progress_callback,
|
||||||
ld_url,
|
ld_url,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_latest_height(&self) -> anyhow::Result<u32> {
|
pub async fn get_latest_height(&self) -> anyhow::Result<u32> {
|
||||||
|
@ -159,7 +161,7 @@ impl Wallet {
|
||||||
cb.clone(),
|
cb.clone(),
|
||||||
ld_url,
|
ld_url,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Self::scan_async(get_tx, db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?;
|
Self::scan_async(get_tx, db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -177,7 +179,7 @@ impl Wallet {
|
||||||
progress_callback,
|
progress_callback,
|
||||||
&self.ld_url,
|
&self.ld_url,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn skip_to_last_height(&self) -> anyhow::Result<()> {
|
pub async fn skip_to_last_height(&self) -> anyhow::Result<()> {
|
||||||
|
@ -203,10 +205,16 @@ impl Wallet {
|
||||||
self.db.trim_to_height(height)
|
self.db.trim_to_height(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_multi_payment(&self, account: u32, recipients_json: &str, anchor_offset: u32,
|
pub async fn send_multi_payment(
|
||||||
progress_callback: impl Fn(Progress) + Send + 'static) -> anyhow::Result<String> {
|
&self,
|
||||||
|
account: u32,
|
||||||
|
recipients_json: &str,
|
||||||
|
anchor_offset: u32,
|
||||||
|
progress_callback: impl Fn(Progress) + Send + 'static,
|
||||||
|
) -> anyhow::Result<String> {
|
||||||
let recipients: Vec<Recipient> = serde_json::from_str(recipients_json)?;
|
let recipients: Vec<Recipient> = serde_json::from_str(recipients_json)?;
|
||||||
self._send_payment(account, &recipients, anchor_offset, progress_callback).await
|
self._send_payment(account, &recipients, anchor_offset, progress_callback)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prepare_payment(
|
pub async fn prepare_payment(
|
||||||
|
@ -225,10 +233,21 @@ impl Wallet {
|
||||||
Ok(tx_str)
|
Ok(tx_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _prepare_payment(&self, account: u32, amount: u64, last_height: u32, recipients: &Vec<Recipient>, anchor_offset: u32) -> anyhow::Result<Tx> {
|
fn _prepare_payment(
|
||||||
|
&self,
|
||||||
|
account: u32,
|
||||||
|
amount: u64,
|
||||||
|
last_height: u32,
|
||||||
|
recipients: &Vec<Recipient>,
|
||||||
|
anchor_offset: u32,
|
||||||
|
) -> anyhow::Result<Tx> {
|
||||||
let amount = Amount::from_u64(amount).unwrap();
|
let amount = Amount::from_u64(amount).unwrap();
|
||||||
let ivk = self.db.get_ivk(account)?;
|
let ivk = self.db.get_ivk(account)?;
|
||||||
let extfvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)?.unwrap();
|
let extfvk = decode_extended_full_viewing_key(
|
||||||
|
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||||
|
&ivk,
|
||||||
|
)?
|
||||||
|
.unwrap();
|
||||||
let notes = self._get_spendable_notes(account, &extfvk, last_height, anchor_offset)?;
|
let notes = self._get_spendable_notes(account, &extfvk, last_height, anchor_offset)?;
|
||||||
let mut builder = ColdTxBuilder::new(last_height);
|
let mut builder = ColdTxBuilder::new(last_height);
|
||||||
prepare_tx(&mut builder, None, ¬es, amount, &extfvk, recipients)?;
|
prepare_tx(&mut builder, None, ¬es, amount, &extfvk, recipients)?;
|
||||||
|
@ -246,7 +265,8 @@ impl Wallet {
|
||||||
progress_callback: impl Fn(Progress) + Send + 'static,
|
progress_callback: impl Fn(Progress) + Send + 'static,
|
||||||
) -> anyhow::Result<String> {
|
) -> anyhow::Result<String> {
|
||||||
let recipients = Self::_build_recipients(to_address, amount, max_amount_per_note, memo)?;
|
let recipients = Self::_build_recipients(to_address, amount, max_amount_per_note, memo)?;
|
||||||
self._send_payment(account, &recipients, anchor_offset, progress_callback).await
|
self._send_payment(account, &recipients, anchor_offset, progress_callback)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _send_payment(
|
async fn _send_payment(
|
||||||
|
@ -268,7 +288,14 @@ impl Wallet {
|
||||||
|
|
||||||
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
|
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
|
||||||
log::info!("Preparing tx");
|
log::info!("Preparing tx");
|
||||||
let selected_notes = prepare_tx(&mut builder, Some(skey.clone()), ¬es, target_amount, &extfvk, recipients)?;
|
let selected_notes = prepare_tx(
|
||||||
|
&mut builder,
|
||||||
|
Some(skey.clone()),
|
||||||
|
¬es,
|
||||||
|
target_amount,
|
||||||
|
&extfvk,
|
||||||
|
recipients,
|
||||||
|
)?;
|
||||||
|
|
||||||
let (progress_tx, progress_rx) = mpsc::channel::<Progress>();
|
let (progress_tx, progress_rx) = mpsc::channel::<Progress>();
|
||||||
|
|
||||||
|
@ -306,7 +333,7 @@ impl Wallet {
|
||||||
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||||
&ivk,
|
&ivk,
|
||||||
)?
|
)?
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut diversifier_index = self.db.get_diversifier(account)?;
|
let mut diversifier_index = self.db.get_diversifier(account)?;
|
||||||
diversifier_index.increment().unwrap();
|
diversifier_index.increment().unwrap();
|
||||||
let (new_diversifier_index, pa) = fvk
|
let (new_diversifier_index, pa) = fvk
|
||||||
|
@ -336,7 +363,13 @@ impl Wallet {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _get_spendable_notes(&self, account: u32, extfvk: &ExtendedFullViewingKey, last_height: u32, anchor_offset: u32) -> anyhow::Result<Vec<SpendableNote>> {
|
fn _get_spendable_notes(
|
||||||
|
&self,
|
||||||
|
account: u32,
|
||||||
|
extfvk: &ExtendedFullViewingKey,
|
||||||
|
last_height: u32,
|
||||||
|
anchor_offset: u32,
|
||||||
|
) -> anyhow::Result<Vec<SpendableNote>> {
|
||||||
let anchor_height = self
|
let anchor_height = self
|
||||||
.db
|
.db
|
||||||
.get_last_sync_height()?
|
.get_last_sync_height()?
|
||||||
|
@ -352,7 +385,12 @@ impl Wallet {
|
||||||
Ok(notes)
|
Ok(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _build_recipients(to_address: &str, amount: u64, max_amount_per_note: u64, memo: &str) -> anyhow::Result<Vec<Recipient>> {
|
fn _build_recipients(
|
||||||
|
to_address: &str,
|
||||||
|
amount: u64,
|
||||||
|
max_amount_per_note: u64,
|
||||||
|
memo: &str,
|
||||||
|
) -> anyhow::Result<Vec<Recipient>> {
|
||||||
let mut recipients: Vec<Recipient> = vec![];
|
let mut recipients: Vec<Recipient> = vec![];
|
||||||
let target_amount = Amount::from_u64(amount).unwrap();
|
let target_amount = Amount::from_u64(amount).unwrap();
|
||||||
let max_amount_per_note = if max_amount_per_note != 0 {
|
let max_amount_per_note = if max_amount_per_note != 0 {
|
||||||
|
@ -376,13 +414,17 @@ impl Wallet {
|
||||||
Ok(recipients)
|
Ok(recipients)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sync_historical_prices(&mut self, currency: &str) -> anyhow::Result<u32> {
|
pub async fn sync_historical_prices(
|
||||||
let ts = self.db.get_missing_prices_timestamp(currency)?;
|
&mut self,
|
||||||
if !ts.is_empty() {
|
now: i64,
|
||||||
let prices = retrieve_historical_prices(&ts, currency).await?;
|
days: u32,
|
||||||
self.db.store_historical_prices(prices, currency)?;
|
currency: &str,
|
||||||
}
|
) -> anyhow::Result<u32> {
|
||||||
Ok(ts.len() as u32)
|
let quotes = fetch_historical_prices(now, days, currency, &self.db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
self.db.store_historical_prices("es, currency).unwrap();
|
||||||
|
Ok(quotes.len() as u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue