aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-08-26 09:33:48 -0300
committerSebastian <sebasjm@gmail.com>2024-08-26 09:34:03 -0300
commit1f3aac3b57af7f99de6d78fd43acea0b4cfdfcb1 (patch)
treea54e59ac94a04e3eb78761fbddb76b72844ba7a1
parentc0f4b0d0f206a82d4283735bb661a229a9bfffd3 (diff)
exchange api
-rw-r--r--packages/kyc-ui/src/Routing.tsx2
-rw-r--r--packages/kyc-ui/src/pages/Start.tsx2
-rw-r--r--packages/taler-util/src/http-client/exchange.ts693
-rw-r--r--packages/taler-util/src/types-taler-exchange.ts554
4 files changed, 977 insertions, 274 deletions
diff --git a/packages/kyc-ui/src/Routing.tsx b/packages/kyc-ui/src/Routing.tsx
index 193efe8e4..8dcfa24a4 100644
--- a/packages/kyc-ui/src/Routing.tsx
+++ b/packages/kyc-ui/src/Routing.tsx
@@ -27,8 +27,6 @@ import { useErrorBoundary } from "preact/hooks";
import { CallengeCompleted } from "./pages/CallengeCompleted.js";
import { Frame } from "./pages/Frame.js";
import { Start } from "./pages/Start.js";
-import { FillForm } from "./pages/FillForm.js";
-import { SaveToken } from "./pages/SaveToken.js";
export function Routing(): VNode {
// check session and defined if this is
diff --git a/packages/kyc-ui/src/pages/Start.tsx b/packages/kyc-ui/src/pages/Start.tsx
index 9813d5ff9..7bdbbf80f 100644
--- a/packages/kyc-ui/src/pages/Start.tsx
+++ b/packages/kyc-ui/src/pages/Start.tsx
@@ -226,7 +226,7 @@ function RequirementRow({
? undefined
: withErrorHandler(
async () => {
- return lib.exchange.startKycProcess(reqId);
+ return lib.exchange.startExternalKycProcess(reqId);
},
(res) => {
window.open(res.body.redirect_url, "_blank");
diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts
index 9b7635cb4..c9d2975d1 100644
--- a/packages/taler-util/src/http-client/exchange.ts
+++ b/packages/taler-util/src/http-client/exchange.ts
@@ -17,8 +17,9 @@ import {
opKnownAlternativeFailure,
opKnownHttpFailure,
opSuccessFromHttp,
- opUnknownFailure
+ opUnknownFailure,
} from "../operation.js";
+import { Codec, codecForAny } from "../codec.js";
import {
TalerSignaturePurpose,
buildSigPS,
@@ -26,7 +27,7 @@ import {
eddsaSign,
encodeCrock,
stringToBytes,
- timestampRoundedToBuffer
+ timestampRoundedToBuffer,
} from "../taler-crypto.js";
import {
AccessToken,
@@ -35,10 +36,12 @@ import {
PaginationParams,
ReserveAccount,
SigningKey,
- codecForTalerCommonConfigResponse
+ codecForTalerCommonConfigResponse,
} from "../types-taler-common.js";
import {
AmlDecisionRequest,
+ BatchWithdrawResponse,
+ ExchangeBatchWithdrawRequest,
ExchangeVersionResponse,
KycRequirementInformationId,
WalletKycRequest,
@@ -52,9 +55,13 @@ import {
codecForExchangeKeys,
codecForKycProcessClientInformation,
codecForKycProcessStartInformation,
- codecForLegitimizationNeededResponse
+ codecForLegitimizationNeededResponse,
} from "../types-taler-exchange.js";
-import { CacheEvictor, addMerchantPaginationParams, nullEvictor } from "./utils.js";
+import {
+ CacheEvictor,
+ addMerchantPaginationParams,
+ nullEvictor,
+} from "./utils.js";
import { TalerError } from "../errors.js";
import { TalerErrorCode } from "../taler-error-codes.js";
@@ -71,6 +78,8 @@ export enum TalerExchangeCacheEviction {
CREATE_DESCISION,
}
+declare const __pubId: unique symbol;
+export type ReservePub = string & { [__pubId]: true };
/**
*/
export class TalerExchangeHttpClient {
@@ -91,6 +100,20 @@ export class TalerExchangeHttpClient {
const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version);
return compare?.compatible ?? false;
}
+
+ // TERMS
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--seed
+ *
+ */
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--seed
+ *
+ */
+
+ // EXCHANGE INFORMATION
+
/**
* https://docs.taler.net/core/api-exchange.html#get--seed
*
@@ -163,6 +186,7 @@ export class TalerExchangeHttpClient {
return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
}
+
/**
* https://docs.taler.net/core/api-merchant.html#get--config
*
@@ -181,10 +205,390 @@ export class TalerExchangeHttpClient {
}
}
- // TERMS
+ //
+ // MANAGEMENT
+ //
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--management-keys
+ *
+ */
+ async getFutureKeys(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-keys
+ *
+ */
+ async signFutureKeys(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-denominations-$H_DENOM_PUB-revoke
+ *
+ */
+ async revokeFutureDenominationKeys(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-signkeys-$EXCHANGE_PUB-revoke
+ *
+ */
+ async revokeFutureSigningKeys(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-auditors
+ *
+ */
+ async enableAuditor(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-auditors-$AUDITOR_PUB-disable
+ *
+ */
+ async disableAuditor(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-wire-fee
+ *
+ */
+ async configWireFee(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-global-fees
+ *
+ */
+ async configGlobalFees(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-wire
+ *
+ */
+ async enableWireMethod(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-wire-disable
+ *
+ */
+ async disableWireMethod(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-drain
+ *
+ */
+ async drainProfits(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-aml-officers
+ *
+ */
+ async updateOfficer(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--management-partners
+ *
+ */
+ async enablePartner(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // AUDITOR
+ //
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--auditors-$AUDITOR_PUB-$H_DENOM_PUB
+ *
+ */
+ async addAuditor(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // WITHDRAWAL
+ //
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB
+ *
+ */
+ async getReserveInfo(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--csr-withdraw
+ *
+ */
+ async prepareCsrWithdawal(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-batch-withdraw
+ *
+ */
+ async withdraw(rid: ReservePub, body: ExchangeBatchWithdrawRequest) {
+ const url = new URL(`reserves/${rid}/batch-withdraw`, this.baseUrl);
+
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(
+ resp,
+ codecForAny() as Codec<BatchWithdrawResponse>,
+ );
+ case HttpStatusCode.Forbidden:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.BadRequest:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Conflict:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Gone:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.UnavailableForLegalReasons:
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForLegitimizationNeededResponse(),
+ );
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction
+ *
+ */
+ async withdrawWithAge(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--age-withdraw-$ACH-reveal
+ *
+ */
+ async revealCoinsForAge(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // RESERVE HISTORY
+ //
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB-history
+ *
+ */
+ async getResverveHistory(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // COIN HISTORY
//
- // KYC operations
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-history
+ *
+ */
+ async getCoinHistory(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // DEPOSIT
+ //
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--batch-deposit
+ *
+ */
+ async deposit(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // REFRESH
+ //
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--csr-melt
+ *
+ */
+ async prepareCsrMelt(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-melt
+ *
+ */
+ async meltCoin(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--refreshes-$RCH-reveal
+ *
+ */
+ async releaveCoin(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-link
+ *
+ */
+ async linkCoin(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // RECOUP
+ //
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup
+ *
+ */
+ async recoupReserveCoin(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup-refresh
+ *
+ */
+ async recoupRefreshCoin(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ // WIRE TRANSFER
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--transfers-$WTID
+ *
+ */
+ async getWireTransferInfo(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--deposits-$H_WIRE-$MERCHANT_PUB-$H_CONTRACT_TERMS-$COIN_PUB
+ *
+ */
+ async getWireTransferIdForDeposit(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ // REFUND
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-refund
+ *
+ */
+ async refund(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ // WALLET TO WALLET
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-merge
+ *
+ */
+ async getPurseInfoAtMerge(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-deposit
+ *
+ */
+ async getPurseInfoAtDeposit(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-create
+ *
+ */
+ async createPurseFromDeposit(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#delete--purses-$PURSE_PUB
+ *
+ */
+ async deletePurse(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge
+ *
+ */
+ async mergePurse(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-purse
+ *
+ */
+ async createPurseFromReserve(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-deposit
+ *
+ */
+ async depositIntoPurse(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ // WADS
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--wads-$WAD_ID
+ *
+ */
+ async getWadInfo(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ //
+ // KYC
//
/**
@@ -198,7 +602,7 @@ export class TalerExchangeHttpClient {
balance,
reserve_pub: account.id,
reserve_sig: encodeCrock(account.signingKey),
- }
+ };
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -213,7 +617,11 @@ export class TalerExchangeHttpClient {
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.UnavailableForLegalReasons:
- return opKnownAlternativeFailure(resp, resp.status, codecForLegitimizationNeededResponse());
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForLegitimizationNeededResponse(),
+ );
default:
return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
@@ -223,9 +631,13 @@ export class TalerExchangeHttpClient {
* https://docs.taler.net/core/api-exchange.html#post--kyc-wallet
*
*/
- async checkKycStatus(account: ReserveAccount, requirementId: number, params: {
- timeout?: number,
- } = {}) {
+ async checkKycStatus(
+ account: ReserveAccount,
+ requirementId: number,
+ params: {
+ timeout?: number;
+ } = {},
+ ) {
const url = new URL(`kyc-check/${String(requirementId)}`, this.baseUrl);
if (params.timeout !== undefined) {
@@ -243,7 +655,11 @@ export class TalerExchangeHttpClient {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForAccountKycStatus());
case HttpStatusCode.Accepted:
- return opKnownAlternativeFailure(resp, resp.status, codecForAccountKycStatus());
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForAccountKycStatus(),
+ );
case HttpStatusCode.NoContent:
return opEmptySuccess(resp);
case HttpStatusCode.Forbidden:
@@ -258,12 +674,16 @@ export class TalerExchangeHttpClient {
}
/**
- * https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN
- *
- */
- async checkKycInfo(token: AccessToken, known: KycRequirementInformationId[], params: {
- timeout?: number,
- } = {}) {
+ * https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN
+ *
+ */
+ async checkKycInfo(
+ token: AccessToken,
+ known: KycRequirementInformationId[],
+ params: {
+ timeout?: number;
+ } = {},
+ ) {
const url = new URL(`kyc-info/${token}`, this.baseUrl);
if (params.timeout !== undefined) {
@@ -273,15 +693,19 @@ export class TalerExchangeHttpClient {
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
- "If-None-Match": known.length ? known.join(",") : undefined
- }
+ "If-None-Match": known.length ? known.join(",") : undefined,
+ },
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForKycProcessClientInformation());
case HttpStatusCode.NoContent:
- return opKnownAlternativeFailure(resp, HttpStatusCode.NoContent, codecForEmptyObject());
+ return opKnownAlternativeFailure(
+ resp,
+ HttpStatusCode.NoContent,
+ codecForEmptyObject(),
+ );
case HttpStatusCode.NotModified:
return opKnownHttpFailure(resp.status, resp);
default:
@@ -289,7 +713,6 @@ export class TalerExchangeHttpClient {
}
}
-
/**
* https://docs.taler.net/core/api-exchange.html#post--kyc-upload-$ID
*
@@ -320,13 +743,15 @@ export class TalerExchangeHttpClient {
* https://docs.taler.net/core/api-exchange.html#post--kyc-start-$ID
*
*/
- async startKycProcess(requirement: KycRequirementInformationId, body: object = {}) {
+ async startExternalKycProcess(
+ requirement: KycRequirementInformationId,
+ body: object = {},
+ ) {
const url = new URL(`kyc-start/${requirement}`, this.baseUrl);
-
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
- body
+ body,
});
switch (resp.status) {
@@ -343,119 +768,17 @@ export class TalerExchangeHttpClient {
}
}
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--kyc-proof-$PROVIDER_NAME?state=$H_PAYTO
+ *
+ */
+ async completeExternalKycProcess(provider: string, state: string) {}
+
//
// AML operations
//
/**
- * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions-$STATE
- *
- */
- // async getDecisionsByState(
- // auth: OfficerAccount,
- // state: TalerExchangeApi.AmlState,
- // pagination?: PaginationParams,
- // ) {
- // const url = new URL(
- // `aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`,
- // this.baseUrl,
- // );
- // addPaginationParams(url, pagination);
-
- // const resp = await this.httpLib.fetch(url.href, {
- // method: "GET",
- // headers: {
- // "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey),
- // },
- // });
-
- // switch (resp.status) {
- // case HttpStatusCode.Ok:
- // return opSuccessFromHttp(resp, codecForAmlRecords());
- // case HttpStatusCode.NoContent:
- // return opFixedSuccess({ records: [] });
- // //this should be unauthorized
- // case HttpStatusCode.Forbidden:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.Unauthorized:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.NotFound:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.Conflict:
- // return opKnownHttpFailure(resp.status, resp);
- // default:
- // return opUnknownFailure(resp, await readTalerErrorResponse(resp));
- // }
- // }
-
- // /**
- // * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO
- // *
- // */
- // async getDecisionDetails(auth: OfficerAccount, account: string) {
- // const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl);
-
- // const resp = await this.httpLib.fetch(url.href, {
- // method: "GET",
- // headers: {
- // "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey),
- // },
- // });
-
- // switch (resp.status) {
- // case HttpStatusCode.Ok:
- // return opSuccessFromHttp(resp, codecForAmlDecisionDetails());
- // case HttpStatusCode.NoContent:
- // return opFixedSuccess({ aml_history: [], kyc_attributes: [] });
- // //this should be unauthorized
- // case HttpStatusCode.Forbidden:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.Unauthorized:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.NotFound:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.Conflict:
- // return opKnownHttpFailure(resp.status, resp);
- // default:
- // return opUnknownFailure(resp, await readTalerErrorResponse(resp));
- // }
- // }
-
- // /**
- // * https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision
- // *
- // */
- // async addDecisionDetails(
- // auth: OfficerAccount,
- // decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">,
- // ) {
- // const url = new URL(`aml/${auth.id}/decision`, this.baseUrl);
-
- // const body = buildDecisionSignature(auth.signingKey, decision);
- // const resp = await this.httpLib.fetch(url.href, {
- // method: "POST",
- // body,
- // });
-
- // switch (resp.status) {
- // case HttpStatusCode.NoContent:
- // return opEmptySuccess(resp);
- // //FIXME: this should be unauthorized
- // case HttpStatusCode.Forbidden:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.Unauthorized:
- // return opKnownHttpFailure(resp.status, resp);
- // //FIXME: this two need to be split by error code
- // case HttpStatusCode.NotFound:
- // return opKnownHttpFailure(resp.status, resp);
- // case HttpStatusCode.Conflict:
- // return opKnownHttpFailure(resp.status, resp);
- // default:
- // return opUnknownFailure(resp, await readTalerErrorResponse(resp));
- // }
- // }
-
- /**
* https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures
*
*/
@@ -481,26 +804,23 @@ export class TalerExchangeHttpClient {
* https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures
*
*/
- async getAmlKycStatistics(auth: OfficerAccount, name: string, filter: {
- since?: Date
- until?: Date
- } = {}) {
+ async getAmlKycStatistics(
+ auth: OfficerAccount,
+ name: string,
+ filter: {
+ since?: Date;
+ until?: Date;
+ } = {},
+ ) {
const url = new URL(`aml/${auth.id}/kyc-statistics/${name}`, this.baseUrl);
if (filter.since !== undefined) {
- url.searchParams.set(
- "start_date",
- String(filter.since.getTime())
- );
+ url.searchParams.set("start_date", String(filter.since.getTime()));
}
if (filter.until !== undefined) {
- url.searchParams.set(
- "end_date",
- String(filter.until.getTime())
- );
+ url.searchParams.set("end_date", String(filter.until.getTime()));
}
-
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
@@ -519,11 +839,14 @@ export class TalerExchangeHttpClient {
* https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions
*
*/
- async getAmlDecisions(auth: OfficerAccount, params: PaginationParams & {
- account?: string,
- active?: boolean,
- investigation?: boolean,
- } = {}) {
+ async getAmlDecisions(
+ auth: OfficerAccount,
+ params: PaginationParams & {
+ account?: string;
+ active?: boolean;
+ investigation?: boolean;
+ } = {},
+ ) {
const url = new URL(`aml/${auth.id}/decisions`, this.baseUrl);
addMerchantPaginationParams(url, params);
@@ -534,7 +857,10 @@ export class TalerExchangeHttpClient {
url.searchParams.set("active", params.active ? "YES" : "NO");
}
if (params.investigation !== undefined) {
- url.searchParams.set("investigation", params.investigation ? "YES" : "NO");
+ url.searchParams.set(
+ "investigation",
+ params.investigation ? "YES" : "NO",
+ );
}
const resp = await this.httpLib.fetch(url.href, {
@@ -564,7 +890,11 @@ export class TalerExchangeHttpClient {
* https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO
*
*/
- async getAmlAttributesForAccount(auth: OfficerAccount, account: string, params: PaginationParams = {}) {
+ async getAmlAttributesForAccount(
+ auth: OfficerAccount,
+ account: string,
+ params: PaginationParams = {},
+ ) {
const url = new URL(`aml/${auth.id}/attributes/${account}`, this.baseUrl);
addMerchantPaginationParams(url, params);
@@ -591,12 +921,14 @@ export class TalerExchangeHttpClient {
}
}
-
/**
* https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO
*
*/
- async makeAmlDesicion(auth: OfficerAccount, decision: Omit<AmlDecisionRequest, "officer_sig">) {
+ async makeAmlDesicion(
+ auth: OfficerAccount,
+ decision: Omit<AmlDecisionRequest, "officer_sig">,
+ ) {
const url = new URL(`aml/${auth.id}/decision`, this.baseUrl);
const body = buildAMLDecisionSignature(auth.signingKey, decision);
@@ -622,20 +954,57 @@ export class TalerExchangeHttpClient {
}
}
+ // RESERVE control
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-open
+ *
+ */
+ async reserveOpen(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#get--reserves-attest-$RESERVE_PUB
+ *
+ */
+ async getReserveAttributes(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--reserves-attest-$RESERVE_PUB
+ *
+ */
+ async signReserveAttributes(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-close
+ *
+ */
+ async closeReserve(): Promise<never> {
+ throw Error("not yet implemented");
+ }
+
+ /**
+ * https://docs.taler.net/core/api-exchange.html#delete--reserves-$RESERVE_PUB
+ *
+ */
+ async deleteReserve(): Promise<never> {
+ throw Error("not yet implemented");
+ }
}
function buildKYCQuerySignature(key: SigningKey): string {
- const sigBlob = buildSigPS(
- TalerSignaturePurpose.AML_QUERY,
- ).build();
+ const sigBlob = buildSigPS(TalerSignaturePurpose.AML_QUERY).build();
return encodeCrock(eddsaSign(sigBlob, key));
}
function buildAMLQuerySignature(key: SigningKey): string {
- const sigBlob = buildSigPS(
- TalerSignaturePurpose.AML_QUERY,
- ).build();
+ const sigBlob = buildSigPS(TalerSignaturePurpose.AML_QUERY).build();
return encodeCrock(eddsaSign(sigBlob, key));
}
diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts
index 0cd722239..e9c80e045 100644
--- a/packages/taler-util/src/types-taler-exchange.ts
+++ b/packages/taler-util/src/types-taler-exchange.ts
@@ -573,11 +573,6 @@ export interface DenomGroupCommon {
// Fee charged by the exchange for refunding a coin of this denomination.
fee_refund: AmountString;
-
- // XOR of all the SHA-512 hash values of the denominations' public keys
- // in this group. Note that for hashing, the binary format of the
- // public keys is used, and not their base32 encoding.
- hash: HashCodeString;
}
export interface DenomCommon {
@@ -933,18 +928,18 @@ export namespace DenomKeyType {
}
}
-export interface RsaBlindedDenominationSignature {
- cipher: DenomKeyType.Rsa;
- blinded_rsa_signature: string;
-}
+// export interface RsaBlindedDenominationSignature {
+// cipher: DenomKeyType.Rsa;
+// blinded_rsa_signature: string;
+// }
-export interface CSBlindedDenominationSignature {
- cipher: DenomKeyType.ClauseSchnorr;
-}
+// export interface CSBlindedDenominationSignature {
+// cipher: DenomKeyType.ClauseSchnorr;
+// }
-export type BlindedDenominationSignature =
- | RsaBlindedDenominationSignature
- | CSBlindedDenominationSignature;
+// export type BlindedDenominationSignature =
+// | RsaBlindedDenominationSignature
+// | CSBlindedDenominationSignature;
export const codecForRsaBlindedDenominationSignature = () =>
buildCodecForObject<RsaBlindedDenominationSignature>()
@@ -1952,6 +1947,8 @@ export enum AmlState {
pending = 1,
frozen = 2,
}
+type Float = number;
+
export interface ExchangeKeysResponse {
// libtool-style representation of the Exchange protocol version, see
// https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
@@ -1969,100 +1966,95 @@ export interface ExchangeKeysResponse {
*/
// How wallets should render this currency.
- // currency_specification: CurrencySpecification;
-
- // // Absolute cost offset for the STEFAN curve used
- // // to (over) approximate fees payable by amount.
- // stefan_abs: AmountString;
-
- // // Factor to multiply the logarithm of the amount
- // // with to (over) approximate fees payable by amount.
- // // Note that the total to be paid is first to be
- // // divided by the smallest denomination to obtain
- // // the value that the logarithm is to be taken of.
- // stefan_log: AmountString;
-
- // // Linear cost factor for the STEFAN curve used
- // // to (over) approximate fees payable by amount.
- // //
- // // Note that this is a scalar, as it is multiplied
- // // with the actual amount.
- // stefan_lin: Float;
-
- // // Type of the asset. "fiat", "crypto", "regional"
- // // or "stock". Wallets should adjust their UI/UX
- // // based on this value.
- // asset_type: string;
-
- // // Array of wire accounts operated by the exchange for
- // // incoming wire transfers.
- // accounts: WireAccount[];
-
- // // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank")
- // // to wire fees.
- // wire_fees: { method: AggregateTransferFee[] };
-
- // // List of exchanges that this exchange is partnering
- // // with to enable wallet-to-wallet transfers.
- // wads: ExchangePartner[];
-
- // // Set to true if this exchange allows the use
- // // of reserves for rewards.
- // // @deprecated in protocol v18.
- // rewards_allowed: false;
-
- // // EdDSA master public key of the exchange, used to sign entries
- // // in denoms and signkeys.
- // master_public_key: EddsaPublicKey;
-
- // // Relative duration until inactive reserves are closed;
- // // not signed (!), can change without notice.
- // reserve_closing_delay: RelativeTime;
-
- // // Threshold amounts beyond which wallet should
- // // trigger the KYC process of the issuing
- // // exchange. Optional option, if not given there is no limit.
- // // Currency must match currency.
- // wallet_balance_limit_without_kyc?: AmountString[];
-
- // // Denominations offered by this exchange
- // denominations: DenomGroup[];
-
- // // Compact EdDSA signature (binary-only) over the
- // // contatentation of all of the master_sigs (in reverse
- // // chronological order by group) in the arrays under
- // // "denominations". Signature of TALER_ExchangeKeySetPS
- // exchange_sig: EddsaSignature;
-
- // // Public EdDSA key of the exchange that was used to generate the signature.
- // // Should match one of the exchange's signing keys from signkeys. It is given
- // // explicitly as the client might otherwise be confused by clock skew as to
- // // which signing key was used for the exchange_sig.
- // exchange_pub: EddsaPublicKey;
-
- // // Denominations for which the exchange currently offers/requests recoup.
- // recoup: Recoup[];
-
- // // Array of globally applicable fees by time range.
- // global_fees: GlobalFees[];
-
- // // The date when the denomination keys were last updated.
- // list_issue_date: Timestamp;
-
- // // Auditors of the exchange.
- // auditors: AuditorKeys[];
-
- // // The exchange's signing keys.
- // signkeys: SignKey[];
-
- // // Optional field with a dictionary of (name, object) pairs defining the
- // // supported and enabled extensions, such as age_restriction.
- // extensions?: { name: ExtensionManifest };
-
- // // Signature by the exchange master key of the SHA-256 hash of the
- // // normalized JSON-object of field extensions, if it was set.
- // // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS.
- // extensions_sig?: EddsaSignature;
+ currency_specification: CurrencySpecification;
+
+ // Absolute cost offset for the STEFAN curve used
+ // to (over) approximate fees payable by amount.
+ stefan_abs: AmountString;
+
+ // Factor to multiply the logarithm of the amount
+ // with to (over) approximate fees payable by amount.
+ // Note that the total to be paid is first to be
+ // divided by the smallest denomination to obtain
+ // the value that the logarithm is to be taken of.
+ stefan_log: AmountString;
+
+ // Linear cost factor for the STEFAN curve used
+ // to (over) approximate fees payable by amount.
+ //
+ // Note that this is a scalar, as it is multiplied
+ // with the actual amount.
+ stefan_lin: Float;
+
+ // Type of the asset. "fiat", "crypto", "regional"
+ // or "stock". Wallets should adjust their UI/UX
+ // based on this value.
+ asset_type: string;
+
+ // Array of wire accounts operated by the exchange for
+ // incoming wire transfers.
+ accounts: WireAccount[];
+
+ // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank")
+ // to wire fees.
+ wire_fees: { method: AggregateTransferFee[] };
+
+ // List of exchanges that this exchange is partnering
+ // with to enable wallet-to-wallet transfers.
+ wads: ExchangePartner[];
+
+ // EdDSA master public key of the exchange, used to sign entries
+ // in denoms and signkeys.
+ master_public_key: EddsaPublicKey;
+
+ // Relative duration until inactive reserves are closed;
+ // not signed (!), can change without notice.
+ reserve_closing_delay: RelativeTime;
+
+ // Threshold amounts beyond which wallet should
+ // trigger the KYC process of the issuing
+ // exchange. Optional option, if not given there is no limit.
+ // Currency must match currency.
+ wallet_balance_limit_without_kyc?: AmountString[];
+
+ // Denominations offered by this exchange
+ denominations: DenomGroup[];
+
+ // Compact EdDSA signature (binary-only) over the
+ // contatentation of all of the master_sigs (in reverse
+ // chronological order by group) in the arrays under
+ // "denominations". Signature of TALER_ExchangeKeySetPS
+ exchange_sig: EddsaSignature;
+
+ // Public EdDSA key of the exchange that was used to generate the signature.
+ // Should match one of the exchange's signing keys from signkeys. It is given
+ // explicitly as the client might otherwise be confused by clock skew as to
+ // which signing key was used for the exchange_sig.
+ exchange_pub: EddsaPublicKey;
+
+ // Denominations for which the exchange currently offers/requests recoup.
+ recoup: Recoup[];
+
+ // Array of globally applicable fees by time range.
+ global_fees: GlobalFees[];
+
+ // The date when the denomination keys were last updated.
+ list_issue_date: Timestamp;
+
+ // Auditors of the exchange.
+ auditors: AuditorKeys[];
+
+ // The exchange's signing keys.
+ signkeys: SignKey[];
+
+ // Optional field with a dictionary of (name, object) pairs defining the
+ // supported and enabled extensions, such as age_restriction.
+ extensions?: { name: ExtensionManifest };
+
+ // Signature by the exchange master key of the SHA-256 hash of the
+ // normalized JSON-object of field extensions, if it was set.
+ // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS.
+ extensions_sig?: EddsaSignature;
}
interface ExtensionManifest {
@@ -2273,6 +2265,28 @@ export const codecForExchangeKeys = (): Codec<ExchangeKeysResponse> =>
.property("version", codecForString())
.property("base_url", codecForString())
.property("currency", codecForString())
+ .property("accounts", codecForAny())
+ .property("asset_type", codecForAny())
+ .property("auditors", codecForAny())
+ .property("currency_specification", codecForAny())
+ .property("denominations", codecForAny())
+ .property("exchange_pub", codecForAny())
+ .property("exchange_sig", codecForAny())
+ .property("extensions", codecForAny())
+ .property("extensions_sig", codecForAny())
+ .property("global_fees", codecForAny())
+ .property("list_issue_date", codecForAny())
+ .property("master_public_key", codecForAny())
+ .property("recoup", codecForAny())
+ .property("reserve_closing_delay", codecForAny())
+ .property("signkeys", codecForAny())
+ .property("stefan_abs", codecForAny())
+ .property("stefan_lin", codecForAny())
+ .property("stefan_log", codecForAny())
+ .property("wads", codecForAny())
+ .property("wallet_balance_limit_without_kyc", codecForAny())
+ .property("wire_fees", codecForAny())
+
.build("TalerExchangeApi.ExchangeKeysResponse");
export const codecForEventCounter = (): Codec<EventCounter> =>
@@ -2493,3 +2507,325 @@ export const codecForKycProcessStartInformation =
buildCodecForObject<KycProcessStartInformation>()
.property("redirect_url", codecForURLString())
.build("TalerExchangeApi.KycProcessStartInformation");
+
+export interface BatchWithdrawResponse {
+ // Array of blinded signatures, in the same order as was
+ // given in the request.
+ ev_sigs: WithdrawResponse[];
+}
+export interface WithdrawResponse {
+ // The blinded signature over the 'coin_ev', affirms the coin's
+ // validity after unblinding.
+ ev_sig: BlindedDenominationSignature;
+}
+export type BlindedDenominationSignature =
+ | RsaBlindedDenominationSignature
+ | CSBlindedDenominationSignature;
+
+export interface RsaBlindedDenominationSignature {
+ cipher: DenomKeyType.Rsa;
+
+ // (blinded) RSA signature
+ blinded_rsa_signature: BlindedRsaSignature;
+}
+
+export interface CSBlindedDenominationSignature {
+ cipher: DenomKeyType.ClauseSchnorr;
+
+ // Signer chosen bit value, 0 or 1, used
+ // in Clause Blind Schnorr to make the
+ // ROS problem harder.
+ b: Integer;
+
+ // Blinded scalar calculated from c_b.
+ s: Cs25519Scalar;
+}
+
+type BlindedRsaSignature = string;
+type Cs25519Scalar = string;
+type HashCode = string;
+type EddsaSignature = string;
+type EddsaPublicKey = string;
+type Amount = AmountString;
+type Base32 = string;
+
+export interface WithdrawError {
+ // Text describing the error.
+ hint: string;
+
+ // Detailed error code.
+ code: Integer;
+
+ // Amount left in the reserve.
+ balance: AmountString;
+
+ // History of the reserve's activity, in the same format
+ // as returned by /reserve/$RID/history.
+ history: TransactionHistoryItem[];
+}
+export type TransactionHistoryItem =
+ | AccountSetupTransaction
+ | ReserveWithdrawTransaction
+ | ReserveAgeWithdrawTransaction
+ | ReserveCreditTransaction
+ | ReserveClosingTransaction
+ | ReserveOpenRequestTransaction
+ | ReserveCloseRequestTransaction
+ | PurseMergeTransaction;
+
+enum TransactionHistoryType {
+ setup = "SETUP",
+ withdraw = "WITHDRAW",
+ ageWithdraw = "AGEWITHDRAW",
+ credit = "CREDIT",
+ closing = "CLOSING",
+ open = "OPEN",
+ close = "CLOSE",
+ merge = "MERGE",
+}
+interface AccountSetupTransaction {
+ type: TransactionHistoryType.setup;
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // KYC fee agreed to by the reserve owner.
+ kyc_fee: AmountString;
+
+ // Time when the KYC was triggered.
+ kyc_timestamp: Timestamp;
+
+ // Hash of the wire details of the account.
+ // Note that this hash is unsalted and potentially
+ // private (as it could be inverted), hence access
+ // to this endpoint must be authorized using the
+ // private key of the reserve.
+ h_wire: HashCode;
+
+ // Signature created with the reserve's private key.
+ // Must be of purpose TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST over
+ // a TALER_AccountSetupRequestSignaturePS.
+ reserve_sig: EddsaSignature;
+}
+interface ReserveWithdrawTransaction {
+ type: "WITHDRAW";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // Amount withdrawn.
+ amount: Amount;
+
+ // Hash of the denomination public key of the coin.
+ h_denom_pub: HashCode;
+
+ // Hash of the blinded coin to be signed.
+ h_coin_envelope: HashCode;
+
+ // Signature over a TALER_WithdrawRequestPS
+ // with purpose TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW
+ // created with the reserve's private key.
+ reserve_sig: EddsaSignature;
+
+ // Fee that is charged for withdraw.
+ withdraw_fee: Amount;
+}
+interface ReserveAgeWithdrawTransaction {
+ type: "AGEWITHDRAW";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // Total Amount withdrawn.
+ amount: Amount;
+
+ // Commitment of all n*kappa blinded coins.
+ h_commitment: HashCode;
+
+ // Signature over a TALER_AgeWithdrawRequestPS
+ // with purpose TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW
+ // created with the reserve's private key.
+ reserve_sig: EddsaSignature;
+
+ // Fee that is charged for withdraw.
+ withdraw_fee: Amount;
+}
+interface ReserveCreditTransaction {
+ type: "CREDIT";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // Amount deposited.
+ amount: Amount;
+
+ // Sender account payto:// URL.
+ sender_account_url: string;
+
+ // Opaque identifier internal to the exchange that
+ // uniquely identifies the wire transfer that credited the reserve.
+ wire_reference: Integer;
+
+ // Timestamp of the incoming wire transfer.
+ timestamp: Timestamp;
+}
+interface ReserveClosingTransaction {
+ type: "CLOSING";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // Closing balance.
+ amount: Amount;
+
+ // Closing fee charged by the exchange.
+ closing_fee: Amount;
+
+ // Wire transfer subject.
+ wtid: Base32;
+
+ // payto:// URI of the wire account into which the funds were returned to.
+ receiver_account_details: string;
+
+ // This is a signature over a
+ // struct TALER_ReserveCloseConfirmationPS with purpose
+ // TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED.
+ exchange_sig: EddsaSignature;
+
+ // Public key used to create 'exchange_sig'.
+ exchange_pub: EddsaPublicKey;
+
+ // Time when the reserve was closed.
+ timestamp: Timestamp;
+}
+interface ReserveOpenRequestTransaction {
+ type: "OPEN";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // Open fee paid from the reserve.
+ open_fee: Amount;
+
+ // This is a signature over
+ // a struct TALER_ReserveOpenPS with purpose
+ // TALER_SIGNATURE_WALLET_RESERVE_OPEN.
+ reserve_sig: EddsaSignature;
+
+ // Timestamp of the open request.
+ request_timestamp: Timestamp;
+
+ // Requested expiration.
+ requested_expiration: Timestamp;
+
+ // Requested number of free open purses.
+ requested_min_purses: Integer;
+}
+interface ReserveCloseRequestTransaction {
+ type: "CLOSE";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // This is a signature over
+ // a struct TALER_ReserveClosePS with purpose
+ // TALER_SIGNATURE_WALLET_RESERVE_CLOSE.
+ reserve_sig: EddsaSignature;
+
+ // Target account payto://, optional.
+ h_payto?: PaytoHash;
+
+ // Timestamp of the close request.
+ request_timestamp: Timestamp;
+}
+interface PurseMergeTransaction {
+ type: "MERGE";
+
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKey;
+
+ // Minimum age required for all coins deposited into the purse.
+ min_age: Integer;
+
+ // Number that identifies who created the purse
+ // and how it was paid for.
+ flags: Integer;
+
+ // Purse public key.
+ purse_pub: EddsaPublicKey;
+
+ // EdDSA signature of the account/reserve affirming the merge
+ // over a TALER_AccountMergeSignaturePS.
+ // Must be of purpose TALER_SIGNATURE_ACCOUNT_MERGE
+ reserve_sig: EddsaSignature;
+
+ // Client-side timestamp of when the merge request was made.
+ merge_timestamp: Timestamp;
+
+ // Indicative time by which the purse should expire
+ // if it has not been merged into an account. At this
+ // point, all of the deposits made should be
+ // auto-refunded.
+ purse_expiration: Timestamp;
+
+ // Purse fee the reserve owner paid for the purse creation.
+ purse_fee: Amount;
+
+ // Total amount merged into the reserve.
+ // (excludes fees).
+ amount: Amount;
+
+ // True if the purse was actually merged.
+ // If false, only the purse_fee has an impact
+ // on the reserve balance!
+ merged: boolean;
+}
+
+interface DenominationExpiredMessage {
+ // Taler error code. Note that beyond
+ // expiration this message format is also
+ // used if the key is not yet valid, or
+ // has been revoked.
+ code: number;
+
+ // Signature by the exchange over a
+ // TALER_DenominationExpiredAffirmationPS.
+ // Must have purpose TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED.
+ exchange_sig: EddsaSignature;
+
+ // Public key of the exchange used to create
+ // the 'exchange_sig.
+ exchange_pub: EddsaPublicKey;
+
+ // Hash of the denomination public key that is unknown.
+ h_denom_pub: HashCode;
+
+ // When was the signature created.
+ timestamp: Timestamp;
+
+ // What kind of operation was requested that now
+ // failed?
+ oper: string;
+}