gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (351096408 -> 8965ef07c)


From: gnunet
Subject: [taler-wallet-core] branch master updated (351096408 -> 8965ef07c)
Date: Mon, 06 Jan 2025 23:56:57 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 351096408 handle new measure error code
     new 292d7bb92 added payto taler
     new 8e02b885e also query /keys since it have some useful information and 
if it's no available then it means problems
     new 318d7ba24 several changes related UI
     new 8965ef07c handle unsupported payto://taler

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/aml-backoffice-ui/src/Routing.tsx         |   6 +-
 packages/aml-backoffice-ui/src/forms/ganaForms.ts  |   5 +-
 .../aml-backoffice-ui/src/pages/CaseDetails.tsx    | 148 ++++++++++-----
 .../aml-backoffice-ui/src/pages/CaseUpdate.tsx     |   2 +-
 packages/aml-backoffice-ui/src/pages/Cases.tsx     |  89 +++++++--
 packages/aml-backoffice-ui/src/pages/Search.tsx    | 198 +++++++++++++++++++--
 .../src/pages/ShowConsolidated.tsx                 |  47 +++--
 packages/aml-backoffice-ui/src/stories.test.ts     |   4 +-
 packages/aml-backoffice-ui/src/stories.tsx         |   3 +-
 .../bank-ui/src/pages/OperationState/views.tsx     |   4 +
 .../bank-ui/src/pages/PaytoWireTransferForm.tsx    |  18 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |   4 +
 packages/kyc-ui/src/pages/TriggerKyc.tsx           |  28 +--
 .../src/components/modal/index.tsx                 |   8 +-
 .../src/paths/instance/accounts/list/Table.tsx     |   3 +-
 packages/taler-util/src/payto.ts                   |  45 ++++-
 packages/taler-util/src/taleruri.ts                |   4 +
 .../src/components/BankDetailsByPaytoType.tsx      |   8 +-
 .../src/wallet/DestinationSelection/views.tsx      |   3 +
 packages/web-util/src/context/exchange-api.ts      |  55 ++++--
 20 files changed, 537 insertions(+), 145 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/Routing.tsx 
b/packages/aml-backoffice-ui/src/Routing.tsx
index d5669fc2e..a48cc373a 100644
--- a/packages/aml-backoffice-ui/src/Routing.tsx
+++ b/packages/aml-backoffice-ui/src/Routing.tsx
@@ -163,10 +163,12 @@ function PrivateRouting(): VNode {
       return <SelectForm account={location.values.cid} />;
     }
     case "investigation": {
-      return <CasesUnderInvestigation />;
+      return (
+        <CasesUnderInvestigation caseByIdRoute={privatePages.caseDetails} />
+      );
     }
     case "active": {
-      return <Cases />;
+      return <Cases caseByIdRoute={privatePages.caseDetails} />;
     }
     case "search": {
       return <Search />;
diff --git a/packages/aml-backoffice-ui/src/forms/ganaForms.ts 
b/packages/aml-backoffice-ui/src/forms/ganaForms.ts
index 8bce4d9fe..4d43f55e7 100644
--- a/packages/aml-backoffice-ui/src/forms/ganaForms.ts
+++ b/packages/aml-backoffice-ui/src/forms/ganaForms.ts
@@ -230,10 +230,7 @@ function convertGanaJsonToDoubleColumnFormSection(
   }, {} as FieldsBySection);
 
   Object.values(sections).forEach((sec) => {
-    sec.sort((a, b) => {
-      console.log("a", a.order, b.order);
-      return (a.order ?? 0) - (b.order ?? 0);
-    });
+    sec.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
   });
   return sections;
 }
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx 
b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index d9b007d1e..5cf090816 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -39,6 +39,7 @@ import {
   Attention,
   Button,
   convertUiField,
+  CopyButton,
   DefaultForm,
   FormConfiguration,
   FormMetadata,
@@ -178,7 +179,7 @@ export function getEventsFromAmlHistory(
   const ke = events.map((event) => {
     return {
       type: "kyc-collection",
-      title: i18n.str`collection`,
+      title: i18n.str`User filled a form`,
       when: AbsoluteTime.fromProtocolTimestamp(event.collection_time),
       values: !event.attributes ? {} : event.attributes,
       provider: event.provider_name,
@@ -274,7 +275,7 @@ export function CaseDetails({
                 expiration_time: AbsoluteTime.toProtocolTimestamp(
                   AbsoluteTime.never(),
                 ),
-                rules: FREEZE_RULES(config.currency),
+                rules: FREEZE_RULES(config.config.currency),
               },
             },
             askInformation: false,
@@ -298,11 +299,10 @@ export function CaseDetails({
     <div class="min-w-60">
       <header class="flex items-center justify-between border-b border-white/5 
px-4 py-4 sm:px-6 sm:py-6 lg:px-8">
         <h1 class="text-base font-semibold leading-7 text-black">
-          <i18n.Translate>
-            Case history for account{" "}
-            <span title={account}>{account.substring(0, 16)}...</span>
-          </i18n.Translate>
+          <i18n.Translate>Case history for account: </i18n.Translate>
         </h1>
+        <div>{account}</div>
+        <CopyButton class="" getContent={() => account} />
       </header>
 
       {!activeDecision || !activeDecision.to_investigate ? undefined : (
@@ -331,7 +331,7 @@ export function CaseDetails({
                   expiration_time: AbsoluteTime.toProtocolTimestamp(
                     AbsoluteTime.never(),
                   ),
-                  rules: FREEZE_RULES(config.currency),
+                  rules: FREEZE_RULES(config.config.currency),
                   successor_measure: "verboten",
                 },
               },
@@ -358,7 +358,7 @@ export function CaseDetails({
                   expiration_time: AbsoluteTime.toProtocolTimestamp(
                     AbsoluteTime.never(),
                   ),
-                  rules: THRESHOLD_100_HOUR(config.currency),
+                  rules: THRESHOLD_100_HOUR(config.config.currency),
                   successor_measure: "verboten",
                 },
               },
@@ -385,7 +385,7 @@ export function CaseDetails({
                   expiration_time: AbsoluteTime.toProtocolTimestamp(
                     AbsoluteTime.never(),
                   ),
-                  rules: THRESHOLD_2000_WEEK(config.currency),
+                  rules: THRESHOLD_2000_WEEK(config.config.currency),
                   successor_measure: "verboten",
                 },
               },
@@ -415,7 +415,7 @@ export function CaseDetails({
                   expiration_time: AbsoluteTime.toProtocolTimestamp(
                     AbsoluteTime.never(),
                   ),
-                  rules: FREEZE_RULES(config.currency),
+                  rules: FREEZE_RULES(config.config.currency),
                 },
               },
               askInformation: true,
@@ -471,20 +471,8 @@ export function CaseDetails({
       ) : (
         <ShowTimeline
           history={events}
-          onSelect={(e) => {
-            switch (e.type) {
-              case "aml-form": {
-                // const { justification, metadata } = e;
-                // setShowForm({ justification, metadata });
-                break;
-              }
-              case "kyc-collection":
-              case "kyc-expiration": {
-                setSelected(e.when);
-                break;
-              }
-              case "aml-form-error":
-            }
+          onSelect={(time) => {
+            setSelected(time);
           }}
         />
       )}
@@ -724,6 +712,7 @@ function SubmitNewDecision({
       />
 
       <ShowDecisionLimitInfo
+        fixed
         since={AbsoluteTime.fromProtocolTimestamp(
           decision.request.decision_time,
         )}
@@ -855,12 +844,14 @@ function ShowDecisionLimitInfo({
   until,
   startOpen,
   justification,
+  fixed,
 }: {
   since: AbsoluteTime;
   until: AbsoluteTime;
   justification?: string;
   ruleSet: LegitimizationRuleSet;
   startOpen?: boolean;
+  fixed?: boolean;
 }): VNode {
   const { i18n } = useTranslationContext();
   const { config } = useExchangeApiContext();
@@ -869,32 +860,74 @@ function ShowDecisionLimitInfo({
   function Header() {
     return (
       <div
-        class="p-4 relative bg-gray-50 flex justify-between cursor-pointer"
-        onClick={() => setOpened((o) => !o)}
+        data-fixed={!!fixed}
+        class="p-4 relative bg-gray-200 flex justify-between 
data-[fixed=false]:cursor-pointer"
+        onClick={() => {
+          if (!fixed) {
+            setOpened((o) => !o);
+          }
+        }}
       >
         <div class="flex min-w-0 gap-x-4">
           <div class="flex rounded-md  shadow-sm border-0 ring-1 ring-inset 
ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
-            <div class="pointer-events-none bg-gray-200 inset-y-0 flex 
items-center px-3">
+            <div class="pointer-events-none bg-gray-300 inset-y-0 flex 
items-center px-3">
               <i18n.Translate>Since</i18n.Translate>
             </div>
-            <div class="p-2  disabled:bg-gray-200 text-right rounded-md 
rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6">
+            <div class="p-2  bg-gray-50 text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-50  sm:text-sm sm:leading-6">
               <Time format="dd/MM/yyyy HH:mm:ss" timestamp={since} />
             </div>
           </div>
         </div>
         <div class="flex shrink-0 items-center gap-x-4">
           <div class="flex rounded-md  shadow-sm border-0 ring-1 ring-inset 
ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
-            <div class="pointer-events-none bg-gray-200 inset-y-0 flex 
items-center px-3">
+            <div class="pointer-events-none bg-gray-300 inset-y-0 flex 
items-center px-3">
               {AbsoluteTime.isExpired(until) ? (
                 <i18n.Translate>Expired</i18n.Translate>
               ) : (
                 <i18n.Translate>Expires</i18n.Translate>
               )}
             </div>
-            <div class="p-2  disabled:bg-gray-200 text-right rounded-md 
rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6">
+            <div class="p-2  bg-gray-50 text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-50  sm:text-sm sm:leading-6">
               <Time format="dd/MM/yyyy HH:mm:ss" timestamp={until} />
             </div>
           </div>
+          {fixed ? (
+            <Fragment />
+          ) : (
+            <div class="rounded-full bg-gray-50 p-2">
+              <svg
+                xmlns="http://www.w3.org/2000/svg";
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke-width="1.5"
+                stroke="currentColor"
+                class="size-6 w-6 h-6"
+              >
+                {opened ? (
+                  <path
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    d="m19.5 8.25-7.5 7.5-7.5-7.5"
+                  />
+                ) : (
+                  <path
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    d="m4.5 15.75 7.5-7.5 7.5 7.5"
+                  />
+                )}
+              </svg>
+              {/* <svg
+              xmlns="http://www.w3.org/2000/svg";
+              fill="none"
+              viewBox="0 0 24 24"
+              stroke-width="1.5"
+              stroke="currentColor"
+              class="size-6 w-6 h-6"
+              >
+              </svg> */}
+            </div>
+          )}
         </div>
       </div>
     );
@@ -902,7 +935,7 @@ function ShowDecisionLimitInfo({
 
   if (!opened) {
     return (
-      <div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl">
+      <div class="overflow-hidden border border-gray-800  rounded-xl">
         <Header />
       </div>
     );
@@ -912,7 +945,7 @@ function ShowDecisionLimitInfo({
   );
 
   return (
-    <div class="overflow-hidden ring-1 ring-gray-900/5 rounded-xl">
+    <div class="overflow-hidden border border-gray-800 rounded-xl">
       <Header />
       <div class="p-4 grid gap-y-4">
         {!justification ? undefined : (
@@ -948,7 +981,7 @@ function ShowDecisionLimitInfo({
               ) : (
                 <RenderAmount
                   value={Amounts.parseOrThrow(balanceLimit.threshold)}
-                  spec={config.currency_specification}
+                  spec={config.config.currency_specification}
                 />
               )}
             </div>
@@ -1008,7 +1041,7 @@ function ShowDecisionLimitInfo({
                       <td class=" relative whitespace-nowrap py-4 pl-3 pr-4 
text-right text-sm font-medium sm:pr-6 text-right">
                         <RenderAmount
                           value={Amounts.parseOrThrow(r.threshold)}
-                          spec={config.currency_specification}
+                          spec={config.config.currency_specification}
                         />
                       </td>
                     </tr>
@@ -1086,30 +1119,26 @@ function ShowTimeline({
   history,
   onSelect,
 }: {
-  onSelect: (e: AmlEvent) => void;
+  onSelect: (e: AbsoluteTime) => void;
   history: AmlEvent[];
 }): VNode {
+  const { i18n } = useTranslationContext();
   return (
     <div class="flow-root">
       <ul role="list">
         {history.map((e, idx) => {
-          const isLast = history.length - 1 === idx;
+          // const isLast = history.length - 1 === idx;
           return (
             <li
               key={idx}
               data-ok={e.type !== "aml-form-error"}
               class="hover:bg-gray-200 p-2 rounded 
data-[ok=true]:cursor-pointer"
               onClick={() => {
-                onSelect(e);
+                onSelect(e.when);
               }}
             >
-              <div class="relative pb-6">
-                {!isLast ? (
-                  <span
-                    class="absolute left-4 top-4 -ml-px h-full w-1 bg-gray-200"
-                    aria-hidden="true"
-                  ></span>
-                ) : undefined}
+              <div class="relative pb-3">
+                <span class="absolute left-3 top-5 -ml-px h-full w-1 
bg-gray-200"></span>
                 <div class="relative flex space-x-3">
                   {(() => {
                     switch (e.type) {
@@ -1192,6 +1221,32 @@ function ShowTimeline({
             </li>
           );
         })}
+        <li
+          class="hover:bg-gray-200 p-2 rounded data-[ok=true]:cursor-pointer"
+          onClick={() => {
+            onSelect(AbsoluteTime.now());
+          }}
+        >
+          <div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
+            <svg
+              xmlns="http://www.w3.org/2000/svg";
+              fill="none"
+              viewBox="0 0 24 24"
+              stroke-width="1.5"
+              stroke="currentColor"
+              class="size-6 w-6 h-6"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
+              />
+            </svg>
+            <p class="text-sm text-gray-900">
+              <i18n.Translate>Now </i18n.Translate>
+            </p>
+          </div>
+        </li>
       </ul>
     </div>
   );
@@ -1240,12 +1295,13 @@ function InputAmount(
             if (
               sep_pos !== -1 &&
               l - sep_pos - 1 >
-                config.currency_specification.num_fractional_input_digits
+                
config.config.currency_specification.num_fractional_input_digits
             ) {
               e.currentTarget.value = e.currentTarget.value.substring(
                 0,
                 sep_pos +
-                  config.currency_specification.num_fractional_input_digits +
+                  config.config.currency_specification
+                    .num_fractional_input_digits +
                   1,
               );
             }
diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx 
b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
index a268aaa89..1bd50141f 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
@@ -94,7 +94,7 @@ export function CaseUpdate({
   const initial: FormType = {
     when: AbsoluteTime.now(),
     state: TalerExchangeApi.AmlState.pending,
-    threshold: Amounts.zeroOfCurrency(config.currency),
+    threshold: Amounts.zeroOfCurrency(config.config.currency),
     comment: "",
   };
 
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx 
b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 0429d397d..07185e476 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -22,6 +22,7 @@ import {
 import {
   Attention,
   Loading,
+  RouteDefinition,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
@@ -33,18 +34,66 @@ import {
 import { privatePages } from "../Routing.js";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
 import { Officer } from "./Officer.js";
+import { useState } from "preact/hooks";
 
 type FormType = {
   // state: TalerExchangeApi.AmlState;
 };
 
+function JumpByIdForm({
+  caseByIdRoute,
+}: {
+  caseByIdRoute: RouteDefinition<{ cid: string }>;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const [account, setAccount] = useState<string>("");
+  return (
+    <form class="mt-5 sm:flex sm:items-center">
+      <div class="w-full sm:max-w-xs">
+        <input
+          type="email"
+          name="email"
+          id="email"
+          onChange={(e) => {
+            setAccount(e.currentTarget.value);
+          }}
+          aria-label="Email"
+          class="block w-full rounded-md bg-white px-3 py-1.5 text-base 
text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 
placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 
focus:outline-indigo-600 sm:text-sm/6"
+          placeholder={i18n.str`Search by ID`}
+        />
+      </div>
+      <a
+        href={caseByIdRoute.url({ cid: account })}
+        class="mt-3 inline-flex w-full items-center justify-center rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:ml-3 sm:mt-0 
sm:w-auto"
+      >
+        <svg
+          xmlns="http://www.w3.org/2000/svg";
+          fill="none"
+          viewBox="0 0 24 24"
+          stroke-width="1.5"
+          stroke="currentColor"
+          class="size-6 w-6 h-6"
+        >
+          <path
+            stroke-linecap="round"
+            stroke-linejoin="round"
+            d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
+          />
+        </svg>
+      </a>
+    </form>
+  );
+}
+
 export function CasesUI({
   records,
   onFirstPage,
   onNext,
   filtered,
+  caseByIdRoute,
 }: {
   filtered: boolean;
+  caseByIdRoute: RouteDefinition<{ cid: string }>;
   onFirstPage?: () => void;
   onNext?: () => void;
   records: TalerExchangeApi.AmlDecision[];
@@ -113,6 +162,8 @@ export function CasesUI({
             </p>
           </div>
         )}
+
+        <JumpByIdForm caseByIdRoute={caseByIdRoute} />
       </div>
       <div class="mt-8 flow-root">
         <div class="overflow-x-auto">
@@ -147,9 +198,9 @@ export function CasesUI({
                               href={privatePages.caseDetails.url({
                                 cid: r.h_payto,
                               })}
-                              class="text-indigo-600 hover:text-indigo-900"
+                              class="text-indigo-600 hover:text-indigo-900 
font-mono"
                             >
-                              {r.h_payto.substring(0, 16)}...
+                              {r.h_payto}
                             </a>
                           </div>
                         </td>
@@ -174,7 +225,11 @@ export function CasesUI({
   );
 }
 
-export function Cases() {
+export function Cases({
+  caseByIdRoute,
+}: {
+  caseByIdRoute: RouteDefinition<{ cid: string }>;
+}) {
   const list = useCurrentDecisions();
   const { i18n } = useTranslationContext();
 
@@ -234,6 +289,7 @@ export function Cases() {
     <CasesUI
       filtered={false}
       records={list.body}
+      caseByIdRoute={caseByIdRoute}
       onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
       onNext={list.isLastPage ? undefined : list.loadNext}
       // filter={stateFilter}
@@ -243,7 +299,11 @@ export function Cases() {
     />
   );
 }
-export function CasesUnderInvestigation() {
+export function CasesUnderInvestigation({
+  caseByIdRoute,
+}: {
+  caseByIdRoute: RouteDefinition<{ cid: string }>;
+}) {
   const list = useCurrentDecisionsUnderInvestigation();
   const { i18n } = useTranslationContext();
 
@@ -303,6 +363,7 @@ export function CasesUnderInvestigation() {
     <CasesUI
       filtered={true}
       records={list.body}
+      caseByIdRoute={caseByIdRoute}
       onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
       onNext={list.isLastPage ? undefined : list.loadNext}
       // filter={stateFilter}
@@ -370,14 +431,18 @@ export const HomeIcon = () => (
   </svg>
 );
 export const FormIcon = () => (
-<svg 
-xmlns="http://www.w3.org/2000/svg"; 
-viewBox="0 0 24 24" 
-fill="currentColor" 
-class="w-6 h-6"
->
-  <path fillRule="evenodd" d="M1.5 5.625c0-1.036.84-1.875 
1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875 
1.875H3.375A1.875 1.875 0 0 1 1.5 18.375V5.625ZM21 9.375A.375.375 0 0 0 20.625 
9h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 
.375-.375v-1.5Zm0 3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 
0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0 
3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1 [...]
-</svg>
+  <svg
+    xmlns="http://www.w3.org/2000/svg";
+    viewBox="0 0 24 24"
+    fill="currentColor"
+    class="w-6 h-6"
+  >
+    <path
+      fillRule="evenodd"
+      d="M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875 
1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 0 1 1.5 
18.375V5.625ZM21 9.375A.375.375 0 0 0 20.625 9h-7.5a.375.375 0 0 
0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0 
3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 
.207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0 3.75a.375.375 0 0 
0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375 [...]
+      clipRule="evenodd"
+    />
+  </svg>
 );
 
 export const SearchIcon = () => (
diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx 
b/packages/aml-backoffice-ui/src/pages/Search.tsx
index 635eb6582..9cb17255c 100644
--- a/packages/aml-backoffice-ui/src/pages/Search.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Search.tsx
@@ -18,6 +18,7 @@ import {
   assertUnreachable,
   buildPayto,
   encodeCrock,
+  getURLHostnamePortPath,
   hashNormalizedPaytoUri,
   HttpStatusCode,
   parsePaytoUri,
@@ -37,6 +38,7 @@ import {
   Time,
   UIFormElementConfig,
   UIHandlerId,
+  useExchangeApiContext,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
@@ -99,14 +101,26 @@ export function Search() {
         </div>
       </form>
 
-      {paytoForm.status.status !== "ok" ? undefined : paytoForm.status.result
-          .paytoType === "x-taler-bank" ? (
-        <XTalerBankForm onSearch={setPayto} />
-      ) : paytoForm.status.result.paytoType === "iban" ? (
-        <IbanForm onSearch={setPayto} />
-      ) : (
-        <GenericForm onSearch={setPayto} />
-      )}
+      {(function () {
+        if (paytoForm.status.status !== "ok") return undefined;
+        switch (paytoForm.status.result.paytoType) {
+          case "iban": {
+            return <IbanForm onSearch={setPayto} />;
+          }
+          case "generic": {
+            return <GenericForm onSearch={setPayto} />;
+          }
+          case "x-taler-bank": {
+            return <XTalerBankForm onSearch={setPayto} />;
+          }
+          case "wallet": {
+            return <WalletForm onSearch={setPayto} />;
+          }
+          default: {
+            assertUnreachable(paytoForm.status.result.paytoType);
+          }
+        }
+      })()}
       {!paytoUri ? undefined : <ShowResult payto={paytoUri} />}
     </div>
   );
@@ -378,6 +392,56 @@ function IbanForm({
     </form>
   );
 }
+function WalletForm({
+  onSearch,
+}: {
+  onSearch: (p: PaytoUri | undefined) => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const { config } = useExchangeApiContext();
+  const fields = walletFields(i18n);
+  const form = useFormState(
+    getShapeFromFields(fields),
+    {
+      exchange: getURLHostnamePortPath(config.keys.base_url),
+    },
+    createTalerPaytoValidator(i18n),
+  );
+  const paytoUri =
+    form.status.status === "fail"
+      ? undefined
+      : buildPayto(
+          "taler",
+          form.status.result.exchange,
+          form.status.result.reservePub,
+        );
+
+  return (
+    <form
+      class="space-y-6"
+      noValidate
+      onSubmit={(e) => {
+        e.preventDefault();
+      }}
+      autoCapitalize="none"
+      autoCorrect="off"
+    >
+      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3">
+        <RenderAllFieldsByUiConfig
+          fields={convertUiField(i18n, fields, form.handler, getConverterById)}
+        />
+      </div>
+      <button
+        disabled={form.status.status === "fail"}
+        class="disabled:bg-gray-100 disabled:text-gray-500 m-4  rounded-md 
w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm 
hover:bg-indigo-700"
+        onClick={() => onSearch(paytoUri)}
+      >
+        <i18n.Translate>Search</i18n.Translate>
+      </button>
+    </form>
+  );
+}
+
 function GenericForm({
   onSearch,
 }: {
@@ -421,7 +485,7 @@ function GenericForm({
 }
 
 interface FormPayto {
-  paytoType: "generic" | "iban" | "x-taler-bank";
+  paytoType: "generic" | "iban" | "x-taler-bank" | "wallet";
 }
 
 function createFormValidator(i18n: InternationalizationAPI) {
@@ -558,6 +622,46 @@ function createTalerBankPaytoValidator(i18n: 
InternationalizationAPI) {
   };
 }
 
+interface PaytoUriTalerForm {
+  exchange: string;
+  reservePub: string;
+}
+function createTalerPaytoValidator(i18n: InternationalizationAPI) {
+  return function check(
+    state: RecursivePartial<FormValues<PaytoUriTalerForm>>,
+  ): FormStatus<PaytoUriTalerForm> {
+    const errors = undefinedIfEmpty<FormErrors<PaytoUriTalerForm>>({
+      exchange: !state.exchange ? i18n.str`required` : undefined,
+      reservePub: !state.reservePub
+        ? i18n.str`required`
+        : state.reservePub.length !== 16
+          ? i18n.str`Should be 16 charaters`
+          : undefined,
+    });
+
+    if (errors === undefined) {
+      const result: PaytoUriTalerForm = {
+        exchange: state.exchange!,
+        reservePub: state.reservePub!,
+      };
+      return {
+        status: "ok",
+        result,
+        errors,
+      };
+    }
+    const result: RecursivePartial<PaytoUriTalerForm> = {
+      exchange: state.exchange,
+      reservePub: state.reservePub,
+    };
+    return {
+      status: "fail",
+      result,
+      errors,
+    };
+  };
+}
+
 const paytoTypeField: (
   i18n: InternationalizationAPI,
 ) => UIFormElementConfig[] = (i18n) => [
@@ -574,6 +678,10 @@ const paytoTypeField: (
         value: "x-taler-bank",
         label: i18n.str`Taler Bank`,
       },
+      {
+        value: "wallet",
+        label: i18n.str`Wallet`,
+      },
       {
         value: "generic",
         label: i18n.str`Generic Payto:// URI`,
@@ -613,20 +721,74 @@ const ibanFields: (i18n: InternationalizationAPI) => 
UIFormElementConfig[] = (
     id: "account" as UIHandlerId,
     type: "text",
     required: true,
-    label: i18n.str`Account`,
+    label: i18n.str`IBAN`,
     help: i18n.str`International Bank Account Number`,
     placeholder: i18n.str`DE1231231231`,
     // validator: (value) => validateIBAN(value, i18n),
   },
-  receiverName(i18n),
+  // receiverName(i18n),
+  // {
+  //   id: "bic" as UIHandlerId,
+  //   type: "text",
+  //   label: i18n.str`Bank`,
+  //   help: i18n.str`Business Identifier Code`,
+  //   placeholder: i18n.str`GENODEM1GLS`,
+  //   // validator: (value) => validateIBAN(value, i18n),
+  // },
+];
+const paytoHashFields: (
+  i18n: InternationalizationAPI,
+) => UIFormElementConfig[] = (i18n) => [
   {
-    id: "bic" as UIHandlerId,
+    id: "account" as UIHandlerId,
     type: "text",
-    label: i18n.str`Bank`,
-    help: i18n.str`Business Identifier Code`,
-    placeholder: i18n.str`GENODEM1GLS`,
+    required: true,
+    label: i18n.str`ID`,
+    help: i18n.str`Normalized payto:// hash`,
+    placeholder: i18n.str`ABC123`,
     // validator: (value) => validateIBAN(value, i18n),
   },
+  // receiverName(i18n),
+  // {
+  //   id: "bic" as UIHandlerId,
+  //   type: "text",
+  //   label: i18n.str`Bank`,
+  //   help: i18n.str`Business Identifier Code`,
+  //   placeholder: i18n.str`GENODEM1GLS`,
+  //   // validator: (value) => validateIBAN(value, i18n),
+  // },
+];
+
+const walletFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = 
(
+  i18n,
+) => [
+  {
+    id: "exchange" as UIHandlerId,
+    type: "text",
+    required: true,
+    label: i18n.str`Host`,
+    help: i18n.str`Exchange hostname`,
+    placeholder: i18n.str`exchange.taler.net`,
+    // validator: (value) => validateIBAN(value, i18n),
+  },
+  {
+    id: "reservePub" as UIHandlerId,
+    type: "text",
+    required: true,
+    label: i18n.str`ID`,
+    help: i18n.str`Wallet reserve public key`,
+    placeholder: i18n.str`abcdef1235`,
+    // validator: (value) => validateIBAN(value, i18n),
+  },
+  // receiverName(i18n),
+  // {
+  //   id: "bic" as UIHandlerId,
+  //   type: "text",
+  //   label: i18n.str`Bank`,
+  //   help: i18n.str`Business Identifier Code`,
+  //   placeholder: i18n.str`GENODEM1GLS`,
+  //   // validator: (value) => validateIBAN(value, i18n),
+  // },
 ];
 
 const talerBankFields: (
@@ -636,8 +798,8 @@ const talerBankFields: (
     id: "account" as UIHandlerId,
     type: "text",
     required: true,
-    label: i18n.str`Bank account`,
-    help: i18n.str`Bank account id`,
+    label: i18n.str`Account`,
+    help: i18n.str`Bank account identification`,
     placeholder: i18n.str`DE123123123`,
   },
   {
@@ -649,7 +811,7 @@ const talerBankFields: (
     placeholder: i18n.str`bank.demo.taler.net`,
     // validator: (value) => validateTalerBank(value, i18n),
   },
-  receiverName(i18n),
+  // receiverName(i18n),
 ];
 
 function validateIBAN(
diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx 
b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
index 6ec6b787a..4088ed823 100644
--- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
+++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
@@ -68,24 +68,35 @@ export function ShowConsolidated({
 
   const formConfig: FormConfiguration = {
     type: "double-column",
-    design: Object.entries(fixed).length > 0 ? [
-      {
-        title: i18n.str`KYC collected info`,
-        fields: Object.entries(fixed).map(([key, field]) => {
-          const result: UIFormElementConfig = {
-            type: "text",
-            label: key as TranslatedString,
-            id: `${key}.value` as UIHandlerId,
-            disabled: true,
-            help: `At ${field.since.t_ms === "never"
-                ? "never"
-                : format(field.since.t_ms, "dd/MM/yyyy HH:mm:ss")
-              }` as TranslatedString,
-          };
-          return result;
-        }),
-      }
-    ] : [],
+    design:
+      Object.entries(fixed).length > 0
+        ? [
+            {
+              title: i18n.str`KYC collected info`,
+              description:
+                until.t_ms === "never"
+                  ? undefined
+                  : i18n.str`All information known until ${format(
+                      until.t_ms,
+                      "dd/MM/yyyy HH:mm:ss",
+                    )}`,
+              fields: Object.entries(fixed).map(([key, field]) => {
+                const result: UIFormElementConfig = {
+                  type: "text",
+                  label: key as TranslatedString,
+                  id: `${key}.value` as UIHandlerId,
+                  disabled: true,
+                  help: `At ${
+                    field.since.t_ms === "never"
+                      ? "never"
+                      : format(field.since.t_ms, "dd/MM/yyyy HH:mm:ss")
+                  }` as TranslatedString,
+                };
+                return result;
+              }),
+            },
+          ]
+        : [],
   };
   const shape: Array<UIHandlerId> = formConfig.design.flatMap((field) =>
     getShapeFromFields(field.fields),
diff --git a/packages/aml-backoffice-ui/src/stories.test.ts 
b/packages/aml-backoffice-ui/src/stories.test.ts
index 265a2165b..7af904eda 100644
--- a/packages/aml-backoffice-ui/src/stories.test.ts
+++ b/packages/aml-backoffice-ui/src/stories.test.ts
@@ -68,9 +68,11 @@ function DefaultTestingContext({
     supported_kyc_requirements: [],
     version: "asd",
   };
+  const keys: TalerExchangeApi.ExchangeKeysResponse = {} as any;
+
   const value: ExchangeContextType = {
     cancelRequest: () => null,
-    config,
+    config: { config, keys },
     url: new URL("/", "http://localhost";),
     hints: [],
     lib: {
diff --git a/packages/aml-backoffice-ui/src/stories.tsx 
b/packages/aml-backoffice-ui/src/stories.tsx
index 9a23d82fa..ed1fecc8b 100644
--- a/packages/aml-backoffice-ui/src/stories.tsx
+++ b/packages/aml-backoffice-ui/src/stories.tsx
@@ -57,9 +57,10 @@ function getWrapperForGroup(): FunctionComponent {
       supported_kyc_requirements: [],
       version: "asd",
     };
+    const keys: TalerExchangeApi.ExchangeKeysResponse = {} as any;
     const value: ExchangeContextType = {
       cancelRequest: () => null,
-      config,
+      config: { config, keys },
       url: new URL("/", "http://localhost";),
       hints: [],
       lib: {
diff --git a/packages/bank-ui/src/pages/OperationState/views.tsx 
b/packages/bank-ui/src/pages/OperationState/views.tsx
index 0569ea6eb..3894d646e 100644
--- a/packages/bank-ui/src/pages/OperationState/views.tsx
+++ b/packages/bank-ui/src/pages/OperationState/views.tsx
@@ -240,6 +240,10 @@ export function NeedConfirmationView({
                         );
                       }
                       switch (details.account.targetType) {
+                        case "taler": {
+                          // FIXME: support wire transfer to wallet
+                          return <div>not yet supported</div>;
+                        }
                         case "iban": {
                           const name = details.account.params["receiver-name"];
                           return (
diff --git a/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx
index 7c7a7f99d..c36e9a921 100644
--- a/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -166,11 +166,13 @@ export function PaytoWireTransferForm({
         ? undefined
         : p.targetType === "iban"
           ? p.iban
-          : p.targetType === "bitcoin"
-            ? p.address
-            : p.targetType === "x-taler-bank"
-              ? p.account
-              : assertUnreachable(p);
+          : p.targetType === "taler"
+            ? undefined // FIXME: unsupported payto://
+            : p.targetType === "bitcoin"
+              ? p.address
+              : p.targetType === "x-taler-bank"
+                ? p.account
+                : assertUnreachable(p);
     } else {
       if (!account || !subject) return;
       let payto;
@@ -317,6 +319,10 @@ export function PaytoWireTransferForm({
                 onChange={() => {
                   if (parsed && parsed.isKnown) {
                     switch (parsed.targetType) {
+                      case "taler": {
+                        // FIXME: unsupported payto
+                        break;
+                      }
                       case "iban": {
                         setAccount(parsed.iban);
                         break;
@@ -543,7 +549,7 @@ export function PaytoWireTransferForm({
                   <b style={{ color: "red" }}> *</b>
                 </label>
                 <div class="mt-2">
-                <textarea
+                  <textarea
                     type="textarea"
                     rows={3}
                     class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
diff --git a/packages/bank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/bank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 143b47647..04ca88988 100644
--- a/packages/bank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/bank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -272,6 +272,10 @@ export function WithdrawalConfirmationQuestion({
                               );
                             }
                             switch (details.account.targetType) {
+                              case "taler": {
+                                // FIXME: support wire transfer to wallet
+                                return <div>not yet supported</div>;
+                              }
                               case "iban": {
                                 const name =
                                   details.account.params["receiver-name"];
diff --git a/packages/kyc-ui/src/pages/TriggerKyc.tsx 
b/packages/kyc-ui/src/pages/TriggerKyc.tsx
index c2a132138..4c4f4f95b 100644
--- a/packages/kyc-ui/src/pages/TriggerKyc.tsx
+++ b/packages/kyc-ui/src/pages/TriggerKyc.tsx
@@ -74,7 +74,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
             {
               id: ".amount" as UIHandlerId,
               type: "amount",
-              currency: config.currency,
+              currency: config.config.currency,
               label: i18n.str`Amount`,
               required: true,
               converterId: "Taler.Amount",
@@ -113,7 +113,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
   const [form, state] = useFormState<FormType>(
     shape,
     {
-      amount: Amounts.parseOrThrow(`${config.currency}:1000000`),
+      amount: Amounts.parseOrThrow(`${config.config.currency}:1000000`),
     },
     (st) => {
       const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({});
@@ -322,7 +322,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000000`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000000`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -334,7 +334,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000010`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000010`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -346,7 +346,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000020`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000020`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -358,7 +358,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000030`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000030`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -370,7 +370,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000040`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000040`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -382,7 +382,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000050`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000050`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -394,7 +394,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000060`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000060`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -406,7 +406,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000070`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000070`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -418,7 +418,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000080`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000080`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -430,7 +430,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000090`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000090`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -442,7 +442,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000100`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000100`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -454,7 +454,7 @@ export function TriggerKyc({ onKycStarted }: Props): VNode {
           <Button
             type="submit"
             handler={triggerAmount(
-              Amounts.parseOrThrow(`${config.currency}:1000110`),
+              Amounts.parseOrThrow(`${config.config.currency}:1000110`),
             )}
             // disabled={!submitHandler}
             class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx 
b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
index a0c5f5044..a5157068b 100644
--- a/packages/merchant-backoffice-ui/src/components/modal/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
@@ -461,9 +461,11 @@ export function ValidBankAccount({
     ? origin.targetPath
     : origin.targetType === "iban"
       ? origin.iban
-      : origin.targetType === "bitcoin"
-        ? `${origin.address.substring(0, 8)}...`
-        : origin.account;
+      : origin.targetType === "taler"
+        ? origin.reservePub
+        : origin.targetType === "bitcoin"
+          ? `${origin.address.substring(0, 8)}...`
+          : origin.account;
 
   return (
     <ConfirmModal
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx
index 1cf3483df..55da0a5da 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx
@@ -107,12 +107,13 @@ function Table({ accounts, onDelete, onSelect }: 
TableProps): VNode {
   const emptyList: Record<
     PaytoType | "unknown",
     { parsed: PaytoUri; acc: Entity }[]
-  > = { bitcoin: [], "x-taler-bank": [], iban: [], unknown: [] };
+  > = { bitcoin: [], "x-taler-bank": [], iban: [], taler: [], unknown: [] };
   const accountsByType = accounts.reduce((prev, acc) => {
     const parsed = parsePaytoUri(acc.payto_uri);
     if (!parsed) return prev; //skip
     if (
       parsed.targetType !== "bitcoin" &&
+      parsed.targetType !== "taler" &&
       parsed.targetType !== "x-taler-bank" &&
       parsed.targetType !== "iban"
     ) {
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
index 6685c053a..7cd3e2913 100644
--- a/packages/taler-util/src/payto.ts
+++ b/packages/taler-util/src/payto.ts
@@ -35,6 +35,7 @@ import { URLSearchParams } from "./url.js";
 export type PaytoUri =
   | PaytoUriUnknown
   | PaytoUriIBAN
+  | PaytoUriTaler
   | PaytoUriTalerBank
   | PaytoUriBitcoin;
 
@@ -77,6 +78,13 @@ export interface PaytoUriIBAN extends PaytoUriGeneric {
   bic?: string;
 }
 
+export interface PaytoUriTaler extends PaytoUriGeneric {
+  isKnown: true;
+  targetType: "taler";
+  exchange: string;
+  reservePub: string;
+}
+
 export interface PaytoUriTalerBank extends PaytoUriGeneric {
   isKnown: true;
   targetType: "x-taler-bank";
@@ -93,7 +101,7 @@ export interface PaytoUriBitcoin extends PaytoUriGeneric {
 
 const paytoPfx = "payto://";
 
-export type PaytoType = "iban" | "bitcoin" | "x-taler-bank";
+export type PaytoType = "iban" | "bitcoin" | "x-taler-bank" | "taler";
 
 export function buildPayto(
   type: "iban",
@@ -101,6 +109,12 @@ export function buildPayto(
   bic: string | undefined,
   params?: Record<string, string>,
 ): PaytoUriIBAN;
+export function buildPayto(
+  type: "taler",
+  exchange: string,
+  reservePub: string,
+  params?: Record<string, string>,
+): PaytoUriIBAN;
 export function buildPayto(
   type: "bitcoin",
   address: string,
@@ -155,6 +169,18 @@ export function buildPayto(
       };
       return result;
     }
+    case "taler": {
+      if (!second) throw Error("missing reservePub for payto://taler");
+      const result: PaytoUriTaler = {
+        isKnown: true,
+        targetType: "taler",
+        exchange: first,
+        reservePub: second,
+        params,
+        targetPath: `${first}/${second}`,
+      };
+      return result;
+    }
     default: {
       const unknownType: never = type;
       throw Error(`unknown payto:// type ${unknownType}`);
@@ -254,6 +280,10 @@ export function hashNormalizedPaytoUri(p: PaytoUri | 
string): Uint8Array {
         break;
       case "bitcoin":
         paytoStr = `payto://bitcoin/${p.address}`;
+        break;
+      case "taler":
+        paytoStr = `payto://taler/${p.exchange}/${p.reservePub}`;
+        break;
     }
   }
   return hashTruncate32(stringToBytes(paytoStr + "\0"));
@@ -320,6 +350,19 @@ export function parsePaytoUri(s: string): PaytoUri | 
undefined {
     params[k] = v; //decodeURIComponent(v);
   });
 
+  if (targetType === "taler") {
+    const parts = targetPath.split("/");
+    const exchange = parts[0];
+    const reservePub = parts[1];
+    return {
+      targetPath,
+      targetType,
+      params,
+      isKnown: true,
+      exchange,
+      reservePub,
+    };
+  }
   if (targetType === "x-taler-bank") {
     const parts = targetPath.split("/");
     const host = parts[0];
diff --git a/packages/taler-util/src/taleruri.ts 
b/packages/taler-util/src/taleruri.ts
index b4455fba4..bce3fcff6 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -695,6 +695,10 @@ export function stringifyWithdrawUri({
   return `${proto}://withdraw/${path}${withdrawalOperationId}`;
 }
 
+export function getURLHostnamePortPath(baseUrl: string) {
+  return getUrlInfo(baseUrl).path;
+}
+
 /**
  * Use baseUrl to defined http or https
  * create path using host+port+pathname
diff --git 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
index 65368fd81..346272ccb 100644
--- 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
+++ 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -20,6 +20,7 @@ import {
   AmountString,
   parsePaytoUri,
   PaytoUriIBAN,
+  PaytoUriTaler,
   PaytoUriTalerBank,
   PaytoUriUnknown,
   segwitMinAmount,
@@ -156,7 +157,7 @@ function IBANAccountInfoTable({
   subject,
 }: {
   subject: string;
-  payto: PaytoUriUnknown | PaytoUriIBAN | PaytoUriTalerBank;
+  payto: PaytoUriUnknown | PaytoUriIBAN | PaytoUriTalerBank | PaytoUriTaler;
 }) {
   const { i18n } = useTranslationContext();
   const api = useBackendContext();
@@ -195,6 +196,11 @@ function IBANAccountInfoTable({
       ) : undefined}
       <Row name={i18n.str`IBAN`} value={payto.iban} />
     </Fragment>
+  ) : payto.targetType === "taler" ? (
+    <Fragment>
+      <Row name={i18n.str`Exchange`} value={payto.exchange} />
+      <Row name={i18n.str`Reserve Pub`} value={payto.reservePub} />
+    </Fragment>
   ) : undefined;
 
   const receiver =
diff --git 
a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx 
b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
index 1b34ad4e9..dbd21ee8d 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
+++ 
b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
@@ -400,6 +400,9 @@ function describeAccount(paytoUri: string): string {
     case "iban": {
       return p.iban;
     }
+    case "taler": {
+      return p.reservePub;
+    }
     case "x-taler-bank": {
       return `${p.host}/${p.account}`;
     }
diff --git a/packages/web-util/src/context/exchange-api.ts 
b/packages/web-util/src/context/exchange-api.ts
index 967b042f9..eefdd481d 100644
--- a/packages/web-util/src/context/exchange-api.ts
+++ b/packages/web-util/src/context/exchange-api.ts
@@ -22,7 +22,7 @@ import {
   TalerError,
   TalerExchangeApi,
   TalerExchangeCacheEviction,
-  TalerExchangeHttpClient
+  TalerExchangeHttpClient,
 } from "@gnu-taler/taler-util";
 import {
   ComponentChildren,
@@ -32,7 +32,11 @@ import {
   h,
 } from "preact";
 import { useContext, useEffect, useState } from "preact/hooks";
-import { BrowserFetchHttpLib, ErrorLoading, useTranslationContext } from 
"../index.browser.js";
+import {
+  BrowserFetchHttpLib,
+  ErrorLoading,
+  useTranslationContext,
+} from "../index.browser.js";
 import {
   APIClient,
   ActiviyTracker,
@@ -47,7 +51,7 @@ import {
 
 export type ExchangeContextType = {
   url: URL;
-  config: TalerExchangeApi.ExchangeVersionResponse;
+  config: KeysAndConfigType;
   lib: ExchangeLib;
   hints: VersionHint[];
   onActivity: Subscriber<ObservabilityEvent>;
@@ -80,6 +84,11 @@ type ConfigResultFail<T> =
 
 const CONFIG_FAIL_TRY_AGAIN_MS = 5000;
 
+export type KeysAndConfigType = {
+  config: TalerExchangeApi.ExchangeVersionResponse;
+  keys: TalerExchangeApi.ExchangeKeysResponse;
+};
+
 export const ExchangeApiProvider = ({
   baseUrl,
   children,
@@ -91,9 +100,8 @@ export const ExchangeApiProvider = ({
   children: ComponentChildren;
   frameOnError: FunctionComponent<{ children: ComponentChildren }>;
 }): VNode => {
-  const [checked, setChecked] =
-    useState<ConfigResult<TalerExchangeApi.ExchangeVersionResponse>>();
-    const { i18n } = useTranslationContext();
+  const [checked, setChecked] = useState<ConfigResult<KeysAndConfigType>>();
+  const { i18n } = useTranslationContext();
 
   const { getRemoteConfig, VERSION, lib, cancelRequest, onActivity } =
     buildExchangeApiClient(baseUrl, evictors);
@@ -103,7 +111,7 @@ export const ExchangeApiProvider = ({
     async function testConfig(): Promise<void> {
       try {
         const config = await getRemoteConfig();
-        if (LibtoolVersion.compare(VERSION, config.version)) {
+        if (LibtoolVersion.compare(VERSION, config.config.version)) {
           setChecked({ type: "ok", config, hints: [] });
         } else {
           setChecked({
@@ -147,7 +155,7 @@ export const ExchangeApiProvider = ({
       children: h(
         "div",
         {},
-        i18n.str`The server version is not supported. Supported version 
"${checked.supported}", server version "${checked.result.version}"`,
+        i18n.str`The server version is not supported. Supported version 
"${checked.supported}", server version "${checked.result.config.version}"`,
       ),
     });
   }
@@ -169,7 +177,7 @@ export const ExchangeApiProvider = ({
 function buildExchangeApiClient(
   url: URL,
   evictors: Evictors,
-): APIClient<ExchangeLib, TalerExchangeApi.ExchangeVersionResponse> {
+): APIClient<ExchangeLib, KeysAndConfigType> {
   const httpFetch = new BrowserFetchHttpLib({
     enableThrottling: true,
     requireTls: false,
@@ -184,16 +192,31 @@ function buildExchangeApiClient(
 
   const ex = new TalerExchangeHttpClient(url.href, httpLib, evictors.exchange);
 
-  async function getRemoteConfig(): 
Promise<TalerExchangeApi.ExchangeVersionResponse> {
-    const resp = await ex.getConfig();
-    if (resp.type === "fail") {
-      if (resp.detail) {
-        throw TalerError.fromUncheckedDetail(resp.detail);
+  async function getRemoteConfig(): Promise<KeysAndConfigType> {
+    const configResp = await ex.getConfig();
+    if (configResp.type === "fail") {
+      if (configResp.detail) {
+        throw TalerError.fromUncheckedDetail(configResp.detail);
       } else {
-        throw TalerError.fromException(new Error("failed to get exchange 
remote config"))
+        throw TalerError.fromException(
+          new Error("failed to get exchange remote config"),
+        );
       }
     }
-    return resp.body;
+    const keysResp = await ex.getKeys();
+    // if (keysResp.type === "fail") {
+    //   if (keysResp.detail) {
+    //     throw TalerError.fromUncheckedDetail(keysResp.detail);
+    //   } else {
+    //     throw TalerError.fromException(
+    //       new Error("failed to get exchange remote config"),
+    //     );
+    //   }
+    // }
+    return {
+      config: configResp.body,
+      keys: keysResp.body,
+    };
   }
 
   return {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]