gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: peer-to-peer pull payments MV


From: gnunet
Subject: [taler-wallet-core] branch master updated: peer-to-peer pull payments MVP
Date: Tue, 23 Aug 2022 20:35:26 +0200

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

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new f3ff5a72 peer-to-peer pull payments MVP
f3ff5a72 is described below

commit f3ff5a72257dda27cab555f8b8d921d45bfc3e4b
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Aug 23 11:29:45 2022 +0200

    peer-to-peer pull payments MVP
    
    p2p pull wip
---
 packages/taler-util/src/talerCrypto.ts             |  51 +++++--
 packages/taler-util/src/talerTypes.ts              |  71 +++++++++
 packages/taler-util/src/taleruri.ts                |  66 ++++++++-
 packages/taler-util/src/walletTypes.ts             |  81 ++++++++--
 packages/taler-wallet-cli/src/harness/harness.ts   |   2 +-
 ...t-peer-to-peer.ts => test-peer-to-peer-pull.ts} |  19 +--
 ...t-peer-to-peer.ts => test-peer-to-peer-push.ts} |   4 +-
 .../src/integrationtests/testrunner.ts             |   6 +-
 .../src/crypto/cryptoImplementation.ts             | 140 +++++++++++++++++-
 .../taler-wallet-core/src/crypto/cryptoTypes.ts    |  66 ++++++++-
 packages/taler-wallet-core/src/db.ts               |  65 ++++++--
 .../src/operations/peer-to-peer.ts                 | 164 ++++++++++++++++++---
 packages/taler-wallet-core/src/wallet-api-types.ts |  20 +++
 packages/taler-wallet-core/src/wallet.ts           |  27 +++-
 14 files changed, 696 insertions(+), 86 deletions(-)

diff --git a/packages/taler-util/src/talerCrypto.ts 
b/packages/taler-util/src/talerCrypto.ts
index 38bb5ad0..d7734707 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 d4de8c37..ee2dee93 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 e3bd120f..e7d66d7d 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 7b482c60..3a415b22 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 c735c995..33f677d9 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.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
similarity index 81%
copy from packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts
copy to packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
index c22258bc..e78bd5a2 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
@@ -27,7 +27,7 @@ import {
 /**
  * Run test for basic, bank-integrated withdrawal and payment.
  */
-export async function runPeerToPeerTest(t: GlobalTestState) {
+export async function runPeerToPeerPullTest(t: GlobalTestState) {
   // Set up test environment
 
   const { wallet, bank, exchange, merchant } =
@@ -40,8 +40,9 @@ export async function runPeerToPeerTest(t: GlobalTestState) {
   await wallet.runUntilDone();
 
   const resp = await wallet.client.call(
-    WalletApiOperation.InitiatePeerPushPayment,
+    WalletApiOperation.InitiatePeerPullPayment,
     {
+      exchangeBaseUrl: exchange.baseUrl,
       amount: "TESTKUDOS:5",
       partialContractTerms: {
         summary: "Hello World",
@@ -49,27 +50,21 @@ export async function runPeerToPeerTest(t: GlobalTestState) 
{
     },
   );
 
-  console.log(resp);
-
   const checkResp = await wallet.client.call(
-    WalletApiOperation.CheckPeerPushPayment,
+    WalletApiOperation.CheckPeerPullPayment,
     {
       talerUri: resp.talerUri,
     },
   );
 
-  console.log(checkResp);
-
   const acceptResp = await wallet.client.call(
-    WalletApiOperation.AcceptPeerPushPayment,
+    WalletApiOperation.AcceptPeerPullPayment,
     {
-      peerPushPaymentIncomingId: checkResp.peerPushPaymentIncomingId,
+      peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId,
     },
   );
 
-  console.log(acceptResp);
-
   await wallet.runUntilDone();
 }
 
-runPeerToPeerTest.suites = ["wallet"];
+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
similarity index 94%
rename from packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts
rename to 
packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts
index c22258bc..11360f6e 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 cafcce79..88e67a8b 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 099bf09f..2f39f780 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 6f4a5fa9..6e0e0162 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 a34a09f7..266197eb 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 4d2f2bb5..eca319a2 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 cc9e98f8..14c40a8d 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 593d2e0f..0d591888 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,

-- 
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]