wallet-core: P2P push payments (still incomplete)
This commit is contained in:
parent
05cdbfb534
commit
b214934b75
@ -773,6 +773,8 @@ export enum TalerSignaturePurpose {
|
||||
WALLET_COIN_LINK = 1204,
|
||||
WALLET_COIN_RECOUP_REFRESH = 1206,
|
||||
WALLET_AGE_ATTESTATION = 1207,
|
||||
WALLET_PURSE_CREATE = 1210,
|
||||
WALLET_PURSE_DEPOSIT = 1211,
|
||||
EXCHANGE_CONFIRM_RECOUP = 1039,
|
||||
EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
|
||||
ANASTASIS_POLICY_UPLOAD = 1400,
|
||||
|
@ -565,8 +565,8 @@ export interface MerchantAbortPayRefundDetails {
|
||||
refund_amount: string;
|
||||
|
||||
/**
|
||||
* Fee for the refund.
|
||||
*/
|
||||
* Fee for the refund.
|
||||
*/
|
||||
refund_fee: string;
|
||||
|
||||
/**
|
||||
@ -1794,3 +1794,41 @@ export const codecForDepositSuccess = (): Codec<DepositSuccess> =>
|
||||
.property("exchange_timestamp", codecForTimestamp)
|
||||
.property("transaction_base_url", codecOptional(codecForString()))
|
||||
.build("DepositSuccess");
|
||||
|
||||
export interface PurseDeposit {
|
||||
/**
|
||||
* Amount to be deposited, can be a fraction of the
|
||||
* coin's total value.
|
||||
*/
|
||||
amount: AmountString;
|
||||
|
||||
/**
|
||||
* Hash of denomination RSA key with which the coin is signed.
|
||||
*/
|
||||
denom_pub_hash: HashCodeString;
|
||||
|
||||
/**
|
||||
* Exchange's unblinded RSA signature of the coin.
|
||||
*/
|
||||
ub_sig: UnblindedSignature;
|
||||
|
||||
/**
|
||||
* Age commitment hash for the coin, if the denomination is age-restricted.
|
||||
*/
|
||||
h_age_commitment?: HashCodeString;
|
||||
|
||||
// FIXME-Oec: proof of age is missing.
|
||||
|
||||
/**
|
||||
* Signature over TALER_PurseDepositSignaturePS
|
||||
* of purpose TALER_SIGNATURE_WALLET_PURSE_DEPOSIT
|
||||
* made by the customer with the
|
||||
* coin's private key.
|
||||
*/
|
||||
coin_sig: EddsaSignatureString;
|
||||
|
||||
/**
|
||||
* Public key of the coin being deposited into the purse.
|
||||
*/
|
||||
coin_pub: EddsaPublicKeyString;
|
||||
}
|
||||
|
@ -32,10 +32,7 @@ import {
|
||||
codecForAmountJson,
|
||||
codecForAmountString,
|
||||
} from "./amounts.js";
|
||||
import {
|
||||
codecForTimestamp,
|
||||
TalerProtocolTimestamp,
|
||||
} from "./time.js";
|
||||
import { codecForTimestamp, TalerProtocolTimestamp } from "./time.js";
|
||||
import {
|
||||
buildCodecForObject,
|
||||
codecForString,
|
||||
@ -1230,15 +1227,14 @@ export interface ForcedCoinSel {
|
||||
}
|
||||
|
||||
export interface TestPayResult {
|
||||
payCoinSelection: PayCoinSelection,
|
||||
payCoinSelection: PayCoinSelection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Result of selecting coins, contains the exchange, and selected
|
||||
* coins with their denomination.
|
||||
*/
|
||||
export interface PayCoinSelection {
|
||||
export interface PayCoinSelection {
|
||||
/**
|
||||
* Amount requested by the merchant.
|
||||
*/
|
||||
@ -1263,4 +1259,19 @@ export interface TestPayResult {
|
||||
* How much of the deposit fees is the customer paying?
|
||||
*/
|
||||
customerDepositFees: AmountJson;
|
||||
}
|
||||
}
|
||||
|
||||
export interface InitiatePeerPushPaymentRequest {
|
||||
amount: AmountString;
|
||||
}
|
||||
|
||||
export interface InitiatePeerPushPaymentResponse {
|
||||
pursePub: string;
|
||||
mergePriv: string;
|
||||
}
|
||||
|
||||
export const codecForInitiatePeerPushPaymentRequest =
|
||||
(): Codec<InitiatePeerPushPaymentRequest> =>
|
||||
buildCodecForObject<InitiatePeerPushPaymentRequest>()
|
||||
.property("amount", codecForAmountString())
|
||||
.build("InitiatePeerPushPaymentRequest");
|
||||
|
@ -1296,6 +1296,36 @@ export class ExchangeService implements ExchangeServiceInterface {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await runCommand(
|
||||
this.globalState,
|
||||
"exchange-offline",
|
||||
"taler-exchange-offline",
|
||||
[
|
||||
"-c",
|
||||
this.configFilename,
|
||||
"global-fee",
|
||||
// year
|
||||
"now",
|
||||
// history fee
|
||||
`${this.exchangeConfig.currency}:0.01`,
|
||||
// kyc fee
|
||||
`${this.exchangeConfig.currency}:0.01`,
|
||||
// account fee
|
||||
`${this.exchangeConfig.currency}:0.01`,
|
||||
// purse fee
|
||||
`${this.exchangeConfig.currency}:0.01`,
|
||||
// purse timeout
|
||||
"1h",
|
||||
// kyc timeout
|
||||
"1h",
|
||||
// history expiration
|
||||
"1year",
|
||||
// free purses per account
|
||||
"5",
|
||||
"upload",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
async revokeDenomination(denomPubHash: string) {
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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,
|
||||
makeTestPayment,
|
||||
} from "../harness/helpers.js";
|
||||
|
||||
/**
|
||||
* Run test for basic, bank-integrated withdrawal and payment.
|
||||
*/
|
||||
export async function runPeerToPeerTest(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.InitiatePeerPushPayment,
|
||||
{
|
||||
amount: "TESTKUDOS:5",
|
||||
},
|
||||
);
|
||||
|
||||
console.log(resp);
|
||||
}
|
||||
|
||||
runPeerToPeerTest.suites = ["wallet"];
|
@ -73,6 +73,7 @@ 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 { runRefundTest } from "./test-refund";
|
||||
import { runRefundAutoTest } from "./test-refund-auto";
|
||||
import { runRefundGoneTest } from "./test-refund-gone";
|
||||
@ -153,6 +154,7 @@ const allTests: TestMainFunction[] = [
|
||||
runPaymentZeroTest,
|
||||
runPayPaidTest,
|
||||
runPaywallFlowTest,
|
||||
runPeerToPeerTest,
|
||||
runRefundAutoTest,
|
||||
runRefundGoneTest,
|
||||
runRefundIncrementalTest,
|
||||
|
@ -24,33 +24,44 @@
|
||||
* Imports.
|
||||
*/
|
||||
|
||||
// FIXME: Crypto should not use DB Types!
|
||||
import {
|
||||
AgeCommitmentProof,
|
||||
AgeRestriction,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
AmountString,
|
||||
BlindedDenominationSignature,
|
||||
bufferForUint32,
|
||||
buildSigPS,
|
||||
CoinDepositPermission,
|
||||
CoinEnvelope,
|
||||
createEddsaKeyPair,
|
||||
createHashContext,
|
||||
decodeCrock,
|
||||
DenomKeyType,
|
||||
DepositInfo,
|
||||
ecdheGetPublic,
|
||||
eddsaGetPublic,
|
||||
EddsaPublicKeyString,
|
||||
eddsaSign,
|
||||
eddsaVerify,
|
||||
encodeCrock,
|
||||
ExchangeProtocolVersion,
|
||||
getRandomBytes,
|
||||
hash,
|
||||
HashCodeString,
|
||||
hashCoinEv,
|
||||
hashCoinEvInner,
|
||||
hashCoinPub,
|
||||
hashDenomPub,
|
||||
hashTruncate32,
|
||||
kdf,
|
||||
kdfKw,
|
||||
keyExchangeEcdheEddsa,
|
||||
Logger,
|
||||
MakeSyncSignatureRequest,
|
||||
PlanchetCreationRequest,
|
||||
WithdrawalPlanchet,
|
||||
PlanchetUnblindInfo,
|
||||
PurseDeposit,
|
||||
RecoupRefreshRequest,
|
||||
RecoupRequest,
|
||||
RefreshPlanchetInfo,
|
||||
@ -59,23 +70,14 @@ import {
|
||||
rsaVerify,
|
||||
setupTipPlanchet,
|
||||
stringToBytes,
|
||||
TalerSignaturePurpose,
|
||||
BlindedDenominationSignature,
|
||||
UnblindedSignature,
|
||||
PlanchetUnblindInfo,
|
||||
TalerProtocolTimestamp,
|
||||
kdfKw,
|
||||
bufferForUint32,
|
||||
kdf,
|
||||
ecdheGetPublic,
|
||||
getRandomBytes,
|
||||
AgeCommitmentProof,
|
||||
AgeRestriction,
|
||||
hashCoinPub,
|
||||
HashCodeString,
|
||||
TalerSignaturePurpose,
|
||||
UnblindedSignature,
|
||||
WithdrawalPlanchet,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import bigint from "big-integer";
|
||||
import { DenominationRecord, TipCoinSource, WireFee } from "../db.js";
|
||||
// FIXME: Crypto should not use DB Types!
|
||||
import { DenominationRecord, WireFee } from "../db.js";
|
||||
import {
|
||||
CreateRecoupRefreshReqRequest,
|
||||
CreateRecoupReqRequest,
|
||||
@ -177,6 +179,12 @@ export interface TalerCryptoInterface {
|
||||
setupRefreshTransferPub(
|
||||
req: SetupRefreshTransferPubRequest,
|
||||
): Promise<TransferPubResponse>;
|
||||
|
||||
signPurseCreation(req: SignPurseCreationRequest): Promise<EddsaSigningResult>;
|
||||
|
||||
signPurseDeposits(
|
||||
req: SignPurseDepositsRequest,
|
||||
): Promise<SignPurseDepositsResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -308,6 +316,16 @@ export const nullCrypto: TalerCryptoInterface = {
|
||||
): Promise<TransferPubResponse> {
|
||||
throw new Error("Function not implemented.");
|
||||
},
|
||||
signPurseCreation: function (
|
||||
req: SignPurseCreationRequest,
|
||||
): Promise<EddsaSigningResult> {
|
||||
throw new Error("Function not implemented.");
|
||||
},
|
||||
signPurseDeposits: function (
|
||||
req: SignPurseDepositsRequest,
|
||||
): Promise<SignPurseDepositsResponse> {
|
||||
throw new Error("Function not implemented.");
|
||||
},
|
||||
};
|
||||
|
||||
export type WithArg<X> = X extends (req: infer T) => infer R
|
||||
@ -336,6 +354,31 @@ export interface SetupWithdrawalPlanchetRequest {
|
||||
coinNumber: number;
|
||||
}
|
||||
|
||||
export interface SignPurseCreationRequest {
|
||||
pursePriv: string;
|
||||
purseExpiration: TalerProtocolTimestamp;
|
||||
purseAmount: AmountString;
|
||||
hContractTerms: HashCodeString;
|
||||
mergePub: EddsaPublicKeyString;
|
||||
minAge: number;
|
||||
}
|
||||
|
||||
export interface SignPurseDepositsRequest {
|
||||
pursePub: string;
|
||||
exchangeBaseUrl: string;
|
||||
coins: {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
contribution: AmountString;
|
||||
denomPubHash: string;
|
||||
denomSig: UnblindedSignature;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface SignPurseDepositsResponse {
|
||||
deposits: PurseDeposit[];
|
||||
}
|
||||
|
||||
export interface RsaVerificationRequest {
|
||||
hm: string;
|
||||
sig: string;
|
||||
@ -1212,6 +1255,51 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
||||
transferPub: (await tci.ecdheGetPublic(tci, { priv: transferPriv })).pub,
|
||||
};
|
||||
},
|
||||
async signPurseCreation(
|
||||
tci: TalerCryptoInterfaceR,
|
||||
req: SignPurseCreationRequest,
|
||||
): Promise<EddsaSigningResult> {
|
||||
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_CREATE)
|
||||
.put(timestampRoundedToBuffer(req.purseExpiration))
|
||||
.put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
|
||||
.put(decodeCrock(req.hContractTerms))
|
||||
.put(decodeCrock(req.mergePub))
|
||||
.put(bufferForUint32(req.minAge))
|
||||
.build();
|
||||
return await tci.eddsaSign(tci, {
|
||||
msg: encodeCrock(sigBlob),
|
||||
priv: req.pursePriv,
|
||||
});
|
||||
},
|
||||
async signPurseDeposits(
|
||||
tci: TalerCryptoInterfaceR,
|
||||
req: SignPurseDepositsRequest,
|
||||
): Promise<SignPurseDepositsResponse> {
|
||||
const hExchangeBaseUrl = hash(stringToBytes(req.exchangeBaseUrl + "\0"));
|
||||
const deposits: PurseDeposit[] = [];
|
||||
for (const c of req.coins) {
|
||||
const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_DEPOSIT)
|
||||
.put(amountToBuffer(Amounts.parseOrThrow(c.contribution)))
|
||||
.put(decodeCrock(req.pursePub))
|
||||
.put(hExchangeBaseUrl)
|
||||
.build();
|
||||
const sigResp = await tci.eddsaSign(tci, {
|
||||
msg: encodeCrock(sigBlob),
|
||||
priv: c.coinPriv,
|
||||
});
|
||||
deposits.push({
|
||||
amount: c.contribution,
|
||||
coin_pub: c.coinPub,
|
||||
coin_sig: sigResp.sig,
|
||||
denom_pub_hash: c.denomPubHash,
|
||||
ub_sig: c.denomSig,
|
||||
h_age_commitment: undefined,
|
||||
});
|
||||
}
|
||||
return {
|
||||
deposits,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function amountToBuffer(amount: AmountJson): Uint8Array {
|
||||
|
@ -148,4 +148,4 @@ export interface CreateRecoupRefreshReqRequest {
|
||||
denomPub: DenominationPubKey;
|
||||
denomPubHash: string;
|
||||
denomSig: UnblindedSignature;
|
||||
}
|
||||
}
|
@ -1309,9 +1309,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 {
|
||||
@ -1497,17 +1497,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;
|
||||
@ -1671,6 +1671,52 @@ export interface BalancePerCurrencyRecord {
|
||||
pendingOutgoing: AmountString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record for a push P2P payment that this wallet initiated.
|
||||
*/
|
||||
export interface PeerPushPaymentInitiationRecord {
|
||||
|
||||
/**
|
||||
* What exchange are funds coming from?
|
||||
*/
|
||||
exchangeBaseUrl: string;
|
||||
|
||||
amount: AmountString;
|
||||
|
||||
/**
|
||||
* Purse public key. Used as the primary key to look
|
||||
* up this record.
|
||||
*/
|
||||
pursePub: string;
|
||||
|
||||
/**
|
||||
* Purse private key.
|
||||
*/
|
||||
pursePriv: string;
|
||||
|
||||
/**
|
||||
* Public key of the merge capability of the purse.
|
||||
*/
|
||||
mergePub: string;
|
||||
|
||||
/**
|
||||
* Private key of the merge capability of the purse.
|
||||
*/
|
||||
mergePriv: string;
|
||||
|
||||
purseExpiration: TalerProtocolTimestamp;
|
||||
|
||||
/**
|
||||
* Did we successfully create the purse with the exchange?
|
||||
*/
|
||||
purseCreated: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record for a push P2P payment that this wallet accepted.
|
||||
*/
|
||||
export interface PeerPushPaymentAcceptanceRecord {}
|
||||
|
||||
export const WalletStoresV1 = {
|
||||
coins: describeStore(
|
||||
describeContents<CoinRecord>("coins", {
|
||||
|
222
packages/taler-wallet-core/src/operations/peer-to-peer.ts
Normal file
222
packages/taler-wallet-core/src/operations/peer-to-peer.ts
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2019 GNUnet e.V.
|
||||
|
||||
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 {
|
||||
AmountJson,
|
||||
Amounts,
|
||||
Logger,
|
||||
InitiatePeerPushPaymentResponse,
|
||||
InitiatePeerPushPaymentRequest,
|
||||
strcmp,
|
||||
CoinPublicKeyString,
|
||||
j2s,
|
||||
getRandomBytes,
|
||||
Duration,
|
||||
durationAdd,
|
||||
TalerProtocolTimestamp,
|
||||
AbsoluteTime,
|
||||
encodeCrock,
|
||||
AmountString,
|
||||
UnblindedSignature,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { CoinStatus } from "../db.js";
|
||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
|
||||
const logger = new Logger("operations/peer-to-peer.ts");
|
||||
|
||||
export interface PeerCoinSelection {
|
||||
exchangeBaseUrl: string;
|
||||
|
||||
/**
|
||||
* Info of Coins that were selected.
|
||||
*/
|
||||
coins: {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
contribution: AmountString;
|
||||
denomPubHash: string;
|
||||
denomSig: UnblindedSignature;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* How much of the deposit fees is the customer paying?
|
||||
*/
|
||||
depositFees: AmountJson;
|
||||
}
|
||||
|
||||
interface CoinInfo {
|
||||
/**
|
||||
* Public key of the coin.
|
||||
*/
|
||||
coinPub: string;
|
||||
|
||||
coinPriv: string;
|
||||
|
||||
/**
|
||||
* Deposit fee for the coin.
|
||||
*/
|
||||
feeDeposit: AmountJson;
|
||||
|
||||
value: AmountJson;
|
||||
|
||||
denomPubHash: string;
|
||||
|
||||
denomSig: UnblindedSignature;
|
||||
}
|
||||
|
||||
export async function initiatePeerToPeerPush(
|
||||
ws: InternalWalletState,
|
||||
req: InitiatePeerPushPaymentRequest,
|
||||
): Promise<InitiatePeerPushPaymentResponse> {
|
||||
const instructedAmount = Amounts.parseOrThrow(req.amount);
|
||||
const coinSelRes: PeerCoinSelection | undefined = await ws.db
|
||||
.mktx((x) => ({
|
||||
exchanges: x.exchanges,
|
||||
coins: x.coins,
|
||||
denominations: x.denominations,
|
||||
}))
|
||||
.runReadOnly(async (tx) => {
|
||||
const exchanges = await tx.exchanges.iter().toArray();
|
||||
for (const exch of exchanges) {
|
||||
if (exch.detailsPointer?.currency !== instructedAmount.currency) {
|
||||
continue;
|
||||
}
|
||||
const coins = (
|
||||
await tx.coins.indexes.byBaseUrl.getAll(exch.baseUrl)
|
||||
).filter((x) => x.status === CoinStatus.Fresh);
|
||||
const coinInfos: CoinInfo[] = [];
|
||||
for (const coin of coins) {
|
||||
const denom = await ws.getDenomInfo(
|
||||
ws,
|
||||
tx,
|
||||
coin.exchangeBaseUrl,
|
||||
coin.denomPubHash,
|
||||
);
|
||||
if (!denom) {
|
||||
throw Error("denom not found");
|
||||
}
|
||||
coinInfos.push({
|
||||
coinPub: coin.coinPub,
|
||||
feeDeposit: denom.feeDeposit,
|
||||
value: denom.value,
|
||||
denomPubHash: denom.denomPubHash,
|
||||
coinPriv: coin.coinPriv,
|
||||
denomSig: coin.denomSig,
|
||||
});
|
||||
}
|
||||
if (coinInfos.length === 0) {
|
||||
continue;
|
||||
}
|
||||
coinInfos.sort(
|
||||
(o1, o2) =>
|
||||
-Amounts.cmp(o1.value, o2.value) ||
|
||||
strcmp(o1.denomPubHash, o2.denomPubHash),
|
||||
);
|
||||
let amountAcc = Amounts.getZero(instructedAmount.currency);
|
||||
let depositFeesAcc = Amounts.getZero(instructedAmount.currency);
|
||||
const resCoins: {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
contribution: AmountString;
|
||||
denomPubHash: string;
|
||||
denomSig: UnblindedSignature;
|
||||
}[] = [];
|
||||
for (const coin of coinInfos) {
|
||||
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
|
||||
const res: PeerCoinSelection = {
|
||||
exchangeBaseUrl: exch.baseUrl,
|
||||
coins: resCoins,
|
||||
depositFees: depositFeesAcc,
|
||||
};
|
||||
return res;
|
||||
}
|
||||
const gap = Amounts.add(
|
||||
coin.feeDeposit,
|
||||
Amounts.sub(instructedAmount, amountAcc).amount,
|
||||
).amount;
|
||||
const contrib = Amounts.min(gap, coin.value);
|
||||
amountAcc = Amounts.add(
|
||||
amountAcc,
|
||||
Amounts.sub(contrib, coin.feeDeposit).amount,
|
||||
).amount;
|
||||
depositFeesAcc = Amounts.add(depositFeesAcc, coin.feeDeposit).amount;
|
||||
resCoins.push({
|
||||
coinPriv: coin.coinPriv,
|
||||
coinPub: coin.coinPub,
|
||||
contribution: Amounts.stringify(contrib),
|
||||
denomPubHash: coin.denomPubHash,
|
||||
denomSig: coin.denomSig,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
logger.info(`selected p2p coins: ${j2s(coinSelRes)}`);
|
||||
|
||||
if (!coinSelRes) {
|
||||
throw Error("insufficient balance");
|
||||
}
|
||||
|
||||
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
|
||||
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
|
||||
const hContractTerms = encodeCrock(getRandomBytes(64));
|
||||
const purseExpiration = AbsoluteTime.toTimestamp(
|
||||
AbsoluteTime.addDuration(
|
||||
AbsoluteTime.now(),
|
||||
Duration.fromSpec({ days: 2 }),
|
||||
),
|
||||
);
|
||||
|
||||
const purseSigResp = await ws.cryptoApi.signPurseCreation({
|
||||
hContractTerms,
|
||||
mergePub: mergePair.pub,
|
||||
minAge: 0,
|
||||
purseAmount: Amounts.stringify(instructedAmount),
|
||||
purseExpiration,
|
||||
pursePriv: pursePair.priv,
|
||||
});
|
||||
|
||||
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
||||
exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
|
||||
pursePub: pursePair.pub,
|
||||
coins: coinSelRes.coins,
|
||||
});
|
||||
|
||||
const createPurseUrl = new URL(
|
||||
`purses/${pursePair.pub}/create`,
|
||||
coinSelRes.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const httpResp = await ws.http.postJson(createPurseUrl.href, {
|
||||
amount: Amounts.stringify(instructedAmount),
|
||||
merge_pub: mergePair.pub,
|
||||
purse_sig: purseSigResp.sig,
|
||||
h_contract_terms: hContractTerms,
|
||||
purse_expiration: purseExpiration,
|
||||
deposits: depositSigsResp.deposits,
|
||||
min_age: 0,
|
||||
});
|
||||
|
||||
const resp = await httpResp.json();
|
||||
|
||||
logger.info(`resp: ${j2s(resp)}`);
|
||||
|
||||
throw Error("not yet implemented");
|
||||
}
|
@ -46,6 +46,8 @@ import {
|
||||
GetExchangeTosResult,
|
||||
GetWithdrawalDetailsForAmountRequest,
|
||||
GetWithdrawalDetailsForUriRequest,
|
||||
InitiatePeerPushPaymentRequest,
|
||||
InitiatePeerPushPaymentResponse,
|
||||
IntegrationTestArgs,
|
||||
ManualWithdrawalDetails,
|
||||
PreparePayRequest,
|
||||
@ -118,6 +120,9 @@ export enum WalletApiOperation {
|
||||
ExportBackupPlain = "exportBackupPlain",
|
||||
WithdrawFakebank = "withdrawFakebank",
|
||||
ExportDb = "exportDb",
|
||||
InitiatePeerPushPayment = "initiatePeerPushPayment",
|
||||
CheckPeerPushPayment = "checkPeerPushPayment",
|
||||
AcceptPeerPushPayment = "acceptPeerPushPayment",
|
||||
}
|
||||
|
||||
export type WalletOperations = {
|
||||
@ -277,6 +282,10 @@ export type WalletOperations = {
|
||||
request: {};
|
||||
response: any;
|
||||
};
|
||||
[WalletApiOperation.InitiatePeerPushPayment]: {
|
||||
request: InitiatePeerPushPaymentRequest;
|
||||
response: InitiatePeerPushPaymentResponse;
|
||||
};
|
||||
};
|
||||
|
||||
export type RequestType<
|
||||
|
@ -47,6 +47,7 @@ import {
|
||||
codecForGetWithdrawalDetailsForAmountRequest,
|
||||
codecForGetWithdrawalDetailsForUri,
|
||||
codecForImportDbRequest,
|
||||
codecForInitiatePeerPushPaymentRequest,
|
||||
codecForIntegrationTestArgs,
|
||||
codecForListKnownBankAccounts,
|
||||
codecForPrepareDepositRequest,
|
||||
@ -143,6 +144,7 @@ import {
|
||||
processDownloadProposal,
|
||||
processPurchasePay,
|
||||
} from "./operations/pay.js";
|
||||
import { initiatePeerToPeerPush } from "./operations/peer-to-peer.js";
|
||||
import { getPendingOperations } from "./operations/pending.js";
|
||||
import { createRecoupGroup, processRecoupGroup } from "./operations/recoup.js";
|
||||
import {
|
||||
@ -1049,6 +1051,10 @@ async function dispatchRequestInternal(
|
||||
await importDb(ws.db.idbHandle(), req.dump);
|
||||
return [];
|
||||
}
|
||||
case "initiatePeerPushPayment": {
|
||||
const req = codecForInitiatePeerPushPaymentRequest().decode(payload);
|
||||
return await initiatePeerToPeerPush(ws, req);
|
||||
}
|
||||
}
|
||||
throw TalerError.fromDetail(
|
||||
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
|
||||
|
Loading…
Reference in New Issue
Block a user