commit 12f7657f6391f59c60dfc1824c31037beeb2b4ab
parent aa80c3d6bf1ea18c71e4d6a963e67e972f6e68a1
Author: Florian Dold <florian@dold.me>
Date: Sun, 19 Jul 2026 00:26:02 +0200
util: add void payto, fix host port path parsing
Diffstat:
3 files changed, 55 insertions(+), 11 deletions(-)
diff --git a/packages/taler-util/src/payto.test.ts b/packages/taler-util/src/payto.test.ts
@@ -20,8 +20,8 @@ import { test } from "node:test";
import {
addPaytoQueryParams,
HostPortPath,
- PaytoType,
Paytos,
+ PaytoType,
} from "./payto.js";
import { Result } from "./result.js";
@@ -59,7 +59,10 @@ test("basic x-taler-bank payto string", (t) => {
}
assert.strictEqual(result.normalizedPath, "bank.demo.taler.net/accountName");
assert.strictEqual(result.host, "bank.demo.taler.net");
- assert.strictEqual(result.url, "https://bank.demo.taler.net/" as HostPortPath);
+ assert.strictEqual(
+ result.url,
+ "https://bank.demo.taler.net/" as HostPortPath,
+ );
assert.strictEqual(result.account, "accountName");
});
@@ -187,3 +190,10 @@ test("Paytos.parseHostPortPath keeps the path", () => {
"https://example.com/foo/bar/",
);
});
+
+test("void payto URI", () => {
+ assert.ok(Result.isOk(Paytos.fromString("payto://void/foo")));
+ assert.ok(
+ Result.isOk(Paytos.fromString("payto://void/foo?receiver-name=Bla")),
+ );
+});
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
@@ -41,6 +41,7 @@ export enum PaytoType {
TalerReserve = "taler-reserve",
TalerReserveHttp = "taler-reserve-http",
Ethereum = "ethereum",
+ Void = "void",
}
export enum ReservePubParseError {
@@ -94,7 +95,8 @@ export namespace Paytos {
| PaytoTalerReserveHttp
| PaytoTalerBank
| PaytoEthereum
- | PaytoBitcoin;
+ | PaytoBitcoin
+ | PaytoVoid;
declare const __full_payto_str: unique symbol;
export type FullPaytoString = string & { [__full_payto_str]: true };
@@ -165,6 +167,10 @@ export namespace Paytos {
account: string;
}
+ export interface PaytoVoid extends PaytoGeneric {
+ targetType: PaytoType.Void;
+ }
+
export interface PaytoBitcoin extends PaytoGeneric {
targetType: PaytoType.Bitcoin;
address: BtAddrString;
@@ -177,7 +183,7 @@ export namespace Paytos {
address: EthAddrString;
}
- const supported_targets: Record<PaytoType, true> = {
+ const supportedTargets: Record<PaytoType, true> = {
iban: true,
bitcoin: true,
"x-taler-bank": true,
@@ -185,6 +191,7 @@ export namespace Paytos {
"taler-reserve-http": true,
ethereum: true,
cyclos: true,
+ void: true,
};
export function hashFull(
@@ -316,8 +323,11 @@ export namespace Paytos {
export function parseHostPortPath(
hostnameAndPath: string,
): HostPortPath | undefined {
- const [host, path] = hostnameAndPath.split("/", 1);
- return parseHostPortPath2(host, path ?? "");
+ const slashIdx = hostnameAndPath.indexOf("/");
+ const host =
+ slashIdx < 0 ? hostnameAndPath : hostnameAndPath.slice(0, slashIdx);
+ const path = slashIdx < 0 ? "" : hostnameAndPath.slice(slashIdx + 1);
+ return parseHostPortPath2(host, path);
}
/**
@@ -487,6 +497,19 @@ export namespace Paytos {
};
}
+ export function createPaytoVoid(
+ voidPath: string,
+ params: Record<string, string> = {},
+ ): PaytoVoid {
+ return {
+ targetType: PaytoType.Void,
+ params,
+ normalizedPath: `void/${voidPath}`,
+ fullPath: `void/${voidPath}`,
+ displayName: `void(${voidPath})`,
+ };
+ }
+
//////////////////////
// parsing function
///////////////////////
@@ -583,7 +606,7 @@ export namespace Paytos {
const targetType = (
firstSlashPos === -1 ? acct : acct.slice(0, firstSlashPos)
) as PaytoType;
- if (!opts.allowUnsupported && !supported_targets[targetType]) {
+ if (!opts.allowUnsupported && !supportedTargets[targetType]) {
return Result.errorWithDetail(PaytoParseError.UNSUPPORTED, {
targetType,
});
@@ -803,6 +826,9 @@ export namespace Paytos {
createCyclos(host ?? (cs[0] as HostPortPath), accountId, params),
);
}
+ case PaytoType.Void: {
+ return Result.of<URI>(createPaytoVoid(targetPath, params));
+ }
default: {
if (opts.allowUnsupported) {
return Result.of<URI>(
diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts
@@ -47,10 +47,10 @@ import {
InsufficientBalanceHint,
j2s,
Logger,
- Paytos,
- Result,
PayCoinSelection,
PaymentInsufficientBalanceDetails,
+ Paytos,
+ Result,
ScopeInfo,
ScopeType,
SelectedCoin,
@@ -202,7 +202,9 @@ async function internalSelectPayCoins(
> {
let restrictWireMethod;
if (req.depositPaytoUri) {
- const parsedPayto = Result.orUndefined(Paytos.fromString(req.depositPaytoUri));
+ const parsedPayto = Result.orUndefined(
+ Paytos.fromString(req.depositPaytoUri),
+ );
if (!parsedPayto) {
throw Error("invalid payto URI");
}
@@ -846,7 +848,13 @@ export function findMatchingWire(
| undefined {
const accountRestrictions: Record<string, AccountRestriction[]> = {};
for (const acc of exchangeWireDetails.wireInfo.accounts) {
- const pp = Result.orUndefined(Paytos.fromString(acc.payto_uri));
+ const ppRes = Paytos.fromString(acc.payto_uri);
+ if (Result.isError(ppRes)) {
+ throw Error(
+ `failed to parse payto ${acc.payto_uri}`,
+ );
+ }
+ const pp = ppRes.value;
checkLogicInvariant(!!pp);
if (pp.targetType !== wireMethod) {
continue;