introducing getBalanceDetail for getting all depositable/transferable amount for a currency

This commit is contained in:
Sebastian 2023-01-20 15:41:08 -03:00
parent 81dda3b6b1
commit 7ea8321ddd
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
13 changed files with 146 additions and 34 deletions

View File

@ -105,6 +105,16 @@ export class CreateReserveResponse {
reservePub: string;
}
export interface GetBalanceDetailRequest {
currency: string;
}
export const codecForGetBalanceDetailRequest = (): Codec<GetBalanceDetailRequest> =>
buildCodecForObject<GetBalanceDetailRequest>()
.property("currency", codecForString())
.build("GetBalanceDetailRequest");
export interface Balance {
available: AmountString;
pendingIncoming: AmountString;
@ -215,11 +225,11 @@ export interface CoinDumpJson {
withdrawal_reserve_pub: string | undefined;
coin_status: CoinStatus;
spend_allocation:
| {
id: string;
amount: string;
}
| undefined;
| {
id: string;
amount: string;
}
| undefined;
/**
* Information about the age restriction
*/
@ -1792,6 +1802,7 @@ export const codecForUserAttentionsRequest = (): Codec<UserAttentionsRequest> =>
)
.build("UserAttentionsRequest");
export interface UserAttentionsRequest {
priority?: AttentionPriority;
}

View File

@ -48,14 +48,12 @@
*/
import {
AmountJson,
BalancesResponse,
Amounts,
Logger,
AuditorHandle,
ExchangeHandle,
BalancesResponse,
canonicalizeBaseUrl,
GetBalanceDetailRequest,
Logger,
parsePaytoUri,
TalerErrorCode,
} from "@gnu-taler/taler-util";
import {
AllowedAuditorInfo,
@ -63,11 +61,10 @@ import {
RefreshGroupRecord,
WalletStoresV1,
} from "../db.js";
import { GetReadOnlyAccess } from "../util/query.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { getExchangeDetails } from "./exchanges.js";
import { checkLogicInvariant } from "../util/invariants.js";
import { TalerError } from "../errors.js";
import { GetReadOnlyAccess } from "../util/query.js";
import { getExchangeDetails } from "./exchanges.js";
/**
* Logger.
@ -429,6 +426,43 @@ export async function getMerchantPaymentBalanceDetails(
return d;
}
export async function getBalanceDetail(
ws: InternalWalletState,
req: GetBalanceDetailRequest,
): Promise<MerchantPaymentBalanceDetails> {
const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = [];
const wires = new Array<string>();
await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails])
.runReadOnly(async (tx) => {
const allExchanges = await tx.exchanges.iter().toArray();
for (const e of allExchanges) {
const details = await getExchangeDetails(tx, e.baseUrl);
if (!details || req.currency !== details.currency) {
continue;
}
details.wireInfo.accounts.forEach((a) => {
const payto = parsePaytoUri(a.payto_uri);
if (payto && !wires.includes(payto.targetType)) {
wires.push(payto.targetType);
}
});
exchanges.push({
exchangePub: details.masterPublicKey,
exchangeBaseUrl: e.baseUrl,
});
}
});
return await getMerchantPaymentBalanceDetails(ws, {
currency: req.currency,
acceptedAuditors: [],
acceptedExchanges: exchanges,
acceptedWireMethods: wires,
minAge: 0,
});
}
export interface PeerPaymentRestrictionsForBalance {
currency: string;
restrictExchangeTo?: string;

View File

@ -457,7 +457,6 @@ export async function prepareDepositGroup(
};
}
export async function createDepositGroup(
ws: InternalWalletState,
req: CreateDepositGroupRequest,

View File

@ -24,7 +24,7 @@
* Imports.
*/
import {
AbortTransactionRequest as AbortTransactionRequest,
AbortTransactionRequest,
AcceptBankIntegratedWithdrawalRequest,
AcceptExchangeTosRequest,
AcceptManualWithdrawalRequest,
@ -56,6 +56,7 @@ import {
ExchangesListResponse,
ForceRefreshRequest,
ForgetKnownBankAccountsRequest,
GetBalanceDetailRequest,
GetContractTermsDetailsRequest,
GetExchangeTosRequest,
GetExchangeTosResult,
@ -66,14 +67,12 @@ import {
InitiatePeerPullPaymentResponse,
InitiatePeerPushPaymentRequest,
InitiatePeerPushPaymentResponse,
InitRequest,
InitResponse,
IntegrationTestArgs,
KnownBankAccounts,
ListKnownBankAccountsRequest,
ManualWithdrawalDetails,
UserAttentionsCountResponse,
UserAttentionsRequest,
UserAttentionsResponse,
PrepareDepositRequest,
PrepareDepositResponse,
PreparePayRequest,
@ -99,14 +98,16 @@ import {
TransactionByIdRequest,
TransactionsRequest,
TransactionsResponse,
UserAttentionByIdRequest,
UserAttentionsCountResponse,
UserAttentionsRequest,
UserAttentionsResponse,
WalletBackupContentV1,
WalletCoreVersion,
WalletCurrencyInfo,
WithdrawFakebankRequest,
WithdrawTestBalanceRequest,
WithdrawUriInfoResponse,
UserAttentionByIdRequest,
InitRequest,
} from "@gnu-taler/taler-util";
import { WalletContractData } from "./db.js";
import {
@ -116,6 +117,7 @@ import {
RemoveBackupProviderRequest,
RunBackupCycleRequest,
} from "./operations/backup/index.js";
import { MerchantPaymentBalanceDetails } from "./operations/balance.js";
import { PendingOperationsResponse as PendingTasksResponse } from "./pending-types.js";
export enum WalletApiOperation {
@ -138,6 +140,7 @@ export enum WalletApiOperation {
GetWithdrawalDetailsForAmount = "getWithdrawalDetailsForAmount",
AcceptManualWithdrawal = "acceptManualWithdrawal",
GetBalances = "getBalances",
GetBalanceDetail = "getBalanceDetail",
GetUserAttentionRequests = "getUserAttentionRequests",
GetUserAttentionUnreadCount = "getUserAttentionUnreadCount",
MarkAttentionRequestAsRead = "markAttentionRequestAsRead",
@ -221,6 +224,11 @@ export type GetBalancesOp = {
request: EmptyObject;
response: BalancesResponse;
};
export type GetBalancesDetailOp = {
op: WalletApiOperation.GetBalanceDetail;
request: GetBalanceDetailRequest;
response: MerchantPaymentBalanceDetails;
};
// group: Managing Transactions
@ -831,6 +839,7 @@ export type WalletOperations = {
[WalletApiOperation.ConfirmPay]: ConfirmPayOp;
[WalletApiOperation.AbortTransaction]: AbortTransactionOp;
[WalletApiOperation.GetBalances]: GetBalancesOp;
[WalletApiOperation.GetBalanceDetail]: GetBalancesDetailOp;
[WalletApiOperation.GetTransactions]: GetTransactionsOp;
[WalletApiOperation.GetTransactionById]: GetTransactionByIdOp;
[WalletApiOperation.RetryPendingNow]: RetryPendingNowOp;

View File

@ -45,6 +45,7 @@ import {
codecForDeleteTransactionRequest,
codecForForceRefreshRequest,
codecForForgetKnownBankAccounts,
codecForGetBalanceDetailRequest,
codecForGetContractTermsDetails,
codecForGetExchangeTosRequest,
codecForGetFeeForDeposit,
@ -87,6 +88,7 @@ import {
ExchangesListResponse,
ExchangeTosStatusDetails,
FeeDescription,
GetBalanceDetailRequest,
GetExchangeTosResult,
InitResponse,
j2s,
@ -154,7 +156,11 @@ import {
runBackupCycle,
} from "./operations/backup/index.js";
import { setWalletDeviceId } from "./operations/backup/state.js";
import { getBalances } from "./operations/balance.js";
import {
getBalanceDetail,
getBalances,
getMerchantPaymentBalanceDetails,
} from "./operations/balance.js";
import {
getExchangeTosStatus,
makeExchangeListItem,
@ -948,9 +954,9 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
ageCommitmentProof: c.ageCommitmentProof,
spend_allocation: c.spendAllocation
? {
amount: c.spendAllocation.amount,
id: c.spendAllocation.id,
}
amount: c.spendAllocation.amount,
id: c.spendAllocation.id,
}
: undefined,
});
}
@ -1111,6 +1117,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
case WalletApiOperation.GetBalances: {
return await getBalances(ws);
}
case WalletApiOperation.GetBalanceDetail: {
const req = codecForGetBalanceDetailRequest().decode(payload);
return await getBalanceDetail(ws, req);
}
case WalletApiOperation.GetUserAttentionRequests: {
const req = codecForUserAttentionsRequest().decode(payload);
return await getUserAttentions(ws, req);
@ -1350,7 +1360,8 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
{
amount: Amounts.stringify(amount),
reserve_pub: wres.reservePub,
debit_account: "payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo",
debit_account:
"payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo",
},
);
const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny());

View File

@ -66,6 +66,7 @@ export namespace State {
error: undefined;
type: Props["type"];
selectCurrency: ButtonHandler;
sendAll: ButtonHandler;
previous: Contact[];
goToBank: ButtonHandler;
goToWallet: ButtonHandler;

View File

@ -27,10 +27,21 @@ import { Contact, Props, State } from "./index.js";
export function useComponentState(props: Props): RecursiveState<State> {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const parsedInitialAmount = !props.amount
? undefined
: Amounts.parse(props.amount);
const hook = useAsyncAsHook(async () => {
if (!parsedInitialAmount) return undefined;
const resp = await api.wallet.call(WalletApiOperation.GetBalanceDetail, {
currency: parsedInitialAmount.currency,
});
return resp;
});
const total = hook && !hook.hasError ? hook.response : undefined;
// const initialCurrency = parsedInitialAmount?.currency;
const [amount, setAmount] = useState(
@ -120,6 +131,14 @@ export function useComponentState(props: Props): RecursiveState<State> {
props.goToWalletBankDeposit(currencyAndAmount);
}),
},
sendAll: {
onClick:
total === undefined
? undefined
: pushAlertOnError(async () => {
setAmount(total.balanceMerchantDepositable);
}),
},
goToWallet: {
onClick: invalid
? undefined
@ -143,6 +162,7 @@ export function useComponentState(props: Props): RecursiveState<State> {
setAmount(undefined);
}),
},
sendAll: {},
goToBank: {
onClick: invalid
? undefined

View File

@ -35,6 +35,7 @@ export const GetCash = tests.createExample(ReadyView, {
},
},
goToBank: {},
sendAll: {},
goToWallet: {},
previous: [],
selectCurrency: {},
@ -49,6 +50,7 @@ export const SendCash = tests.createExample(ReadyView, {
},
},
goToBank: {},
sendAll: {},
goToWallet: {},
previous: [],
selectCurrency: {},

View File

@ -118,6 +118,16 @@ describe("Destination selection states", () => {
expect(state.goToBank.onClick).not.eq(undefined);
expect(state.goToWallet.onClick).not.eq(undefined);
expect(state.amountHandler.value).deep.eq(
Amounts.parseOrThrow("ARS:2"),
);
},
(state) => {
if (state.status !== "ready") expect.fail();
if (state.error) expect.fail();
expect(state.goToBank.onClick).not.eq(undefined);
expect(state.goToWallet.onClick).not.eq(undefined);
expect(state.amountHandler.value).deep.eq(
Amounts.parseOrThrow("ARS:2"),
);

View File

@ -17,6 +17,7 @@
import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact";
import { AmountField } from "../../components/AmountField.js";
import { JustInDevMode } from "../../components/JustInDevMode.js";
import { SelectList } from "../../components/SelectList.js";
import {
Input,
@ -283,6 +284,7 @@ export function ReadySendView({
goToBank,
goToWallet,
previous,
sendAll,
}: State.Ready): VNode {
const { i18n } = useTranslationContext();
@ -292,13 +294,18 @@ export function ReadySendView({
<i18n.Translate>Specify the amount and the destination</i18n.Translate>
</h1>
<div>
<Grid container columns={2} justifyContent="space-between">
<AmountField
label={i18n.str`Amount`}
required
handler={amountHandler}
/>
</div>
<JustInDevMode>
<Button onClick={sendAll.onClick}>
<i18n.Translate>Send all</i18n.Translate>
</Button>
</JustInDevMode>
</Grid>
<Grid container spacing={1} columns={1}>
{previous.length > 0 ? (

View File

@ -111,8 +111,10 @@ export function HistoryView({
balances: Balance[];
}): VNode {
const { i18n } = useTranslationContext();
const currencies = balances.map((b) => b.available.split(":")[0]);
const { pushAlertOnError } = useAlertContext();
const currencies = balances
.filter((b) => Amounts.isNonZero(b.available))
.map((b) => b.available.split(":")[0]);
const defaultCurrencyIndex = currencies.findIndex(
(c) => c === defaultCurrency,

View File

@ -88,8 +88,8 @@ export interface BackgroundOperations {
};
setLoggingLevel: {
request: {
tag?: string,
level: LogLevel
tag?: string;
level: LogLevel;
};
response: void;
};

View File

@ -186,12 +186,18 @@ const backendHandlers: BackendHandlerType = {
setLoggingLevel,
};
async function setLoggingLevel({ tag, level }: { tag?: string, level: LogLevel }): Promise<void> {
logger.info(`setting ${tag} to ${level}`)
async function setLoggingLevel({
tag,
level,
}: {
tag?: string;
level: LogLevel;
}): Promise<void> {
logger.info(`setting ${tag} to ${level}`);
if (!tag) {
setGlobalLogLevelFromString(level)
setGlobalLogLevelFromString(level);
} else {
setLogLevelFromString(tag, level)
setLogLevelFromString(tag, level);
}
}