aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/talerCrypto.ts51
-rw-r--r--packages/taler-util/src/talerTypes.ts71
-rw-r--r--packages/taler-util/src/taleruri.ts66
-rw-r--r--packages/taler-util/src/walletTypes.ts81
-rw-r--r--packages/taler-wallet-cli/src/harness/harness.ts2
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts70
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts (renamed from packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts)4
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/testrunner.ts6
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts140
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoTypes.ts66
-rw-r--r--packages/taler-wallet-core/src/db.ts65
-rw-r--r--packages/taler-wallet-core/src/operations/peer-to-peer.ts164
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts20
-rw-r--r--packages/taler-wallet-core/src/wallet.ts27
14 files changed, 759 insertions, 74 deletions
diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts
index 38bb5ad0a..d7734707a 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -1214,6 +1214,9 @@ type ContractPrivateKey = FlavorP<Uint8Array, "ContractPrivateKey", 32> &
type MergePrivateKey = FlavorP<Uint8Array, "MergePrivateKey", 32> &
MaterialEddsaPriv;
+const mergeSalt = "p2p-merge-contract";
+const depositSalt = "p2p-deposit-contract";
+
export function encryptContractForMerge(
pursePub: PursePublicKey,
contractPriv: ContractPrivateKey,
@@ -1230,12 +1233,24 @@ export function encryptContractForMerge(
contractTermsCompressed,
]);
const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
- return encryptWithDerivedKey(
- getRandomBytesF(24),
- key,
- data,
- "p2p-merge-contract",
- );
+ return encryptWithDerivedKey(getRandomBytesF(24), key, data, mergeSalt);
+}
+
+export function encryptContractForDeposit(
+ pursePub: PursePublicKey,
+ contractPriv: ContractPrivateKey,
+ contractTerms: any,
+): Promise<OpaqueData> {
+ const contractTermsCanon = canonicalJson(contractTerms) + "\0";
+ const contractTermsBytes = stringToBytes(contractTermsCanon);
+ const contractTermsCompressed = fflate.zlibSync(contractTermsBytes);
+ const data = typedArrayConcat([
+ bufferForUint32(ContractFormatTag.PaymentRequest),
+ bufferForUint32(contractTermsBytes.length),
+ contractTermsCompressed,
+ ]);
+ const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
+ return encryptWithDerivedKey(getRandomBytesF(24), key, data, depositSalt);
}
export interface DecryptForMergeResult {
@@ -1243,13 +1258,17 @@ export interface DecryptForMergeResult {
mergePriv: Uint8Array;
}
+export interface DecryptForDepositResult {
+ contractTerms: any;
+}
+
export async function decryptContractForMerge(
enc: OpaqueData,
pursePub: PursePublicKey,
contractPriv: ContractPrivateKey,
): Promise<DecryptForMergeResult> {
const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
- const dec = await decryptWithDerivedKey(enc, key, "p2p-merge-contract");
+ const dec = await decryptWithDerivedKey(enc, key, mergeSalt);
const mergePriv = dec.slice(8, 8 + 32);
const contractTermsCompressed = dec.slice(8 + 32);
const contractTermsBuf = fflate.unzlibSync(contractTermsCompressed);
@@ -1263,6 +1282,20 @@ export async function decryptContractForMerge(
};
}
-export function encryptContractForDeposit() {
- throw Error("not implemented");
+export async function decryptContractForDeposit(
+ enc: OpaqueData,
+ pursePub: PursePublicKey,
+ contractPriv: ContractPrivateKey,
+): Promise<DecryptForDepositResult> {
+ const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
+ const dec = await decryptWithDerivedKey(enc, key, depositSalt);
+ const contractTermsCompressed = dec.slice(8);
+ const contractTermsBuf = fflate.unzlibSync(contractTermsCompressed);
+ // Slice of the '\0' at the end and decode to a string
+ const contractTermsString = bytesToString(
+ contractTermsBuf.slice(0, contractTermsBuf.length - 1),
+ );
+ return {
+ contractTerms: JSON.parse(contractTermsString),
+ };
}
diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts
index d4de8c37b..ee2dee93c 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -1874,3 +1874,74 @@ export interface PeerContractTerms {
summary: string;
purse_expiration: TalerProtocolTimestamp;
}
+
+export interface EncryptedContract {
+ // Encrypted contract.
+ econtract: string;
+
+ // Signature over the (encrypted) contract.
+ econtract_sig: string;
+
+ // Ephemeral public key for the DH operation to decrypt the encrypted contract.
+ contract_pub: string;
+}
+
+/**
+ * Payload for /reserves/{reserve_pub}/purse
+ * endpoint of the exchange.
+ */
+export interface ExchangeReservePurseRequest {
+ /**
+ * Minimum amount that must be credited to the reserve, that is
+ * the total value of the purse minus the deposit fees.
+ * If the deposit fees are lower, the contribution to the
+ * reserve can be higher!
+ */
+ purse_value: AmountString;
+
+ // Minimum age required for all coins deposited into the purse.
+ min_age: number;
+
+ // Purse fee the reserve owner is willing to pay
+ // for the purse creation. Optional, if not present
+ // the purse is to be created from the purse quota
+ // of the reserve.
+ purse_fee: AmountString;
+
+ // Optional encrypted contract, in case the buyer is
+ // proposing the contract and thus establishing the
+ // purse with the payment.
+ econtract?: EncryptedContract;
+
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKeyString;
+
+ // EdDSA signature of the purse private key affirming the merge
+ // over a TALER_PurseMergeSignaturePS.
+ // Must be of purpose TALER_SIGNATURE_PURSE_MERGE.
+ merge_sig: EddsaSignatureString;
+
+ // EdDSA signature of the account/reserve affirming the merge.
+ // Must be of purpose TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
+ reserve_sig: EddsaSignatureString;
+
+ // Purse public key.
+ purse_pub: EddsaPublicKeyString;
+
+ // EdDSA signature of the purse over
+ // TALER_PurseRequestSignaturePS of
+ // purpose TALER_SIGNATURE_PURSE_REQUEST
+ // confirming that the
+ // above details hold for this purse.
+ purse_sig: EddsaSignatureString;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCodeString;
+
+ // Client-side timestamp of when the merge request was made.
+ merge_timestamp: TalerProtocolTimestamp;
+
+ // Indicative time by which the purse should expire
+ // if it has not been paid.
+ purse_expiration: TalerProtocolTimestamp;
+}
diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts
index e3bd120f0..e7d66d7d5 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -45,6 +45,11 @@ export interface PayPushUriResult {
contractPriv: string;
}
+export interface PayPullUriResult {
+ exchangeBaseUrl: string;
+ contractPriv: string;
+}
+
/**
* Parse a taler[+http]://withdraw URI.
* Return undefined if not passed a valid URI.
@@ -84,10 +89,14 @@ export enum TalerUriType {
TalerTip = "taler-tip",
TalerRefund = "taler-refund",
TalerNotifyReserve = "taler-notify-reserve",
- TalerPayPush = "pay-push",
+ TalerPayPush = "taler-pay-push",
+ TalerPayPull = "taler-pay-pull",
Unknown = "unknown",
}
+const talerActionPayPull = "pay-pull";
+const talerActionPayPush = "pay-push";
+
/**
* Classify a taler:// URI.
*/
@@ -117,12 +126,18 @@ export function classifyTalerUri(s: string): TalerUriType {
if (sl.startsWith("taler+http://withdraw/")) {
return TalerUriType.TalerWithdraw;
}
- if (sl.startsWith("taler://pay-push/")) {
+ if (sl.startsWith(`taler://${talerActionPayPush}/`)) {
return TalerUriType.TalerPayPush;
}
- if (sl.startsWith("taler+http://pay-push/")) {
+ if (sl.startsWith(`taler+http://${talerActionPayPush}/`)) {
return TalerUriType.TalerPayPush;
}
+ if (sl.startsWith(`taler://${talerActionPayPull}/`)) {
+ return TalerUriType.TalerPayPull;
+ }
+ if (sl.startsWith(`taler+http://${talerActionPayPull}/`)) {
+ return TalerUriType.TalerPayPull;
+ }
if (sl.startsWith("taler://notify-reserve/")) {
return TalerUriType.TalerNotifyReserve;
}
@@ -189,7 +204,29 @@ export function parsePayUri(s: string): PayUriResult | undefined {
}
export function parsePayPushUri(s: string): PayPushUriResult | undefined {
- const pi = parseProtoInfo(s, "pay-push");
+ const pi = parseProtoInfo(s, talerActionPayPush);
+ if (!pi) {
+ return undefined;
+ }
+ const c = pi?.rest.split("?");
+ const parts = c[0].split("/");
+ if (parts.length < 2) {
+ return undefined;
+ }
+ const host = parts[0].toLowerCase();
+ const contractPriv = parts[parts.length - 1];
+ const pathSegments = parts.slice(1, parts.length - 1);
+ const p = [host, ...pathSegments].join("/");
+ const exchangeBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
+
+ return {
+ exchangeBaseUrl,
+ contractPriv,
+ };
+}
+
+export function parsePayPullUri(s: string): PayPullUriResult | undefined {
+ const pi = parseProtoInfo(s, talerActionPayPull);
if (!pi) {
return undefined;
}
@@ -283,3 +320,24 @@ export function constructPayPushUri(args: {
}
return `${proto}://pay-push/${url.host}${url.pathname}${args.contractPriv}`;
}
+
+export function constructPayPullUri(args: {
+ exchangeBaseUrl: string;
+ contractPriv: string;
+}): string {
+ const url = new URL(args.exchangeBaseUrl);
+ let proto: string;
+ if (url.protocol === "https:") {
+ proto = "taler";
+ } else if (url.protocol === "http:") {
+ proto = "taler+http";
+ } else {
+ throw Error(`Unsupported exchange URL protocol ${args.exchangeBaseUrl}`);
+ }
+ if (!url.pathname.endsWith("/")) {
+ throw Error(
+ `exchange base URL must end with a slash (got ${args.exchangeBaseUrl}instead)`,
+ );
+ }
+ return `${proto}://pay-pull/${url.host}${url.pathname}${args.contractPriv}`;
+}
diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts
index 7b482c60e..3a415b221 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -627,7 +627,7 @@ export interface ExchangeAccount {
master_sig: string;
}
-export type WireFeeMap = { [wireMethod: string]: WireFee[] }
+export type WireFeeMap = { [wireMethod: string]: WireFee[] };
export interface WireInfo {
feesForType: WireFeeMap;
accounts: ExchangeAccount[];
@@ -639,7 +639,6 @@ const codecForExchangeAccount = (): Codec<ExchangeAccount> =>
.property("master_sig", codecForString())
.build("codecForExchangeAccount");
-
const codecForWireFee = (): Codec<WireFee> =>
buildCodecForObject<WireFee>()
.property("sig", codecForString())
@@ -658,19 +657,18 @@ const codecForWireInfo = (): Codec<WireInfo> =>
const codecForDenominationInfo = (): Codec<DenominationInfo> =>
buildCodecForObject<DenominationInfo>()
- .property("denomPubHash", (codecForString()))
- .property("value", (codecForAmountJson()))
- .property("feeWithdraw", (codecForAmountJson()))
- .property("feeDeposit", (codecForAmountJson()))
- .property("feeRefresh", (codecForAmountJson()))
- .property("feeRefund", (codecForAmountJson()))
- .property("stampStart", (codecForTimestamp))
- .property("stampExpireWithdraw", (codecForTimestamp))
- .property("stampExpireLegal", (codecForTimestamp))
- .property("stampExpireDeposit", (codecForTimestamp))
+ .property("denomPubHash", codecForString())
+ .property("value", codecForAmountJson())
+ .property("feeWithdraw", codecForAmountJson())
+ .property("feeDeposit", codecForAmountJson())
+ .property("feeRefresh", codecForAmountJson())
+ .property("feeRefund", codecForAmountJson())
+ .property("stampStart", codecForTimestamp)
+ .property("stampExpireWithdraw", codecForTimestamp)
+ .property("stampExpireLegal", codecForTimestamp)
+ .property("stampExpireDeposit", codecForTimestamp)
.build("codecForDenominationInfo");
-
export interface DenominationInfo {
value: AmountJson;
denomPubHash: string;
@@ -713,7 +711,6 @@ export interface DenominationInfo {
* Data after which coins of this denomination can't be deposited anymore.
*/
stampExpireDeposit: TalerProtocolTimestamp;
-
}
export interface ExchangeListItem {
@@ -726,7 +723,6 @@ export interface ExchangeListItem {
denominations: DenominationInfo[];
}
-
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
buildCodecForObject<AuditorDenomSig>()
.property("denom_pub_h", codecForString())
@@ -740,7 +736,6 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> =>
.property("denomination_keys", codecForList(codecForAuditorDenomSig()))
.build("codecForExchangeAuditor");
-
const codecForExchangeTos = (): Codec<ExchangeTos> =>
buildCodecForObject<ExchangeTos>()
.property("acceptedVersion", codecOptional(codecForString()))
@@ -1452,18 +1447,34 @@ export interface CheckPeerPushPaymentRequest {
talerUri: string;
}
+export interface CheckPeerPullPaymentRequest {
+ talerUri: string;
+}
+
export interface CheckPeerPushPaymentResponse {
contractTerms: any;
amount: AmountString;
peerPushPaymentIncomingId: string;
}
+export interface CheckPeerPullPaymentResponse {
+ contractTerms: any;
+ amount: AmountString;
+ peerPullPaymentIncomingId: string;
+}
+
export const codecForCheckPeerPushPaymentRequest =
(): Codec<CheckPeerPushPaymentRequest> =>
buildCodecForObject<CheckPeerPushPaymentRequest>()
.property("talerUri", codecForString())
.build("CheckPeerPushPaymentRequest");
+export const codecForCheckPeerPullPaymentRequest =
+ (): Codec<CheckPeerPullPaymentRequest> =>
+ buildCodecForObject<CheckPeerPullPaymentRequest>()
+ .property("talerUri", codecForString())
+ .build("CheckPeerPullPaymentRequest");
+
export interface AcceptPeerPushPaymentRequest {
/**
* Transparent identifier of the incoming peer push payment.
@@ -1476,3 +1487,41 @@ export const codecForAcceptPeerPushPaymentRequest =
buildCodecForObject<AcceptPeerPushPaymentRequest>()
.property("peerPushPaymentIncomingId", codecForString())
.build("AcceptPeerPushPaymentRequest");
+
+export interface AcceptPeerPullPaymentRequest {
+ /**
+ * Transparent identifier of the incoming peer pull payment.
+ */
+ peerPullPaymentIncomingId: string;
+}
+
+export const codecForAcceptPeerPullPaymentRequest =
+ (): Codec<AcceptPeerPullPaymentRequest> =>
+ buildCodecForObject<AcceptPeerPullPaymentRequest>()
+ .property("peerPullPaymentIncomingId", codecForString())
+ .build("AcceptPeerPllPaymentRequest");
+
+export interface InitiatePeerPullPaymentRequest {
+ /**
+ * FIXME: Make this optional?
+ */
+ exchangeBaseUrl: string;
+ amount: AmountString;
+ partialContractTerms: any;
+}
+
+export const codecForInitiatePeerPullPaymentRequest =
+ (): Codec<InitiatePeerPullPaymentRequest> =>
+ buildCodecForObject<InitiatePeerPullPaymentRequest>()
+ .property("partialContractTerms", codecForAny())
+ .property("amount", codecForAmountString())
+ .property("exchangeBaseUrl", codecForAmountString())
+ .build("InitiatePeerPullPaymentRequest");
+
+export interface InitiatePeerPullPaymentResponse {
+ /**
+ * Taler URI for the other party to make the payment
+ * that was requested.
+ */
+ talerUri: string;
+}
diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts
index c735c9956..33f677d94 100644
--- a/packages/taler-wallet-cli/src/harness/harness.ts
+++ b/packages/taler-wallet-cli/src/harness/harness.ts
@@ -1284,7 +1284,7 @@ export class ExchangeService implements ExchangeServiceInterface {
// account fee
`${this.exchangeConfig.currency}:0.01`,
// purse fee
- `${this.exchangeConfig.currency}:0.01`,
+ `${this.exchangeConfig.currency}:0.00`,
// purse timeout
"1h",
// kyc timeout
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
new file mode 100644
index 000000000..e78bd5a29
--- /dev/null
+++ b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
@@ -0,0 +1,70 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import {
+ createSimpleTestkudosEnvironment,
+ withdrawViaBank,
+} from "../harness/helpers.js";
+
+/**
+ * Run test for basic, bank-integrated withdrawal and payment.
+ */
+export async function runPeerToPeerPullTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { wallet, bank, exchange, merchant } =
+ await createSimpleTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
+
+ await wallet.runUntilDone();
+
+ const resp = await wallet.client.call(
+ WalletApiOperation.InitiatePeerPullPayment,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ amount: "TESTKUDOS:5",
+ partialContractTerms: {
+ summary: "Hello World",
+ },
+ },
+ );
+
+ const checkResp = await wallet.client.call(
+ WalletApiOperation.CheckPeerPullPayment,
+ {
+ talerUri: resp.talerUri,
+ },
+ );
+
+ const acceptResp = await wallet.client.call(
+ WalletApiOperation.AcceptPeerPullPayment,
+ {
+ peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId,
+ },
+ );
+
+ await wallet.runUntilDone();
+}
+
+runPeerToPeerPullTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts
index c22258bc8..11360f6e9 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts
@@ -27,7 +27,7 @@ import {
/**
* Run test for basic, bank-integrated withdrawal and payment.
*/
-export async function runPeerToPeerTest(t: GlobalTestState) {
+export async function runPeerToPeerPushTest(t: GlobalTestState) {
// Set up test environment
const { wallet, bank, exchange, merchant } =
@@ -72,4 +72,4 @@ export async function runPeerToPeerTest(t: GlobalTestState) {
await wallet.runUntilDone();
}
-runPeerToPeerTest.suites = ["wallet"];
+runPeerToPeerPushTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
index cafcce79e..88e67a8bb 100644
--- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
@@ -73,7 +73,8 @@ import { runPaymentDemoTest } from "./test-payment-on-demo";
import { runPaymentTransientTest } from "./test-payment-transient";
import { runPaymentZeroTest } from "./test-payment-zero.js";
import { runPaywallFlowTest } from "./test-paywall-flow";
-import { runPeerToPeerTest } from "./test-peer-to-peer.js";
+import { runPeerToPeerPullTest } from "./test-peer-to-peer-pull.js";
+import { runPeerToPeerPushTest } from "./test-peer-to-peer-push.js";
import { runRefundTest } from "./test-refund";
import { runRefundAutoTest } from "./test-refund-auto";
import { runRefundGoneTest } from "./test-refund-gone";
@@ -154,7 +155,8 @@ const allTests: TestMainFunction[] = [
runPaymentZeroTest,
runPayPaidTest,
runPaywallFlowTest,
- runPeerToPeerTest,
+ runPeerToPeerPushTest,
+ runPeerToPeerPullTest,
runRefundAutoTest,
runRefundGoneTest,
runRefundIncrementalTest,
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 099bf09fe..2f39f7806 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -33,11 +33,11 @@ import {
BlindedDenominationSignature,
bufferForUint32,
buildSigPS,
- bytesToString,
CoinDepositPermission,
CoinEnvelope,
createHashContext,
decodeCrock,
+ decryptContractForDeposit,
decryptContractForMerge,
DenomKeyType,
DepositInfo,
@@ -47,6 +47,7 @@ import {
eddsaSign,
eddsaVerify,
encodeCrock,
+ encryptContractForDeposit,
encryptContractForMerge,
ExchangeProtocolVersion,
getRandomBytes,
@@ -85,17 +86,23 @@ import { DenominationRecord } from "../db.js";
import {
CreateRecoupRefreshReqRequest,
CreateRecoupReqRequest,
+ DecryptContractForDepositRequest,
+ DecryptContractForDepositResponse,
DecryptContractRequest,
DecryptContractResponse,
DerivedRefreshSession,
DerivedTipPlanchet,
DeriveRefreshSessionRequest,
DeriveTipRequest,
+ EncryptContractForDepositRequest,
+ EncryptContractForDepositResponse,
EncryptContractRequest,
EncryptContractResponse,
EncryptedContract,
SignPurseMergeRequest,
SignPurseMergeResponse,
+ SignReservePurseCreateRequest,
+ SignReservePurseCreateResponse,
SignTrackTransactionRequest,
} from "./cryptoTypes.js";
@@ -205,7 +212,19 @@ export interface TalerCryptoInterface {
req: DecryptContractRequest,
): Promise<DecryptContractResponse>;
+ encryptContractForDeposit(
+ req: EncryptContractForDepositRequest,
+ ): Promise<EncryptContractForDepositResponse>;
+
+ decryptContractForDeposit(
+ req: DecryptContractForDepositRequest,
+ ): Promise<DecryptContractForDepositResponse>;
+
signPurseMerge(req: SignPurseMergeRequest): Promise<SignPurseMergeResponse>;
+
+ signReservePurseCreate(
+ req: SignReservePurseCreateRequest,
+ ): Promise<SignReservePurseCreateResponse>;
}
/**
@@ -362,6 +381,21 @@ export const nullCrypto: TalerCryptoInterface = {
): Promise<SignPurseMergeResponse> {
throw new Error("Function not implemented.");
},
+ encryptContractForDeposit: function (
+ req: EncryptContractForDepositRequest,
+ ): Promise<EncryptContractForDepositResponse> {
+ throw new Error("Function not implemented.");
+ },
+ decryptContractForDeposit: function (
+ req: DecryptContractForDepositRequest,
+ ): Promise<DecryptContractForDepositResponse> {
+ throw new Error("Function not implemented.");
+ },
+ signReservePurseCreate: function (
+ req: SignReservePurseCreateRequest,
+ ): Promise<SignReservePurseCreateResponse> {
+ throw new Error("Function not implemented.");
+ },
};
export type WithArg<X> = X extends (req: infer T) => infer R
@@ -1047,7 +1081,8 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
if (depositInfo.requiredMinimumAge != null) {
s.minimum_age_sig = minimumAgeSig;
- s.age_commitment = depositInfo.ageCommitmentProof?.commitment.publicKeys;
+ s.age_commitment =
+ depositInfo.ageCommitmentProof?.commitment.publicKeys;
} else if (depositInfo.ageCommitmentProof) {
(s as any).h_age_commitment = hAgeCommitment;
}
@@ -1389,6 +1424,43 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
mergePriv: encodeCrock(res.mergePriv),
};
},
+ async encryptContractForDeposit(
+ tci: TalerCryptoInterfaceR,
+ req: EncryptContractForDepositRequest,
+ ): Promise<EncryptContractForDepositResponse> {
+ const contractKeyPair = await this.createEddsaKeypair(tci, {});
+ const enc = await encryptContractForDeposit(
+ decodeCrock(req.pursePub),
+ decodeCrock(contractKeyPair.priv),
+ req.contractTerms,
+ );
+ const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
+ .put(hash(enc))
+ .put(decodeCrock(contractKeyPair.pub))
+ .build();
+ const sig = eddsaSign(sigBlob, decodeCrock(req.pursePriv));
+ return {
+ econtract: {
+ contract_pub: contractKeyPair.pub,
+ econtract: encodeCrock(enc),
+ econtract_sig: encodeCrock(sig),
+ },
+ contractPriv: contractKeyPair.priv,
+ };
+ },
+ async decryptContractForDeposit(
+ tci: TalerCryptoInterfaceR,
+ req: DecryptContractForDepositRequest,
+ ): Promise<DecryptContractForDepositResponse> {
+ const res = await decryptContractForDeposit(
+ decodeCrock(req.ciphertext),
+ decodeCrock(req.pursePub),
+ decodeCrock(req.contractPriv),
+ );
+ return {
+ contractTerms: res.contractTerms,
+ };
+ },
async signPurseMerge(
tci: TalerCryptoInterfaceR,
req: SignPurseMergeRequest,
@@ -1431,6 +1503,70 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
accountSig: reserveSigResp.sig,
};
},
+ async signReservePurseCreate(
+ tci: TalerCryptoInterfaceR,
+ req: SignReservePurseCreateRequest,
+ ): Promise<SignReservePurseCreateResponse> {
+ const mergeSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_MERGE)
+ .put(timestampRoundedToBuffer(req.mergeTimestamp))
+ .put(decodeCrock(req.pursePub))
+ .put(hashTruncate32(stringToBytes(req.reservePayto + "\0")))
+ .build();
+ const mergeSigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(mergeSigBlob),
+ priv: req.mergePriv,
+ });
+
+ logger.info(`payto URI: ${req.reservePayto}`);
+ logger.info(
+ `signing WALLET_PURSE_MERGE over ${encodeCrock(mergeSigBlob)}`,
+ );
+
+ const reserveSigBlob = buildSigPS(
+ TalerSignaturePurpose.WALLET_ACCOUNT_MERGE,
+ )
+ .put(timestampRoundedToBuffer(req.purseExpiration))
+ .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
+ .put(amountToBuffer(Amounts.parseOrThrow(req.purseFee)))
+ .put(decodeCrock(req.contractTermsHash))
+ .put(decodeCrock(req.pursePub))
+ .put(timestampRoundedToBuffer(req.mergeTimestamp))
+ // FIXME: put in min_age
+ .put(bufferForUint32(0))
+ .put(bufferForUint32(req.flags))
+ .build();
+
+ logger.info(
+ `signing WALLET_ACCOUNT_MERGE over ${encodeCrock(reserveSigBlob)}`,
+ );
+
+ const reserveSigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(reserveSigBlob),
+ priv: req.reservePriv,
+ });
+
+ const mergePub = encodeCrock(eddsaGetPublic(decodeCrock(req.mergePriv)));
+
+ const purseSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_CREATE)
+ .put(timestampRoundedToBuffer(req.purseExpiration))
+ .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
+ .put(decodeCrock(req.contractTermsHash))
+ .put(decodeCrock(mergePub))
+ // FIXME: add age!
+ .put(bufferForUint32(0))
+ .build();
+
+ const purseSigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(purseSigBlob),
+ priv: req.pursePriv,
+ });
+
+ return {
+ mergeSig: mergeSigResp.sig,
+ accountSig: reserveSigResp.sig,
+ purseSig: purseSigResp.sig,
+ };
+ },
};
function amountToBuffer(amount: AmountJson): Uint8Array {
diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
index 6f4a5fa95..6e0e01627 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
@@ -187,6 +187,19 @@ export interface EncryptContractResponse {
contractPriv: string;
}
+export interface EncryptContractForDepositRequest {
+ contractTerms: any;
+
+ pursePub: string;
+ pursePriv: string;
+}
+
+export interface EncryptContractForDepositResponse {
+ econtract: EncryptedContract;
+
+ contractPriv: string;
+}
+
export interface DecryptContractRequest {
ciphertext: string;
pursePub: string;
@@ -198,6 +211,16 @@ export interface DecryptContractResponse {
mergePriv: string;
}
+export interface DecryptContractForDepositRequest {
+ ciphertext: string;
+ pursePub: string;
+ contractPriv: string;
+}
+
+export interface DecryptContractForDepositResponse {
+ contractTerms: any;
+}
+
export interface SignPurseMergeRequest {
mergeTimestamp: TalerProtocolTimestamp;
@@ -227,6 +250,47 @@ export interface SignPurseMergeResponse {
* Signature made by the purse's merge private key.
*/
mergeSig: string;
-
+
+ accountSig: string;
+}
+
+export interface SignReservePurseCreateRequest {
+ mergeTimestamp: TalerProtocolTimestamp;
+
+ pursePub: string;
+
+ pursePriv: string;
+
+ reservePayto: string;
+
+ reservePriv: string;
+
+ mergePriv: string;
+
+ purseExpiration: TalerProtocolTimestamp;
+
+ purseAmount: AmountString;
+ purseFee: AmountString;
+
+ contractTermsHash: string;
+
+ /**
+ * Flags.
+ */
+ flags: WalletAccountMergeFlags;
+}
+
+/**
+ * Response with signatures needed for creation of a purse
+ * from a reserve for a PULL payment.
+ */
+export interface SignReservePurseCreateResponse {
+ /**
+ * Signature made by the purse's merge private key.
+ */
+ mergeSig: string;
+
accountSig: string;
+
+ purseSig: string;
}
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index a34a09f75..266197eb5 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -393,7 +393,6 @@ export interface ExchangeDetailsRecord {
wireInfo: WireInfo;
}
-
export interface ExchangeDetailsPointer {
masterPublicKey: string;
@@ -922,7 +921,6 @@ export interface RefreshSessionRecord {
norevealIndex?: number;
}
-
export enum RefundState {
Failed = "failed",
Applied = "applied",
@@ -1186,9 +1184,9 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState";
*/
export type ConfigRecord =
| {
- key: typeof WALLET_BACKUP_STATE_KEY;
- value: WalletBackupConfState;
- }
+ key: typeof WALLET_BACKUP_STATE_KEY;
+ value: WalletBackupConfState;
+ }
| { key: "currencyDefaultsApplied"; value: boolean };
export interface WalletBackupConfState {
@@ -1405,17 +1403,17 @@ export enum BackupProviderStateTag {
export type BackupProviderState =
| {
- tag: BackupProviderStateTag.Provisional;
- }
+ tag: BackupProviderStateTag.Provisional;
+ }
| {
- tag: BackupProviderStateTag.Ready;
- nextBackupTimestamp: TalerProtocolTimestamp;
- }
+ tag: BackupProviderStateTag.Ready;
+ nextBackupTimestamp: TalerProtocolTimestamp;
+ }
| {
- tag: BackupProviderStateTag.Retrying;
- retryInfo: RetryInfo;
- lastError?: TalerErrorDetail;
- };
+ tag: BackupProviderStateTag.Retrying;
+ retryInfo: RetryInfo;
+ lastError?: TalerErrorDetail;
+ };
export interface BackupProviderTerms {
supportedProtocolVersion: string;
@@ -1625,6 +1623,36 @@ export interface PeerPushPaymentInitiationRecord {
timestampCreated: TalerProtocolTimestamp;
}
+export interface PeerPullPaymentInitiationRecord {
+ /**
+ * What exchange are we using for the payment request?
+ */
+ exchangeBaseUrl: string;
+
+ /**
+ * Amount requested.
+ */
+ amount: AmountString;
+
+ /**
+ * Purse public key. Used as the primary key to look
+ * up this record.
+ */
+ pursePub: string;
+
+ /**
+ * Purse private key.
+ */
+ pursePriv: string;
+
+ /**
+ * Contract terms for the other party.
+ *
+ * FIXME: Nail down type!
+ */
+ contractTerms: any;
+}
+
/**
* Record for a push P2P payment that this wallet was offered.
*
@@ -1825,6 +1853,15 @@ export const WalletStoresV1 = {
]),
},
),
+ peerPullPaymentInitiation: describeStore(
+ describeContents<PeerPullPaymentInitiationRecord>(
+ "peerPushPaymentInitiation",
+ {
+ keyPath: "pursePub",
+ },
+ ),
+ {},
+ ),
};
export interface MetaConfigRecord {
diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
index 4d2f2bb5f..eca319a29 100644
--- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts
+++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
@@ -37,7 +37,10 @@ import {
eddsaGetPublic,
encodeCrock,
ExchangePurseMergeRequest,
+ ExchangeReservePurseRequest,
getRandomBytes,
+ InitiatePeerPullPaymentRequest,
+ InitiatePeerPullPaymentResponse,
InitiatePeerPushPaymentRequest,
InitiatePeerPushPaymentResponse,
j2s,
@@ -370,24 +373,12 @@ export function talerPaytoFromExchangeReserve(
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
}
-export async function acceptPeerPushPayment(
+async function getMergeReserveInfo(
ws: InternalWalletState,
- req: AcceptPeerPushPaymentRequest,
-) {
- const peerInc = await ws.db
- .mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming }))
- .runReadOnly(async (tx) => {
- return tx.peerPushPaymentIncoming.get(req.peerPushPaymentIncomingId);
- });
-
- if (!peerInc) {
- throw Error(
- `can't accept unknown incoming p2p push payment (${req.peerPushPaymentIncomingId})`,
- );
- }
-
- const amount = Amounts.parseOrThrow(peerInc.contractTerms.amount);
-
+ req: {
+ exchangeBaseUrl: string;
+ },
+): Promise<MergeReserveInfo> {
// We have to eagerly create the key pair outside of the transaction,
// due to the async crypto API.
const newReservePair = await ws.cryptoApi.createEddsaKeypair({});
@@ -398,7 +389,7 @@ export async function acceptPeerPushPayment(
withdrawalGroups: x.withdrawalGroups,
}))
.runReadWrite(async (tx) => {
- const ex = await tx.exchanges.get(peerInc.exchangeBaseUrl);
+ const ex = await tx.exchanges.get(req.exchangeBaseUrl);
checkDbInvariant(!!ex);
if (ex.currentMergeReserveInfo) {
return ex.currentMergeReserveInfo;
@@ -411,6 +402,31 @@ export async function acceptPeerPushPayment(
return ex.currentMergeReserveInfo;
});
+ return mergeReserveInfo;
+}
+
+export async function acceptPeerPushPayment(
+ ws: InternalWalletState,
+ req: AcceptPeerPushPaymentRequest,
+) {
+ const peerInc = await ws.db
+ .mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming }))
+ .runReadOnly(async (tx) => {
+ return tx.peerPushPaymentIncoming.get(req.peerPushPaymentIncomingId);
+ });
+
+ if (!peerInc) {
+ throw Error(
+ `can't accept unknown incoming p2p push payment (${req.peerPushPaymentIncomingId})`,
+ );
+ }
+
+ const amount = Amounts.parseOrThrow(peerInc.contractTerms.amount);
+
+ const mergeReserveInfo = await getMergeReserveInfo(ws, {
+ exchangeBaseUrl: peerInc.exchangeBaseUrl,
+ });
+
const mergeTimestamp = TalerProtocolTimestamp.now();
const reservePayto = talerPaytoFromExchangeReserve(
@@ -461,3 +477,115 @@ export async function acceptPeerPushPayment(
},
});
}
+
+export async function initiatePeerRequestForPay(
+ ws: InternalWalletState,
+ req: InitiatePeerPullPaymentRequest,
+): Promise<InitiatePeerPullPaymentResponse> {
+ const mergeReserveInfo = await getMergeReserveInfo(ws, {
+ exchangeBaseUrl: req.exchangeBaseUrl,
+ });
+
+ const mergeTimestamp = TalerProtocolTimestamp.now();
+
+ const pursePair = await ws.cryptoApi.createEddsaKeypair({});
+ const mergePair = await ws.cryptoApi.createEddsaKeypair({});
+
+ const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ days: 2 }),
+ ),
+ );
+
+ const reservePayto = talerPaytoFromExchangeReserve(
+ req.exchangeBaseUrl,
+ mergeReserveInfo.reservePub,
+ );
+
+ const contractTerms = {
+ ...req.partialContractTerms,
+ amount: req.amount,
+ purse_expiration: purseExpiration,
+ };
+
+ const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
+ contractTerms,
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ });
+
+ const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
+
+ const purseFee = Amounts.stringify(
+ Amounts.getZero(Amounts.parseOrThrow(req.amount).currency),
+ );
+
+ const sigRes = await ws.cryptoApi.signReservePurseCreate({
+ contractTermsHash: hContractTerms,
+ flags: WalletAccountMergeFlags.CreateWithPurseFee,
+ mergePriv: mergePair.priv,
+ mergeTimestamp: mergeTimestamp,
+ purseAmount: req.amount,
+ purseExpiration: purseExpiration,
+ purseFee: purseFee,
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ reservePayto,
+ reservePriv: mergeReserveInfo.reservePriv,
+ });
+
+ await ws.db
+ .mktx((x) => ({
+ peerPullPaymentInitiation: x.peerPullPaymentInitiation,
+ }))
+ .runReadWrite(async (tx) => {
+ await tx.peerPullPaymentInitiation.put({
+ amount: req.amount,
+ contractTerms,
+ exchangeBaseUrl: req.exchangeBaseUrl,
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ });
+ });
+
+ const reservePurseReqBody: ExchangeReservePurseRequest = {
+ merge_sig: sigRes.mergeSig,
+ merge_timestamp: mergeTimestamp,
+ h_contract_terms: hContractTerms,
+ merge_pub: mergePair.pub,
+ min_age: 0,
+ purse_expiration: purseExpiration,
+ purse_fee: purseFee,
+ purse_pub: pursePair.pub,
+ purse_sig: sigRes.purseSig,
+ purse_value: req.amount,
+ reserve_sig: sigRes.accountSig,
+ econtract: econtractResp.econtract,
+ };
+
+ logger.info(`reserve purse request: ${j2s(reservePurseReqBody)}`);
+
+ const reservePurseMergeUrl = new URL(
+ `reserves/${mergeReserveInfo.reservePub}/purse`,
+ req.exchangeBaseUrl,
+ );
+
+ const httpResp = await ws.http.postJson(
+ reservePurseMergeUrl.href,
+ reservePurseReqBody,
+ );
+
+ const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
+
+ logger.info(`reserve merge response: ${j2s(resp)}`);
+
+ // FIXME: Now create a withdrawal operation!
+
+ return {
+ talerUri: constructPayPushUri({
+ exchangeBaseUrl: req.exchangeBaseUrl,
+ contractPriv: econtractResp.contractPriv,
+ }),
+ };
+}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index cc9e98f8c..14c40a8db 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -27,6 +27,7 @@ import {
AcceptExchangeTosRequest,
AcceptManualWithdrawalRequest,
AcceptManualWithdrawalResult,
+ AcceptPeerPullPaymentRequest,
AcceptPeerPushPaymentRequest,
AcceptTipRequest,
AcceptWithdrawalResponse,
@@ -35,6 +36,8 @@ import {
ApplyRefundResponse,
BackupRecovery,
BalancesResponse,
+ CheckPeerPullPaymentRequest,
+ CheckPeerPullPaymentResponse,
CheckPeerPushPaymentRequest,
CheckPeerPushPaymentResponse,
CoinDumpJson,
@@ -49,6 +52,8 @@ import {
GetExchangeTosResult,
GetWithdrawalDetailsForAmountRequest,
GetWithdrawalDetailsForUriRequest,
+ InitiatePeerPullPaymentRequest,
+ InitiatePeerPullPaymentResponse,
InitiatePeerPushPaymentRequest,
InitiatePeerPushPaymentResponse,
IntegrationTestArgs,
@@ -126,6 +131,9 @@ export enum WalletApiOperation {
InitiatePeerPushPayment = "initiatePeerPushPayment",
CheckPeerPushPayment = "checkPeerPushPayment",
AcceptPeerPushPayment = "acceptPeerPushPayment",
+ InitiatePeerPullPayment = "initiatePeerPullPayment",
+ CheckPeerPullPayment = "checkPeerPullPayment",
+ AcceptPeerPullPayment = "acceptPeerPullPayment",
}
export type WalletOperations = {
@@ -297,6 +305,18 @@ export type WalletOperations = {
request: AcceptPeerPushPaymentRequest;
response: {};
};
+ [WalletApiOperation.InitiatePeerPullPayment]: {
+ request: InitiatePeerPullPaymentRequest;
+ response: InitiatePeerPullPaymentResponse;
+ };
+ [WalletApiOperation.CheckPeerPullPayment]: {
+ request: CheckPeerPullPaymentRequest;
+ response: CheckPeerPullPaymentResponse;
+ };
+ [WalletApiOperation.AcceptPeerPullPayment]: {
+ request: AcceptPeerPullPaymentRequest;
+ response: {};
+ };
};
export type RequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 593d2e0ff..0d5918886 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -32,6 +32,7 @@ import {
codecForAcceptBankIntegratedWithdrawalRequest,
codecForAcceptExchangeTosRequest,
codecForAcceptManualWithdrawalRequet,
+ codecForAcceptPeerPullPaymentRequest,
codecForAcceptPeerPushPaymentRequest,
codecForAcceptTipRequest,
codecForAddExchangeRequest,
@@ -50,6 +51,7 @@ import {
codecForGetWithdrawalDetailsForAmountRequest,
codecForGetWithdrawalDetailsForUri,
codecForImportDbRequest,
+ codecForInitiatePeerPullPaymentRequest,
codecForInitiatePeerPushPaymentRequest,
codecForIntegrationTestArgs,
codecForListKnownBankAccounts,
@@ -150,6 +152,7 @@ import {
import {
acceptPeerPushPayment,
checkPeerPushPayment,
+ initiatePeerRequestForPay,
initiatePeerToPeerPush,
} from "./operations/peer-to-peer.js";
import { getPendingOperations } from "./operations/pending.js";
@@ -455,11 +458,20 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
for (const c of builtinAuditors) {
await tx.auditorTrustStore.put(c);
}
- for (const url of builtinExchanges) {
- await updateExchangeFromUrl(ws, url, { forceNow: true });
- }
}
+ // FIXME: make sure exchanges are added transactionally to
+ // DB in first-time default application
});
+
+ for (const url of builtinExchanges) {
+ try {
+ await updateExchangeFromUrl(ws, url, { forceNow: true });
+ } catch (e) {
+ logger.warn(
+ `could not update builtin exchange ${url} during wallet initialization`,
+ );
+ }
+ }
}
async function getExchangeTos(
@@ -568,8 +580,9 @@ async function getExchanges(
continue;
}
- const denominations = await tx.denominations.indexes
- .byExchangeBaseUrl.iter(r.baseUrl).toArray();
+ const denominations = await tx.denominations.indexes.byExchangeBaseUrl
+ .iter(r.baseUrl)
+ .toArray();
if (!denominations) {
continue;
@@ -1030,6 +1043,10 @@ async function dispatchRequestInternal(
await acceptPeerPushPayment(ws, req);
return {};
}
+ case "initiatePeerPullPayment": {
+ const req = codecForInitiatePeerPullPaymentRequest().decode(payload);
+ return await initiatePeerRequestForPay(ws, req);
+ }
}
throw TalerError.fromDetail(
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,