diff options
Diffstat (limited to 'packages/taler-util')
-rw-r--r-- | packages/taler-util/src/MerchantApiClient.ts | 21 | ||||
-rw-r--r-- | packages/taler-util/src/bank-api-client.ts | 20 | ||||
-rw-r--r-- | packages/taler-util/src/codec.ts | 13 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/merchant.ts | 31 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 210 | ||||
-rw-r--r-- | packages/taler-util/src/index.ts | 9 | ||||
-rw-r--r-- | packages/taler-util/src/merchant-api-types.ts | 352 | ||||
-rw-r--r-- | packages/taler-util/src/taler-crypto.ts | 17 | ||||
-rw-r--r-- | packages/taler-util/src/taler-types.ts | 6 | ||||
-rw-r--r-- | packages/taler-util/src/taleruri.test.ts | 63 | ||||
-rw-r--r-- | packages/taler-util/src/taleruri.ts | 23 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 86 |
12 files changed, 376 insertions, 475 deletions
diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts index c27f1d582..f58757fb5 100644 --- a/packages/taler-util/src/MerchantApiClient.ts +++ b/packages/taler-util/src/MerchantApiClient.ts @@ -19,6 +19,7 @@ import { TalerMerchantApi, codecForMerchantConfig, codecForMerchantOrderPrivateStatusResponse, + codecForPostOrderResponse, } from "./http-client/types.js"; import { HttpStatusCode } from "./http-status-codes.js"; import { @@ -31,13 +32,6 @@ import { FacadeCredentials } from "./libeufin-api-types.js"; import { LibtoolVersion } from "./libtool-version.js"; import { Logger } from "./logging.js"; import { - MerchantInstancesResponse, - MerchantPostOrderRequest, - MerchantPostOrderResponse, - MerchantTemplateAddDetails, - codecForMerchantPostOrderResponse, -} from "./merchant-api-types.js"; -import { FailCasesByMethod, OperationFail, OperationOk, @@ -206,7 +200,7 @@ export class MerchantApiClient { }); } - async getInstances(): Promise<MerchantInstancesResponse> { + async getInstances(): Promise<TalerMerchantApi.InstancesResponse> { const url = new URL("management/instances", this.baseUrl); const resp = await this.httpClient.fetch(url.href, { headers: this.makeAuthHeader(), @@ -227,18 +221,15 @@ export class MerchantApiClient { } async createOrder( - req: MerchantPostOrderRequest, - ): Promise<MerchantPostOrderResponse> { + req: TalerMerchantApi.PostOrderRequest, + ): Promise<TalerMerchantApi.PostOrderResponse> { let url = new URL("private/orders", this.baseUrl); const resp = await this.httpClient.fetch(url.href, { method: "POST", body: req, headers: this.makeAuthHeader(), }); - return readSuccessResponseJsonOrThrow( - resp, - codecForMerchantPostOrderResponse(), - ); + return readSuccessResponseJsonOrThrow(resp, codecForPostOrderResponse()); } async deleteOrder(req: { orderId: string; force?: boolean }): Promise<void> { @@ -292,7 +283,7 @@ export class MerchantApiClient { }; } - async createTemplate(req: MerchantTemplateAddDetails) { + async createTemplate(req: TalerMerchantApi.MerchantTemplateAddDetails) { let url = new URL("private/templates", this.baseUrl); const resp = await this.httpClient.fetch(url.href, { method: "POST", diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts index 51359129d..e9f442af6 100644 --- a/packages/taler-util/src/bank-api-client.ts +++ b/packages/taler-util/src/bank-api-client.ts @@ -43,8 +43,10 @@ import { import { checkSuccessResponseOrThrow, createPlatformHttpLib, + expectSuccessResponseOrThrow, HttpRequestLibrary, readSuccessResponseJsonOrThrow, + readSuccessResponseTextOrThrow, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; @@ -238,7 +240,7 @@ export class TalerCorebankApiClient { httpLib: HttpRequestLibrary; constructor( - private baseUrl: string, + public baseUrl: string, private args: BankAccessApiClientArgs = {}, ) { this.httpLib = args.httpClient ?? createPlatformHttpLib(); @@ -437,4 +439,20 @@ export class TalerCorebankApiClient { }); await readSuccessResponseJsonOrThrow(resp, codecForAny()); } + + async abortWithdrawalOperationV2( + username: string, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${username}/withdrawals/${wopi.withdrawal_id}/abort`, + this.baseUrl, + ); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: {}, + headers: this.makeAuthHeader(), + }); + await expectSuccessResponseOrThrow(resp); + } } diff --git a/packages/taler-util/src/codec.ts b/packages/taler-util/src/codec.ts index 54d450d82..b04ce0612 100644 --- a/packages/taler-util/src/codec.ts +++ b/packages/taler-util/src/codec.ts @@ -146,7 +146,7 @@ class UnionCodecBuilder< constructor( private discriminator: TagPropertyLabel, private baseCodec?: Codec<CommonBaseType>, - ) {} + ) { } /** * Define a property for the object. @@ -491,6 +491,17 @@ export function codecOptional<V>(innerCodec: Codec<V>): Codec<V | undefined> { }; } +export function codecOptionalDefault<V>(innerCodec: Codec<V>, def: V): Codec<V> { + return { + decode(x: any, c?: Context): V { + if (x === undefined || x === null) { + return def; + } + return innerCodec.decode(x, c); + }, + }; +} + export function codecForLazy<V>(innerCodec: () => Codec<V>): Codec<V> { let instance: Codec<V> | undefined = undefined return { diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts index d682dcfa0..892971fee 100644 --- a/packages/taler-util/src/http-client/merchant.ts +++ b/packages/taler-util/src/http-client/merchant.ts @@ -32,6 +32,7 @@ import { codecForInventorySummaryResponse, codecForMerchantConfig, codecForMerchantOrderPrivateStatusResponse, + codecForMerchantPosProductDetail, codecForMerchantRefundResponse, codecForOrderHistory, codecForOtpDeviceDetails, @@ -122,7 +123,7 @@ export enum TalerMerchantManagementCacheEviction { * Uses libtool's current:revision:age versioning. */ export class TalerMerchantInstanceHttpClient { - public readonly PROTOCOL_VERSION = "10:0:6"; + public readonly PROTOCOL_VERSION = "15:0:0"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -859,6 +860,32 @@ export class TalerMerchantInstanceHttpClient { } /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-pos + */ + async getPointOfSaleInventory(token: AccessToken | undefined) { + const url = new URL(`private/pos`, this.baseUrl); + + const headers: Record<string, string> = {}; + if (token) { + headers.Authorization = makeBearerTokenAuthHeader(token); + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForMerchantPosProductDetail()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + + } + + /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products-$PRODUCT_ID */ async getProductDetails(token: AccessToken | undefined, productId: string) { @@ -1611,6 +1638,8 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/templates`, this.baseUrl); + addMerchantPaginationParams(url, params); + const headers: Record<string, string> = {}; if (token) { headers.Authorization = makeBearerTokenAuthHeader(token); diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index c0004a218..edddf7d94 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -1,4 +1,3 @@ -import { deprecate } from "util"; import { codecForAmountString } from "../amounts.js"; import { Codec, @@ -14,11 +13,14 @@ import { codecForNumber, codecForString, codecOptional, + codecOptionalDefault, } from "../codec.js"; import { PaytoString, codecForPaytoString } from "../payto.js"; import { AmountString, + ExchangeWireAccount, InternationalizedString, + codecForExchangeWireAccount, codecForInternationalizedString, codecForLocation, } from "../taler-types.js"; @@ -27,7 +29,6 @@ import { AbsoluteTime, TalerProtocolDuration, TalerProtocolTimestamp, - codecForAbsoluteTime, codecForDuration, codecForTimestamp, } from "../time.js"; @@ -340,7 +341,7 @@ export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> => .property("name", codecForConstString("libeufin-bank")) .property("version", codecForString()) .property("bank_name", codecForString()) - .property("base_url", codecForString()) + .property("base_url", codecOptional(codecForString())) .property("allow_conversion", codecForBoolean()) .property("allow_registrations", codecForBoolean()) .property("allow_deletions", codecForBoolean()) @@ -358,7 +359,7 @@ export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> => ), ), ) - .property("wire_type", codecForString()) + .property("wire_type", codecOptionalDefault(codecForString(), "iban")) .build("TalerCorebankApi.Config"); //FIXME: implement this codec @@ -603,6 +604,37 @@ export const codecForInventoryEntry = .property("product_serial", codecForNumber()) .build("TalerMerchantApi.InventoryEntry"); +export const codecForMerchantPosProductDetail = + (): Codec<TalerMerchantApi.MerchantPosProductDetail> => + buildCodecForObject<TalerMerchantApi.MerchantPosProductDetail>() + .property("product_serial", codecForNumber()) + .property("product_id", codecOptional(codecForString())) + .property("categories", codecForList(codecForNumber())) + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .property("unit", codecForString()) + .property("price", codecForAmountString()) + .property("image", codecForString()) + .property("taxes", codecOptional(codecForList(codecForTax()))) + .property("total_stock", codecForNumber()) + .property("minimum_age", codecOptional(codecForNumber())) + .build("TalerMerchantApi.MerchantPosProductDetail"); + +export const codecForMerchantCategory = + (): Codec<TalerMerchantApi.MerchantCategory> => + buildCodecForObject<TalerMerchantApi.MerchantCategory>() + .property("id", codecForNumber()) + .property("name", codecForString()) + .property("name_i18n", codecForInternationalizedString()) + .build("TalerMerchantApi.MerchantCategory"); + +export const codecForFullInventoryDetailsResponse = + (): Codec<TalerMerchantApi.FullInventoryDetailsResponse> => + buildCodecForObject<TalerMerchantApi.FullInventoryDetailsResponse>() + .property("categories", codecForList(codecForMerchantCategory())) + .property("products", codecForList(codecForMerchantPosProductDetail())) + .build("TalerMerchantApi.FullInventoryDetailsResponse"); + export const codecForProductDetail = (): Codec<TalerMerchantApi.ProductDetail> => buildCodecForObject<TalerMerchantApi.ProductDetail>() @@ -611,9 +643,9 @@ export const codecForProductDetail = .property("unit", codecForString()) .property("price", codecForAmountString()) .property("image", codecForString()) - .property("taxes", codecForList(codecForTax())) - .property("address", codecForLocation()) - .property("next_restock", codecForTimestamp) + .property("taxes", codecOptional(codecForList(codecForTax()))) + .property("address", codecOptional(codecForLocation())) + .property("next_restock", codecOptional(codecForTimestamp)) .property("total_stock", codecForNumber()) .property("total_sold", codecForNumber()) .property("total_lost", codecForNumber()) @@ -893,8 +925,6 @@ export const codecForTemplateContractDetailsDefaults = .property("summary", codecOptional(codecForString())) .property("currency", codecOptional(codecForString())) .property("amount", codecOptional(codecForAmountString())) - .property("minimum_age", codecOptional(codecForNumber())) - .property("pay_duration", codecOptional(codecForDuration)) .build("TalerMerchantApi.TemplateContractDetailsDefaults"); export const codecForWalletTemplateDetails = @@ -1587,6 +1617,21 @@ export const codecForChallengerInfoResponse = .property("expires", codecForTimestamp) .build("ChallengerApi.ChallengerInfoResponse"); +export const codecForTemplateEditableDetails = + (): Codec<TalerMerchantApi.TemplateEditableDetails> => + buildCodecForObject<TalerMerchantApi.TemplateEditableDetails>() + .property("summary", codecOptional(codecForString())) + .property("currency", codecOptional(codecForString())) + .property("amount", codecOptional(codecForAmountString())) + .build("TemplateEditableDetails"); + +export const codecForMerchantReserveCreateConfirmation = + (): Codec<TalerMerchantApi.MerchantReserveCreateConfirmation> => + buildCodecForObject<TalerMerchantApi.MerchantReserveCreateConfirmation>() + .property("accounts", codecForList(codecForExchangeWireAccount())) + .property("reserve_pub", codecForString()) + .build("MerchantReserveCreateConfirmation"); + type EmailAddress = string; type PhoneNumber = string; type EddsaSignature = string; @@ -2394,7 +2439,7 @@ export namespace TalerCorebankApi { // Is 2FA enabled and what channel is used for challenges? tan_channel?: TanChannel; - + // Current status of the account // active: the account can be used // deleted: the account has been deleted but is retained for compliance @@ -4068,6 +4113,68 @@ export namespace TalerMerchantApi { product_serial: Integer; } + export interface FullInventoryDetailsResponse { + // List of products that are present in the inventory. + products: MerchantPosProductDetail[]; + + // List of categories in the inventory. + categories: MerchantCategory[]; + } + + export interface MerchantPosProductDetail { + // A unique numeric ID of the product + product_serial: number; + + // A merchant-internal unique identifier for the product + product_id?: string; + + // A list of category IDs this product belongs to. + // Typically, a product only belongs to one category, but more than one is supported. + categories: number[]; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n: { [lang_tag: string]: string }; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // Optional, if missing treat as "infinite". + total_stock?: Integer; + + // Minimum age buyer must have (in years). + minimum_age?: Integer; + } + + export interface MerchantCategory { + // A unique numeric ID of the category + id: number; + + // The name of the category. This will be shown to users and used in the order summary. + name: string; + + // Map from IETF BCP 47 language tags to localized names + name_i18n?: { [lang_tag: string]: string }; + } + export interface ProductDetail { // Human-readable product description. description: string; @@ -4089,7 +4196,7 @@ export namespace TalerMerchantApi { image: ImageDataUrl; // A list of taxes paid by the merchant for one unit of this product. - taxes: Tax[]; + taxes?: Tax[]; // Number of units of the product in stock in sum in total, // including all existing sales ever. Given in product-specific @@ -4104,7 +4211,7 @@ export namespace TalerMerchantApi { total_lost: Integer; // Identifies where the product is in stock. - address: Location; + address?: Location; // Identifies when we expect the next restocking to happen. next_restock?: Timestamp; @@ -4165,9 +4272,9 @@ export namespace TalerMerchantApi { otp_id?: string; } - type Order = MinimalOrderDetail | ContractTerms; + export type Order = MinimalOrderDetail & Partial<ContractTerms>; - interface MinimalOrderDetail { + export interface MinimalOrderDetail { // Amount to be paid by the customer. amount: AmountString; @@ -4186,7 +4293,7 @@ export namespace TalerMerchantApi { fulfillment_message?: string; } - interface MinimalInventoryProduct { + export interface MinimalInventoryProduct { // Which product is requested (here mandatory!). product_id: string; @@ -4464,7 +4571,6 @@ export namespace TalerMerchantApi { confirmed?: boolean; } - export interface OtpDeviceAddDetails { // Device ID to use. otp_device_id: string; @@ -4627,12 +4733,12 @@ export namespace TalerMerchantApi { currency?: string; - amount?: AmountString; - - minimum_age?: Integer; - - pay_duration?: RelativeTime; + /** + * Amount *or* a plain currency string. + */ + amount?: string; } + export interface TemplatePatchDetails { // Human-readable description for the template. template_description: string; @@ -5158,6 +5264,68 @@ export namespace TalerMerchantApi { // Master public key of the exchange. master_pub: EddsaPublicKey; } + + export interface MerchantReserveCreateConfirmation { + // Public key identifying the reserve. + reserve_pub: EddsaPublicKey; + + // Wire accounts of the exchange where to transfer the funds. + accounts: ExchangeWireAccount[]; + } + + export interface TemplateEditableDetails { + // Human-readable summary for the template. + summary?: string; + + // Required currency for payments to the template. + // The user may specify any amount, but it must be + // in this currency. + // This parameter is optional and should not be present + // if "amount" is given. + currency?: string; + + // The price is imposed by the merchant and cannot be changed by the customer. + // This parameter is optional. + amount?: AmountString; + } + + export interface MerchantTemplateContractDetails { + // Human-readable summary for the template. + summary?: string; + + // The price is imposed by the merchant and cannot be changed by the customer. + // This parameter is optional. + amount?: string; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age: number; + + // The time the customer need to pay before his order will be deleted. + // It is deleted if the customer did not pay and if the duration is over. + pay_duration: TalerProtocolDuration; + } + + export interface MerchantTemplateAddDetails { + // Template ID to use. + template_id: string; + + // Human-readable description for the template. + template_description: string; + + // A base64-encoded image selected by the merchant. + // This parameter is optional. + // We are not sure about it. + image?: string; + + editable_defaults?: TemplateEditableDetails; + + // Additional information in a separate template. + template_contract: MerchantTemplateContractDetails; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + } } export namespace ChallengerApi { diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index 24d6e9950..9f99f2f5a 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -18,18 +18,18 @@ export * from "./contract-terms.js"; export * from "./errors.js"; export { fnutil } from "./fnutils.js"; export * from "./helpers.js"; -export * from "./http-client/bank-conversion.js"; export * from "./http-client/authentication.js"; +export * from "./http-client/bank-conversion.js"; export * from "./http-client/bank-core.js"; -export * from "./http-client/merchant.js"; -export * from "./http-client/challenger.js"; export * from "./http-client/bank-integration.js"; export * from "./http-client/bank-revenue.js"; export * from "./http-client/bank-wire.js"; +export * from "./http-client/challenger.js"; export * from "./http-client/exchange.js"; -export { CacheEvictor } from "./http-client/utils.js"; +export * from "./http-client/merchant.js"; export * from "./http-client/officer-account.js"; export * from "./http-client/types.js"; +export { CacheEvictor } from "./http-client/utils.js"; export * from "./http-status-codes.js"; export * from "./i18n.js"; export * from "./iban.js"; @@ -38,7 +38,6 @@ export * from "./kdf.js"; export * from "./libeufin-api-types.js"; export * from "./libtool-version.js"; export * from "./logging.js"; -export * from "./merchant-api-types.js"; export { crypto_sign_keyPair_fromSeed, randomBytes, diff --git a/packages/taler-util/src/merchant-api-types.ts b/packages/taler-util/src/merchant-api-types.ts deleted file mode 100644 index 639ae8d13..000000000 --- a/packages/taler-util/src/merchant-api-types.ts +++ /dev/null @@ -1,352 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Test harness for various GNU Taler components. - * Also provides a fault-injection proxy. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { - AbsoluteTime, - AmountString, - Codec, - CoinPublicKeyString, - EddsaPublicKeyString, - ExchangeWireAccount, - FacadeCredentials, - MerchantContractTerms, - TalerProtocolDuration, - TalerProtocolTimestamp, - buildCodecForObject, - buildCodecForUnion, - codecForAmountString, - codecForAny, - codecForBoolean, - codecForCheckPaymentClaimedResponse, - codecForCheckPaymentUnpaidResponse, - codecForConstString, - codecForExchangeWireAccount, - codecForList, - codecForMerchantContractTerms, - codecForNumber, - codecForString, - codecForTimestamp, - codecOptional, -} from "@gnu-taler/taler-util"; - -export interface MerchantPostOrderRequest { - // The order must at least contain the minimal - // order detail, but can override all - order: Partial<MerchantContractTerms>; - - // if set, the backend will then set the refund deadline to the current - // time plus the specified delay. - refund_delay?: TalerProtocolDuration; - - // specifies the payment target preferred by the client. Can be used - // to select among the various (active) wire methods supported by the instance. - payment_target?: string; - - // FIXME: some fields are missing - - // Should a token for claiming the order be generated? - // False can make sense if the ORDER_ID is sufficiently - // high entropy to prevent adversarial claims (like it is - // if the backend auto-generates one). Default is 'true'. - create_token?: boolean; -} - -export type ClaimToken = string; - -export interface MerchantPostOrderResponse { - order_id: string; - token?: ClaimToken; -} - -export const codecForMerchantPostOrderResponse = - (): Codec<MerchantPostOrderResponse> => - buildCodecForObject<MerchantPostOrderResponse>() - .property("order_id", codecForString()) - .property("token", codecOptional(codecForString())) - .build("PostOrderResponse"); - -export const codecForMerchantRefundDetails = (): Codec<RefundDetails> => - buildCodecForObject<RefundDetails>() - .property("reason", codecForString()) - .property("pending", codecForBoolean()) - .property("amount", codecForAmountString()) - .property("timestamp", codecForTimestamp) - .build("PostOrderResponse"); - -export const codecForMerchantCheckPaymentPaidResponse = - (): Codec<MerchantCheckPaymentPaidResponse> => - buildCodecForObject<MerchantCheckPaymentPaidResponse>() - .property("order_status_url", codecForString()) - .property("order_status", codecForConstString("paid")) - .property("refunded", codecForBoolean()) - .property("wired", codecForBoolean()) - .property("deposit_total", codecForAmountString()) - .property("exchange_ec", codecForNumber()) - .property("exchange_hc", codecForNumber()) - .property("refund_amount", codecForAmountString()) - .property("contract_terms", codecForMerchantContractTerms()) - // FIXME: specify - .property("wire_details", codecForAny()) - .property("wire_reports", codecForAny()) - .property("refund_details", codecForAny()) - .build("CheckPaymentPaidResponse"); - -export type MerchantOrderPrivateStatusResponse = - | MerchantCheckPaymentPaidResponse - | CheckPaymentUnpaidResponse - | CheckPaymentClaimedResponse; - -export interface CheckPaymentClaimedResponse { - // Wallet claimed the order, but didn't pay yet. - order_status: "claimed"; - - contract_terms: MerchantContractTerms; -} - -export interface MerchantCheckPaymentPaidResponse { - // did the customer pay for this contract - order_status: "paid"; - - // Was the payment refunded (even partially) - refunded: boolean; - - // Did the exchange wire us the funds - wired: boolean; - - // Total amount the exchange deposited into our bank account - // for this contract, excluding fees. - deposit_total: AmountString; - - // Numeric error code indicating errors the exchange - // encountered tracking the wire transfer for this purchase (before - // we even got to specific coin issues). - // 0 if there were no issues. - exchange_ec: number; - - // HTTP status code returned by the exchange when we asked for - // information to track the wire transfer for this purchase. - // 0 if there were no issues. - exchange_hc: number; - - // Total amount that was refunded, 0 if refunded is false. - refund_amount: AmountString; - - // Contract terms - contract_terms: MerchantContractTerms; - - // Ihe wire transfer status from the exchange for this order if available, otherwise empty array - wire_details: TransactionWireTransfer[]; - - // Reports about trouble obtaining wire transfer details, empty array if no trouble were encountered. - wire_reports: TransactionWireReport[]; - - // The refund details for this order. One entry per - // refunded coin; empty array if there are no refunds. - refund_details: RefundDetails[]; - - order_status_url: string; -} - -export interface CheckPaymentUnpaidResponse { - order_status: "unpaid"; - - // URI that the wallet must process to complete the payment. - taler_pay_uri: string; - - order_status_url: string; - - // Alternative order ID which was paid for already in the same session. - // Only given if the same product was purchased before in the same session. - already_paid_order_id?: string; - - // We do we NOT return the contract terms here because they may not - // exist in case the wallet did not yet claim them. -} - -export interface RefundDetails { - // Reason given for the refund - reason: string; - - // when was the refund approved - timestamp: TalerProtocolTimestamp; - - // has not been taken yet - pending: boolean; - - // Total amount that was refunded (minus a refund fee). - amount: AmountString; -} - -export interface TransactionWireTransfer { - // Responsible exchange - exchange_url: string; - - // 32-byte wire transfer identifier - wtid: string; - - // execution time of the wire transfer - execution_time: AbsoluteTime; - - // Total amount that has been wire transferred - // to the merchant - amount: AmountString; - - // Was this transfer confirmed by the merchant via the - // POST /transfers API, or is it merely claimed by the exchange? - confirmed: boolean; -} - -export interface TransactionWireReport { - // Numerical error code - code: number; - - // Human-readable error description - hint: string; - - // Numerical error code from the exchange. - exchange_ec: number; - - // HTTP status code received from the exchange. - exchange_hc: number; - - // Public key of the coin for which we got the exchange error. - coin_pub: CoinPublicKeyString; -} - -export interface ReserveStatusEntry { - // Public key of the reserve - reserve_pub: string; - - // Timestamp when it was established - creation_time: AbsoluteTime; - - // Timestamp when it expires - expiration_time: AbsoluteTime; - - // Initial amount as per reserve creation call - merchant_initial_amount: AmountString; - - // Initial amount as per exchange, 0 if exchange did - // not confirm reserve creation yet. - exchange_initial_amount: AmountString; - - // Amount picked up so far. - pickup_amount: AmountString; - - // Amount approved for tips that exceeds the pickup_amount. - committed_amount: AmountString; - - // Is this reserve active (false if it was deleted but not purged) - active: boolean; -} - -export interface MerchantInstancesResponse { - // List of instances that are present in the backend (see Instance) - instances: MerchantInstanceDetail[]; -} - -export interface MerchantInstanceDetail { - // Merchant name corresponding to this instance. - name: string; - - // Merchant instance this response is about ($INSTANCE) - id: string; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKeyString; - - // List of the payment targets supported by this instance. Clients can - // specify the desired payment target in /order requests. Note that - // front-ends do not have to support wallets selecting payment targets. - payment_targets: string[]; -} - -export interface MerchantTemplateContractDetails { - // Human-readable summary for the template. - summary?: string; - - // The price is imposed by the merchant and cannot be changed by the customer. - // This parameter is optional. - amount?: string; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age: number; - - // The time the customer need to pay before his order will be deleted. - // It is deleted if the customer did not pay and if the duration is over. - pay_duration: TalerProtocolDuration; -} - -export interface MerchantTemplateAddDetails { - // Template ID to use. - template_id: string; - - // Human-readable description for the template. - template_description: string; - - // A base64-encoded image selected by the merchant. - // This parameter is optional. - // We are not sure about it. - image?: string; - - // Additional information in a separate template. - template_contract: MerchantTemplateContractDetails; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; -} - -export interface MerchantReserveCreateConfirmation { - // Public key identifying the reserve. - reserve_pub: EddsaPublicKeyString; - - // Wire accounts of the exchange where to transfer the funds. - accounts: ExchangeWireAccount[]; -} - -export const codecForMerchantReserveCreateConfirmation = - (): Codec<MerchantReserveCreateConfirmation> => - buildCodecForObject<MerchantReserveCreateConfirmation>() - .property("accounts", codecForList(codecForExchangeWireAccount())) - .property("reserve_pub", codecForString()) - .build("MerchantReserveCreateConfirmation"); - -export interface AccountAddDetails { - // payto:// URI of the account. - payto_uri: string; - - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // Credentials to use when accessing the credit facade. - // Never returned on a GET (as this may be somewhat - // sensitive data). Can be set in POST - // or PATCH requests to update (or delete) credentials. - // To really delete credentials, set them to the type: "none". - credit_facade_credentials?: FacadeCredentials; -} diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index e587773e2..950161b10 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -21,23 +21,23 @@ /** * Imports. */ -import * as nacl from "./nacl-fast.js"; -import { hmacSha256, hmacSha512 } from "./kdf.js"; import bigint from "big-integer"; +import * as fflate from "fflate"; +import { AmountLike, Amounts } from "./amounts.js"; import * as argon2 from "./argon2.js"; +import { canonicalJson } from "./helpers.js"; +import { hmacSha256, hmacSha512 } from "./kdf.js"; +import { Logger } from "./logging.js"; +import * as nacl from "./nacl-fast.js"; +import { secretbox } from "./nacl-fast.js"; import { CoinEnvelope, CoinPublicKeyString, - DenominationPubKey, DenomKeyType, + DenominationPubKey, HashCodeString, } from "./taler-types.js"; -import { Logger } from "./logging.js"; -import { secretbox } from "./nacl-fast.js"; -import * as fflate from "fflate"; -import { canonicalJson } from "./helpers.js"; import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js"; -import { AmountLike, Amounts } from "./amounts.js"; export type Flavor<T, FlavorT extends string> = T & { _flavor?: `taler.${FlavorT}`; @@ -974,6 +974,7 @@ export function hashWire(paytoUri: string, salt: string): string { export enum TalerSignaturePurpose { MERCHANT_TRACK_TRANSACTION = 1103, WALLET_RESERVE_WITHDRAW = 1200, + WALLET_RESERVE_HISTORY = 1208, WALLET_COIN_DEPOSIT = 1201, GLOBAL_FEES = 1022, MASTER_DENOMINATION_KEY_VALIDITY = 1025, diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts index 392e7149c..e2536b74a 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -1329,8 +1329,12 @@ export const codecForDenominationPubKey = () => .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) .build("DenominationPubKey"); +export type LitAmountString = `${string}:${number}`; + declare const __amount_str: unique symbol; -export type AmountString = string & { [__amount_str]: true }; +export type AmountString = + | (string & { [__amount_str]: true }) + | LitAmountString; // export type AmountString = string; export type Base32String = string; export type EddsaSignatureString = string; diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts index 7f10d21fd..b92366fb3 100644 --- a/packages/taler-util/src/taleruri.test.ts +++ b/packages/taler-util/src/taleruri.test.ts @@ -314,7 +314,7 @@ test("taler peer to peer pull URI (stringify)", (t) => { test("taler pay template URI (parsing)", (t) => { const url1 = - "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY?amount=KUDOS:5"; + "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; const r1 = parsePayTemplateUri(url1); if (!r1) { t.fail(); @@ -322,12 +322,11 @@ test("taler pay template URI (parsing)", (t) => { } t.deepEqual(r1.merchantBaseUrl, "https://merchant.example.com/"); t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"); - t.deepEqual(r1.templateParams.amount, "KUDOS:5"); }); test("taler pay template URI (parsing, http with port)", (t) => { const url1 = - "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY?amount=KUDOS:5"; + "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; const r1 = parsePayTemplateUri(url1); if (!r1) { t.fail(); @@ -335,20 +334,16 @@ test("taler pay template URI (parsing, http with port)", (t) => { } t.deepEqual(r1.merchantBaseUrl, "http://merchant.example.com:1234/"); t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"); - t.deepEqual(r1.templateParams.amount, "KUDOS:5"); }); test("taler pay template URI (stringify)", (t) => { const url1 = stringifyPayTemplateUri({ merchantBaseUrl: "http://merchant.example.com:1234/", templateId: "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY", - templateParams: { - amount: "KUDOS:5", - }, }); t.deepEqual( url1, - "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY?amount=KUDOS%3A5", + "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY", ); }); @@ -423,24 +418,27 @@ test("taler dev exp URI (stringify)", (t) => { */ test("taler withdraw exchange URI (parse)", (t) => { + // Pubkey has been phased out, may no longer be specified. { - const r1 = parseWithdrawExchangeUri( + const rx1 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net/someroot/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A2", ); - if (!r1) { + if (rx1) { t.fail(); return; } - t.deepEqual( - r1.exchangePub, - "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", - ); - t.deepEqual( - r1.exchangeBaseUrl, - "https://exchange.demo.taler.net/someroot/", + } + { + const rx2 = parseWithdrawExchangeUri( + "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", ); - t.deepEqual(r1.amount, "KUDOS:2"); + if (rx2) { + t.fail(); + return; + } } + + // Now test well-formed URIs { const r2 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net/someroot/", @@ -449,7 +447,6 @@ test("taler withdraw exchange URI (parse)", (t) => { t.fail(); return; } - t.deepEqual(r2.exchangePub, undefined); t.deepEqual(r2.amount, undefined); t.deepEqual( r2.exchangeBaseUrl, @@ -465,7 +462,6 @@ test("taler withdraw exchange URI (parse)", (t) => { t.fail(); return; } - t.deepEqual(r3.exchangePub, undefined); t.deepEqual(r3.amount, undefined); t.deepEqual(r3.exchangeBaseUrl, "https://exchange.demo.taler.net/"); } @@ -479,7 +475,6 @@ test("taler withdraw exchange URI (parse)", (t) => { t.fail(); return; } - t.deepEqual(r4.exchangePub, undefined); t.deepEqual(r4.amount, undefined); t.deepEqual(r4.exchangeBaseUrl, "https://exchange.demo.taler.net/"); } @@ -488,27 +483,21 @@ test("taler withdraw exchange URI (parse)", (t) => { test("taler withdraw exchange URI (stringify)", (t) => { const url = stringifyWithdrawExchange({ exchangeBaseUrl: "https://exchange.demo.taler.net", - exchangePub: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", }); - t.deepEqual( - url, - "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", - ); + t.deepEqual(url, "taler://withdraw-exchange/exchange.demo.taler.net/"); }); test("taler withdraw exchange URI with amount (stringify)", (t) => { const url = stringifyWithdrawExchange({ exchangeBaseUrl: "https://exchange.demo.taler.net", - exchangePub: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", amount: "KUDOS:19" as AmountString, }); t.deepEqual( url, - "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A19", + "taler://withdraw-exchange/exchange.demo.taler.net/?a=KUDOS%3A19", ); }); - /** * 5.13 action: add-exchange https://lsd.gnunet.org/lsd0006/#name-action-add-exchange */ @@ -522,10 +511,7 @@ test("taler add exchange URI (parse)", (t) => { t.fail(); return; } - t.deepEqual( - r1.exchangeBaseUrl, - "https://exchange.example.com/", - ); + t.deepEqual(r1.exchangeBaseUrl, "https://exchange.example.com/"); } { const r2 = parseAddExchangeUri( @@ -535,22 +521,15 @@ test("taler add exchange URI (parse)", (t) => { t.fail(); return; } - t.deepEqual( - r2.exchangeBaseUrl, - "https://exchanges.example.com/api/", - ); + t.deepEqual(r2.exchangeBaseUrl, "https://exchanges.example.com/api/"); } - }); test("taler add exchange URI (stringify)", (t) => { const url = stringifyAddExchange({ exchangeBaseUrl: "https://exchange.demo.taler.net", }); - t.deepEqual( - url, - "taler://add-exchange/exchange.demo.taler.net/", - ); + t.deepEqual(url, "taler://add-exchange/exchange.demo.taler.net/"); }); /** diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts index b4f9db6ef..54b7525e3 100644 --- a/packages/taler-util/src/taleruri.ts +++ b/packages/taler-util/src/taleruri.ts @@ -83,7 +83,6 @@ export interface PayTemplateUriResult { type: TalerUriAction.PayTemplate; merchantBaseUrl: string; templateId: string; - templateParams: TemplateParams; } export interface WithdrawUriResult { @@ -124,7 +123,6 @@ export interface BackupRestoreUri { export interface WithdrawExchangeUri { type: TalerUriAction.WithdrawExchange; exchangeBaseUrl: string; - exchangePub?: string; amount?: AmountString; } @@ -212,9 +210,7 @@ export function parseAddExchangeUriWithError(s: string) { const result: AddExchangeUri = { type: TalerUriAction.AddExchange, - exchangeBaseUrl: canonicalizeBaseUrl( - `${pi.body.innerProto}://${p}/`, - ), + exchangeBaseUrl: canonicalizeBaseUrl(`${pi.body.innerProto}://${p}/`), }; return opFixedSuccess(result); } @@ -440,7 +436,6 @@ export function parsePayTemplateUri( type: TalerUriAction.PayTemplate, merchantBaseUrl, templateId, - templateParams: params, }; } @@ -507,7 +502,14 @@ export function parseWithdrawExchangeUri( return undefined; } const host = parts[0].toLowerCase(); - const exchangePub = parts.length > 1 ? parts[parts.length - 1] : undefined; + // Used to be the reserve public key, now it's empty! + const lastPathComponent = + parts.length > 1 ? parts[parts.length - 1] : undefined; + + if (lastPathComponent) { + // invalid taler://withdraw-exchange URI, must end with a slash + return undefined; + } const pathSegments = parts.slice(1, parts.length - 1); const hostAndSegments = [host, ...pathSegments].join("/"); const exchangeBaseUrl = canonicalizeBaseUrl( @@ -519,7 +521,6 @@ export function parseWithdrawExchangeUri( return { type: TalerUriAction.WithdrawExchange, exchangeBaseUrl, - exchangePub: exchangePub != "" ? exchangePub : undefined, amount, }; } @@ -641,13 +642,12 @@ export function stringifyRestoreUri({ export function stringifyWithdrawExchange({ exchangeBaseUrl, - exchangePub, amount, }: Omit<WithdrawExchangeUri, "type">): string { const { proto, path, query } = getUrlInfo(exchangeBaseUrl, { a: amount, }); - return `${proto}://withdraw-exchange/${path}${exchangePub ?? ""}${query}`; + return `${proto}://withdraw-exchange/${path}${query}`; } export function stringifyAddExchange({ @@ -666,9 +666,8 @@ export function stringifyDevExperimentUri({ export function stringifyPayTemplateUri({ merchantBaseUrl, templateId, - templateParams, }: Omit<PayTemplateUriResult, "type">): string { - const { proto, path, query } = getUrlInfo(merchantBaseUrl, templateParams); + const { proto, path, query } = getUrlInfo(merchantBaseUrl); return `${proto}://pay-template/${path}${templateId}${query}`; } diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index ec6cb6f58..d472af187 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -48,6 +48,7 @@ import { } from "./codec.js"; import { CurrencySpecification, + TalerMerchantApi, TemplateParams, WithdrawalOperationStatus, canonicalizeBaseUrl, @@ -487,6 +488,7 @@ export interface PartialWalletRunConfig { builtin?: Partial<WalletRunConfig["builtin"]>; testing?: Partial<WalletRunConfig["testing"]>; features?: Partial<WalletRunConfig["features"]>; + lazyTaskLoop?: Partial<WalletRunConfig["lazyTaskLoop"]>; } export interface WalletRunConfig { @@ -521,6 +523,16 @@ export interface WalletRunConfig { features: { allowHttp: boolean; }; + + /** + * Start processing tasks only when explicitly required, even after + * init has been called. + * + * Useful when the wallet is started to make single read-only request, + * as otherwise wallet-core starts making network request and process + * unrelated pending tasks. + */ + lazyTaskLoop: boolean; } export interface InitRequest { @@ -651,11 +663,11 @@ export interface CoinDumpJson { withdrawal_reserve_pub: string | undefined; coin_status: CoinStatus; spend_allocation: - | { - id: string; - amount: AmountString; - } - | undefined; + | { + id: string; + amount: AmountString; + } + | undefined; /** * Information about the age restriction */ @@ -789,7 +801,7 @@ export const codecForPreparePayResultPaymentPossible = ) .build("PreparePayResultPaymentPossible"); -export interface BalanceDetails {} +export interface BalanceDetails { } /** * Detailed reason for why the wallet's balance is insufficient. @@ -1721,15 +1733,12 @@ export interface AddExchangeRequest { * @deprecated use a separate API call to start a forced exchange update instead */ forceUpdate?: boolean; - - masterPub?: string; } export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => buildCodecForObject<AddExchangeRequest>() .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("forceUpdate", codecOptional(codecForBoolean())) - .property("masterPub", codecOptional(codecForString())) .build("AddExchangeRequest"); export interface UpdateExchangeEntryRequest { @@ -1837,32 +1846,37 @@ export interface GetWithdrawalDetailsForAmountRequest { export interface PrepareBankIntegratedWithdrawalRequest { talerWithdrawUri: string; - exchangeBaseUrl: string; - forcedDenomSel?: ForcedDenomSel; - restrictAge?: number; + selectedExchange?: string; } export const codecForPrepareBankIntegratedWithdrawalRequest = (): Codec<PrepareBankIntegratedWithdrawalRequest> => buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("talerWithdrawUri", codecForString()) - .property("forcedDenomSel", codecForAny()) - .property("restrictAge", codecOptional(codecForNumber())) + .property("selectedExchange", codecOptional(codecForString())) .build("PrepareBankIntegratedWithdrawalRequest"); export interface PrepareBankIntegratedWithdrawalResponse { - transactionId: string; + transactionId?: string; + info: WithdrawUriInfoResponse; } export interface ConfirmWithdrawalRequest { transactionId: string; + exchangeBaseUrl: string; + amount: AmountString; + forcedDenomSel?: ForcedDenomSel; + restrictAge?: number; } export const codecForConfirmWithdrawalRequestRequest = (): Codec<ConfirmWithdrawalRequest> => buildCodecForObject<ConfirmWithdrawalRequest>() .property("transactionId", codecForString()) + .property("amount", codecForAmountString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("forcedDenomSel", codecForAny()) + .property("restrictAge", codecOptional(codecForNumber())) .build("ConfirmWithdrawalRequest"); export interface AcceptBankIntegratedWithdrawalRequest { @@ -1931,6 +1945,9 @@ export const codecForApplyRefundFromPurchaseIdRequest = export interface GetWithdrawalDetailsForUriRequest { talerWithdrawUri: string; + /** + * @deprecated not used + */ restrictAge?: number; } @@ -2023,6 +2040,21 @@ export const codecForSharePaymentResult = (): Codec<SharePaymentResult> => .property("privatePayUri", codecForString()) .build("SharePaymentResult"); +export interface CheckPayTemplateRequest { + talerPayTemplateUri: string; +} + +export type CheckPayTemplateReponse = { + templateDetails: TalerMerchantApi.WalletTemplateDetails; + supportedCurrencies: string[]; +} + +export const codecForCheckPayTemplateRequest = + (): Codec<CheckPayTemplateRequest> => + buildCodecForObject<CheckPayTemplateRequest>() + .property("talerPayTemplateUri", codecForString()) + .build("CheckPayTemplateRequest"); + export interface PreparePayTemplateRequest { talerPayTemplateUri: string; templateParams?: TemplateParams; @@ -3010,6 +3042,18 @@ export interface TestingWaitTransactionRequest { txState: TransactionState; } +export interface TestingGetReserveHistoryRequest { + reservePub: string; + exchangeBaseUrl: string; +} + +export const codecForTestingGetReserveHistoryRequest = + (): Codec<TestingGetReserveHistoryRequest> => + buildCodecForObject<TestingGetReserveHistoryRequest>() + .property("reservePub", codecForString()) + .property("exchangeBaseUrl", codecForString()) + .build("TestingGetReserveHistoryRequest"); + export interface TestingGetDenomStatsRequest { exchangeBaseUrl: string; } @@ -3294,3 +3338,13 @@ export const codecForSyncTermsOfServiceResponse = .property("annual_fee", codecForAmountString()) .property("version", codecForString()) .build("SyncTermsOfServiceResponse"); + +export interface HintNetworkAvailabilityRequest { + isNetworkAvailable: boolean; +} + +export const codecForHintNetworkAvailabilityRequest = + (): Codec<HintNetworkAvailabilityRequest> => + buildCodecForObject<HintNetworkAvailabilityRequest>() + .property("isNetworkAvailable", codecForBoolean()) + .build("HintNetworkAvailabilityRequest"); |