peer-to-peer pull payments MVP
p2p pull wip
This commit is contained in:
parent
4ca38113ab
commit
f3ff5a7225
@ -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),
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}`;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"];
|
@ -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"];
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
@ -230,3 +253,44 @@ export interface SignPurseMergeResponse {
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -37,7 +37,10 @@ import {
|
||||
eddsaGetPublic,
|
||||
encodeCrock,
|
||||
ExchangePurseMergeRequest,
|
||||
ExchangeReservePurseRequest,
|
||||
getRandomBytes,
|
||||
InitiatePeerPullPaymentRequest,
|
||||
InitiatePeerPullPaymentResponse,
|
||||
InitiatePeerPushPaymentRequest,
|
||||
InitiatePeerPushPaymentResponse,
|
||||
j2s,
|
||||
@ -370,6 +373,38 @@ export function talerPaytoFromExchangeReserve(
|
||||
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
|
||||
}
|
||||
|
||||
async function getMergeReserveInfo(
|
||||
ws: InternalWalletState,
|
||||
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({});
|
||||
|
||||
const mergeReserveInfo: MergeReserveInfo = await ws.db
|
||||
.mktx((x) => ({
|
||||
exchanges: x.exchanges,
|
||||
withdrawalGroups: x.withdrawalGroups,
|
||||
}))
|
||||
.runReadWrite(async (tx) => {
|
||||
const ex = await tx.exchanges.get(req.exchangeBaseUrl);
|
||||
checkDbInvariant(!!ex);
|
||||
if (ex.currentMergeReserveInfo) {
|
||||
return ex.currentMergeReserveInfo;
|
||||
}
|
||||
await tx.exchanges.put(ex);
|
||||
ex.currentMergeReserveInfo = {
|
||||
reservePriv: newReservePair.priv,
|
||||
reservePub: newReservePair.pub,
|
||||
};
|
||||
return ex.currentMergeReserveInfo;
|
||||
});
|
||||
|
||||
return mergeReserveInfo;
|
||||
}
|
||||
|
||||
export async function acceptPeerPushPayment(
|
||||
ws: InternalWalletState,
|
||||
req: AcceptPeerPushPaymentRequest,
|
||||
@ -388,28 +423,9 @@ export async function acceptPeerPushPayment(
|
||||
|
||||
const amount = Amounts.parseOrThrow(peerInc.contractTerms.amount);
|
||||
|
||||
// We have to eagerly create the key pair outside of the transaction,
|
||||
// due to the async crypto API.
|
||||
const newReservePair = await ws.cryptoApi.createEddsaKeypair({});
|
||||
|
||||
const mergeReserveInfo: MergeReserveInfo = await ws.db
|
||||
.mktx((x) => ({
|
||||
exchanges: x.exchanges,
|
||||
withdrawalGroups: x.withdrawalGroups,
|
||||
}))
|
||||
.runReadWrite(async (tx) => {
|
||||
const ex = await tx.exchanges.get(peerInc.exchangeBaseUrl);
|
||||
checkDbInvariant(!!ex);
|
||||
if (ex.currentMergeReserveInfo) {
|
||||
return ex.currentMergeReserveInfo;
|
||||
}
|
||||
await tx.exchanges.put(ex);
|
||||
ex.currentMergeReserveInfo = {
|
||||
reservePriv: newReservePair.priv,
|
||||
reservePub: newReservePair.pub,
|
||||
};
|
||||
return ex.currentMergeReserveInfo;
|
||||
});
|
||||
const mergeReserveInfo = await getMergeReserveInfo(ws, {
|
||||
exchangeBaseUrl: peerInc.exchangeBaseUrl,
|
||||
});
|
||||
|
||||
const mergeTimestamp = TalerProtocolTimestamp.now();
|
||||
|
||||
@ -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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -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<
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user