show pending incoming amount
This commit is contained in:
parent
2780418c3e
commit
b0b737f72e
@ -324,6 +324,11 @@ namespace RpcFunctions {
|
||||
native.EddsaPrivateKey.fromCrock(
|
||||
meltCoin.coinPriv)).toCrock();
|
||||
|
||||
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
||||
for (let denom of newCoinDenoms) {
|
||||
valueOutput = Amounts.add(valueOutput, denom.value).amount;
|
||||
}
|
||||
|
||||
let refreshSession: RefreshSession = {
|
||||
meltCoinPub: meltCoin.coinPub,
|
||||
newDenoms: newCoinDenoms.map((d) => d.denom_pub),
|
||||
@ -336,6 +341,7 @@ namespace RpcFunctions {
|
||||
exchangeBaseUrl,
|
||||
transferPrivs,
|
||||
finished: false,
|
||||
valueOutput,
|
||||
};
|
||||
|
||||
return refreshSession;
|
||||
|
@ -24,12 +24,19 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
export interface JoinResult<L,R> {
|
||||
left: L;
|
||||
right: R;
|
||||
}
|
||||
|
||||
|
||||
export class Store<T> {
|
||||
name: string;
|
||||
validator?: (v: T) => T;
|
||||
storeParams: IDBObjectStoreParameters;
|
||||
|
||||
constructor(name: string, storeParams: IDBObjectStoreParameters, validator?: (v: T) => T) {
|
||||
constructor(name: string, storeParams: IDBObjectStoreParameters,
|
||||
validator?: (v: T) => T) {
|
||||
this.name = name;
|
||||
this.validator = validator;
|
||||
this.storeParams = storeParams;
|
||||
@ -54,12 +61,15 @@ export class Index<S extends IDBValidKey,T> {
|
||||
export interface QueryStream<T> {
|
||||
indexJoin<S,I extends IDBValidKey>(index: Index<I,S>,
|
||||
keyFn: (obj: T) => I): QueryStream<[T, S]>;
|
||||
filter(f: (x: any) => boolean): QueryStream<T>;
|
||||
keyJoin<S,I extends IDBValidKey>(store: Store<S>,
|
||||
keyFn: (obj: T) => I): QueryStream<JoinResult<T,S>>;
|
||||
filter(f: (T: any) => boolean): QueryStream<T>;
|
||||
reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
|
||||
flatMap(f: (x: T) => T[]): QueryStream<T>;
|
||||
toArray(): Promise<T[]>;
|
||||
}
|
||||
|
||||
export let AbortTransaction = Symbol("abort_transaction");
|
||||
|
||||
/**
|
||||
* Get an unresolved promise together with its extracted resolve / reject
|
||||
@ -101,6 +111,12 @@ abstract class QueryStreamBase<T> implements QueryStream<T> {
|
||||
return new QueryStreamIndexJoin(this, index.storeName, index.indexName, keyFn);
|
||||
}
|
||||
|
||||
keyJoin<S, I extends IDBValidKey>(store: Store<S>,
|
||||
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
|
||||
this.root.addStoreAccess(store.name, false);
|
||||
return new QueryStreamKeyJoin(this, store.name, keyFn);
|
||||
}
|
||||
|
||||
filter(f: (x: any) => boolean): QueryStream<T> {
|
||||
return new QueryStreamFilter(this, f);
|
||||
}
|
||||
@ -234,6 +250,42 @@ class QueryStreamIndexJoin<T, S> extends QueryStreamBase<[T, S]> {
|
||||
}
|
||||
|
||||
|
||||
class QueryStreamKeyJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
|
||||
s: QueryStreamBase<T>;
|
||||
storeName: string;
|
||||
key: any;
|
||||
|
||||
constructor(s: QueryStreamBase<T>, storeName: string,
|
||||
key: any) {
|
||||
super(s.root);
|
||||
this.s = s;
|
||||
this.storeName = storeName;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
subscribe(f: SubscribeFn) {
|
||||
this.s.subscribe((isDone, value, tx) => {
|
||||
if (isDone) {
|
||||
f(true, undefined, tx);
|
||||
return;
|
||||
}
|
||||
console.log("joining on", this.key(value));
|
||||
let s = tx.objectStore(this.storeName);
|
||||
let req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
||||
req.onsuccess = () => {
|
||||
let cursor = req.result;
|
||||
if (cursor) {
|
||||
f(false, {left:value, right: cursor.value}, tx);
|
||||
cursor.continue();
|
||||
} else {
|
||||
f(true, undefined, tx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class IterQueryStream<T> extends QueryStreamBase<T> {
|
||||
private storeName: string;
|
||||
private options: any;
|
||||
@ -304,7 +356,8 @@ export class QueryRoot {
|
||||
return new IterQueryStream(this, store.name, {});
|
||||
}
|
||||
|
||||
iterIndex<S extends IDBValidKey,T>(index: Index<S,T>, only?: S): QueryStream<T> {
|
||||
iterIndex<S extends IDBValidKey,T>(index: Index<S,T>,
|
||||
only?: S): QueryStream<T> {
|
||||
this.stores.add(index.storeName);
|
||||
return new IterQueryStream(this, index.storeName, {
|
||||
only,
|
||||
@ -326,6 +379,30 @@ export class QueryRoot {
|
||||
}
|
||||
|
||||
|
||||
mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
|
||||
let doPut = (tx: IDBTransaction) => {
|
||||
let reqGet = tx.objectStore(store.name).get(key);
|
||||
reqGet.onsuccess = () => {
|
||||
let r = reqGet.result;
|
||||
let m: T;
|
||||
try {
|
||||
m = f(r);
|
||||
} catch (e) {
|
||||
if (e == AbortTransaction) {
|
||||
tx.abort();
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
tx.objectStore(store.name).put(m);
|
||||
}
|
||||
};
|
||||
this.addWork(doPut, store.name, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add all object from an iterable to the given object store.
|
||||
* Fails if the object's key is already present
|
||||
@ -380,7 +457,8 @@ export class QueryRoot {
|
||||
/**
|
||||
* Get one object from a store by its key.
|
||||
*/
|
||||
getIndexed<I extends IDBValidKey,T>(index: Index<I,T>, key: I): Promise<T|undefined> {
|
||||
getIndexed<I extends IDBValidKey,T>(index: Index<I,T>,
|
||||
key: I): Promise<T|undefined> {
|
||||
if (key === void 0) {
|
||||
throw Error("key must not be undefined");
|
||||
}
|
||||
@ -388,7 +466,9 @@ export class QueryRoot {
|
||||
const {resolve, promise} = openPromise();
|
||||
|
||||
const doGetIndexed = (tx: IDBTransaction) => {
|
||||
const req = tx.objectStore(index.storeName).index(index.indexName).get(key);
|
||||
const req = tx.objectStore(index.storeName)
|
||||
.index(index.indexName)
|
||||
.get(key);
|
||||
req.onsuccess = () => {
|
||||
resolve(req.result);
|
||||
};
|
||||
@ -417,6 +497,9 @@ export class QueryRoot {
|
||||
tx.oncomplete = () => {
|
||||
resolve();
|
||||
};
|
||||
tx.onabort = () => {
|
||||
reject(Error("transaction aborted"));
|
||||
};
|
||||
for (let w of this.work) {
|
||||
w(tx);
|
||||
}
|
||||
|
@ -42,6 +42,12 @@ export class AmountJson {
|
||||
}
|
||||
|
||||
|
||||
export interface SignedAmountJson {
|
||||
amount: AmountJson;
|
||||
isNegative: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface ReserveRecord {
|
||||
reserve_pub: string;
|
||||
reserve_priv: string,
|
||||
@ -194,6 +200,12 @@ export interface RefreshSession {
|
||||
*/
|
||||
valueWithFee: AmountJson
|
||||
|
||||
/**
|
||||
* Sum of the value of denominations we want
|
||||
* to withdraw in this session, without fees.
|
||||
*/
|
||||
valueOutput: AmountJson;
|
||||
|
||||
/**
|
||||
* Signature to confirm the melting.
|
||||
*/
|
||||
@ -308,6 +320,15 @@ export class ExchangeHandle {
|
||||
static checked: (obj: any) => ExchangeHandle;
|
||||
}
|
||||
|
||||
export interface WalletBalance {
|
||||
[currency: string]: WalletBalanceEntry;
|
||||
}
|
||||
|
||||
export interface WalletBalanceEntry {
|
||||
available: AmountJson;
|
||||
pendingIncoming: AmountJson;
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class
|
||||
export class Contract {
|
||||
|
@ -27,10 +27,11 @@ import {
|
||||
IExchangeInfo,
|
||||
Denomination,
|
||||
Notifier,
|
||||
WireInfo, RefreshSession, ReserveRecord, CoinPaySig
|
||||
WireInfo, RefreshSession, ReserveRecord, CoinPaySig, WalletBalance,
|
||||
WalletBalanceEntry
|
||||
} from "./types";
|
||||
import {HttpResponse, RequestException} from "./http";
|
||||
import {QueryRoot, Store, Index} from "./query";
|
||||
import {QueryRoot, Store, Index, JoinResult, AbortTransaction} from "./query";
|
||||
import {Checkable} from "./checkable";
|
||||
import {canonicalizeBaseUrl} from "./helpers";
|
||||
import {ReserveCreationInfo, Amounts} from "./types";
|
||||
@ -904,11 +905,32 @@ export class Wallet {
|
||||
console.log("creating pre coin at", new Date());
|
||||
let preCoin = await this.cryptoApi
|
||||
.createPreCoin(denom, reserve);
|
||||
|
||||
let aborted = false;
|
||||
|
||||
function mutateReserve(r: ReserveRecord) {
|
||||
let currentAmount = r.current_amount;
|
||||
if (!currentAmount) {
|
||||
throw Error("can't withdraw from reserve when current amount is" +
|
||||
" unknown");
|
||||
}
|
||||
let x = Amounts.sub(currentAmount, preCoin.coinValue);
|
||||
if (x.saturated) {
|
||||
aborted = true;
|
||||
throw AbortTransaction;
|
||||
}
|
||||
r.current_amount = x.amount;
|
||||
return r;
|
||||
}
|
||||
|
||||
await this.q()
|
||||
.put(Stores.precoins, preCoin)
|
||||
.mutate(Stores.reserves, reserve.reserve_pub, mutateReserve)
|
||||
.finish();
|
||||
if (!aborted) {
|
||||
await this.processPreCoin(preCoin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@ -1155,26 +1177,99 @@ export class Wallet {
|
||||
* Retrieve a mapping from currency name to the amount
|
||||
* that is currenctly available for spending in the wallet.
|
||||
*/
|
||||
async getBalances(): Promise<any> {
|
||||
function collectBalances(c: Coin, byCurrency: any) {
|
||||
async getBalances(): Promise<WalletBalance> {
|
||||
function ensureEntry(balance: WalletBalance, currency: string) {
|
||||
let entry: WalletBalanceEntry|undefined = balance[currency];
|
||||
let z = Amounts.getZero(currency);
|
||||
if (!entry) {
|
||||
balance[currency] = entry = {
|
||||
available: z,
|
||||
pendingIncoming: z,
|
||||
};
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
function collectBalances(c: Coin, balance: WalletBalance) {
|
||||
if (c.suspended) {
|
||||
return byCurrency;
|
||||
return balance;
|
||||
}
|
||||
let acc: AmountJson = byCurrency[c.currentAmount.currency];
|
||||
if (!acc) {
|
||||
acc = Amounts.getZero(c.currentAmount.currency);
|
||||
}
|
||||
byCurrency[c.currentAmount.currency] = Amounts.add(c.currentAmount,
|
||||
acc).amount;
|
||||
return byCurrency;
|
||||
let currency = c.currentAmount.currency;
|
||||
let entry = ensureEntry(balance, currency);
|
||||
entry.available = Amounts.add(entry.available, c.currentAmount).amount;
|
||||
return balance;
|
||||
}
|
||||
|
||||
let byCurrency = await (
|
||||
this.q()
|
||||
function collectPendingWithdraw(r: ReserveRecord, balance: WalletBalance) {
|
||||
if (!r.confirmed) {
|
||||
return balance;
|
||||
}
|
||||
let entry = ensureEntry(balance, r.requested_amount.currency);
|
||||
let amount = r.current_amount;
|
||||
if (!amount) {
|
||||
amount = r.requested_amount;
|
||||
}
|
||||
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) {
|
||||
entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
|
||||
amount).amount;
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
function collectPendingRefresh(r: RefreshSession, balance: WalletBalance) {
|
||||
if (!r.finished) {
|
||||
return balance;
|
||||
}
|
||||
let entry = ensureEntry(balance, r.valueWithFee.currency);
|
||||
entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
|
||||
r.valueOutput).amount;
|
||||
|
||||
return balance;
|
||||
}
|
||||
|
||||
function collectSmallestWithdraw(e: IExchangeInfo, sw: any) {
|
||||
let min: AmountJson|undefined;
|
||||
for (let d of e.active_denoms) {
|
||||
let v = Amounts.add(d.value, d.fee_withdraw).amount;
|
||||
if (!min) {
|
||||
min = v;
|
||||
continue;
|
||||
}
|
||||
if (Amounts.cmp(v, min) < 0) {
|
||||
min = v;
|
||||
}
|
||||
}
|
||||
sw[e.baseUrl] = min;
|
||||
return sw;
|
||||
}
|
||||
|
||||
let balance = {};
|
||||
// Mapping from exchange pub to smallest
|
||||
// possible amount we can withdraw
|
||||
let smallestWithdraw: {[baseUrl: string]: AmountJson} = {};
|
||||
|
||||
smallestWithdraw = await (this.q()
|
||||
.iter(Stores.exchanges)
|
||||
.reduce(collectSmallestWithdraw, {}));
|
||||
|
||||
console.log("smallest withdraw", smallestWithdraw);
|
||||
|
||||
await (this.q()
|
||||
.iter(Stores.coins)
|
||||
.reduce(collectBalances, {}));
|
||||
.reduce(collectBalances, balance));
|
||||
|
||||
await (this.q()
|
||||
.iter(Stores.refresh)
|
||||
.reduce(collectPendingRefresh, balance));
|
||||
|
||||
console.log("balances collected");
|
||||
|
||||
await (this.q()
|
||||
.iter(Stores.reserves)
|
||||
.reduce(collectPendingWithdraw, balance));
|
||||
console.log("balance", balance);
|
||||
return balance;
|
||||
|
||||
return {balances: byCurrency};
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,7 +28,10 @@
|
||||
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
||||
import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
|
||||
import {HistoryRecord, HistoryLevel} from "../lib/wallet/wallet";
|
||||
import {AmountJson} from "../lib/wallet/types";
|
||||
import {
|
||||
AmountJson, WalletBalance, Amounts,
|
||||
WalletBalanceEntry
|
||||
} from "../lib/wallet/types";
|
||||
import {abbrev, prettyAmount} from "../lib/wallet/renderHtml";
|
||||
|
||||
declare var i18n: any;
|
||||
@ -105,7 +108,7 @@ export function main() {
|
||||
<WalletNavBar />
|
||||
<div style="margin:1em">
|
||||
<Router>
|
||||
<WalletBalance route="/balance" default/>
|
||||
<WalletBalanceView route="/balance" default/>
|
||||
<WalletHistory route="/history"/>
|
||||
<WalletDebug route="/debug"/>
|
||||
</Router>
|
||||
@ -183,8 +186,8 @@ function ExtensionLink(props: any) {
|
||||
</a>)
|
||||
}
|
||||
|
||||
class WalletBalance extends preact.Component<any, any> {
|
||||
myWallet: any;
|
||||
class WalletBalanceView extends preact.Component<any, any> {
|
||||
balance: WalletBalance;
|
||||
gotError = false;
|
||||
|
||||
componentWillMount() {
|
||||
@ -203,22 +206,31 @@ class WalletBalance extends preact.Component<any, any> {
|
||||
}
|
||||
this.gotError = false;
|
||||
console.log("got wallet", resp);
|
||||
this.myWallet = resp.balances;
|
||||
this.balance = resp;
|
||||
this.setState({});
|
||||
});
|
||||
}
|
||||
|
||||
renderEmpty() : JSX.Element {
|
||||
renderEmpty(): JSX.Element {
|
||||
let helpLink = (
|
||||
<ExtensionLink target="pages/help/empty-wallet.html">
|
||||
help
|
||||
</ExtensionLink>
|
||||
);
|
||||
return <div>You have no balance to show. Need some {helpLink} getting started?</div>;
|
||||
return <div>You have no balance to show. Need some {helpLink}
|
||||
getting started?</div>;
|
||||
}
|
||||
|
||||
formatPending(amount: AmountJson) {
|
||||
return (
|
||||
<span>
|
||||
(<span style="color: darkgreen">{prettyAmount(amount)}</span> pending)
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
let wallet = this.myWallet;
|
||||
let wallet = this.balance;
|
||||
if (this.gotError) {
|
||||
return i18n`Error: could not retrieve balance information.`;
|
||||
}
|
||||
@ -227,7 +239,18 @@ class WalletBalance extends preact.Component<any, any> {
|
||||
}
|
||||
console.log(wallet);
|
||||
let listing = Object.keys(wallet).map((key) => {
|
||||
return <p>{prettyAmount(wallet[key])}</p>
|
||||
let entry: WalletBalanceEntry = wallet[key];
|
||||
return (
|
||||
<p>
|
||||
{prettyAmount(entry.available)}
|
||||
{ " "}
|
||||
{Amounts.isNonZero(entry.pendingIncoming)
|
||||
? this.formatPending(entry.pendingIncoming)
|
||||
: []
|
||||
}
|
||||
|
||||
</p>
|
||||
);
|
||||
});
|
||||
if (listing.length > 0) {
|
||||
return <div>{listing}</div>;
|
||||
|
Loading…
Reference in New Issue
Block a user