diff options
author | Florian Dold <florian@dold.me> | 2024-09-11 14:37:36 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-09-11 14:37:36 +0200 |
commit | e308a6a83a95735481134452bc4d4240783eec53 (patch) | |
tree | dfa5ec262a1a54ac8d2e88159986a66199cb0803 | |
parent | 3db5ec833d76284bbc7526c1a6941885198d048d (diff) |
wallet-core: exposed KYC limits WIP
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts | 25 | ||||
-rw-r--r-- | packages/taler-util/src/types-taler-exchange.ts | 56 | ||||
-rw-r--r-- | packages/taler-util/src/types-taler-wallet.ts | 56 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/denomSelection.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/exchanges.ts | 10 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/withdraw.ts | 25 |
7 files changed, 115 insertions, 69 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts index 7861fae97..bf3c2dbb2 100644 --- a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts +++ b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts @@ -18,8 +18,6 @@ * Imports. */ import { - encodeCrock, - hashPaytoUri, NotificationType, TalerCorebankApiClient, TransactionMajorState, @@ -145,6 +143,14 @@ async function createKycTestkudosEnvironment( config.setString("KYC-RULE-R1", "timeframe", "1d"); config.setString("KYC-RULE-R1", "next_measures", "M1"); + config.setString("KYC-RULE-R1", "operation_type", "withdraw"); + config.setString("KYC-RULE-R1", "enabled", "yes"); + config.setString("KYC-RULE-R1", "exposed", "yes"); + config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); + config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:300"); + config.setString("KYC-RULE-R1", "timeframe", "1d"); + config.setString("KYC-RULE-R1", "next_measures", "verboten"); + config.setString("KYC-MEASURE-M1", "check_name", "C1"); config.setString("KYC-MEASURE-M1", "context", "{}"); config.setString("KYC-MEASURE-M1", "program", "P1"); @@ -226,13 +232,24 @@ export async function runKycThresholdWithdrawalTest(t: GlobalTestState) { // Hand it to the wallet - await walletClient.client.call( + const withdrawalUrlInfo = await walletClient.client.call( WalletApiOperation.GetWithdrawalDetailsForUri, { talerWithdrawUri: wop.taler_withdraw_uri, }, ); + const withdrawalAmountInfo = await walletClient.call( + WalletApiOperation.GetWithdrawalDetailsForAmount, + { + amount: withdrawalUrlInfo.amount!, + exchangeBaseUrl: withdrawalUrlInfo.possibleExchanges[0].exchangeBaseUrl, + }, + ); + + // t.assertTrue(!!withdrawalAmountInfo.kycHardLimit); + // t.assertAmountEquals(withdrawalAmountInfo.kycHardLimit, "TESTKUDOS:300"); + // Withdraw const acceptResp = await walletClient.client.call( @@ -265,7 +282,6 @@ export async function runKycThresholdWithdrawalTest(t: GlobalTestState) { await kycNotificationCond; - const txDet = await walletClient.call(WalletApiOperation.GetTransactionById, { transactionId: withdrawalTxId, }); @@ -275,7 +291,6 @@ export async function runKycThresholdWithdrawalTest(t: GlobalTestState) { const kycPaytoHash = txDet.kycPaytoHash; t.assertTrue(!!kycPaytoHash); - await postAmlDecisionNoRules(t, { amlPriv: amlKeypair.priv, amlPub: amlKeypair.pub, diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts index 678b0b6c8..7c879b52e 100644 --- a/packages/taler-util/src/types-taler-exchange.ts +++ b/packages/taler-util/src/types-taler-exchange.ts @@ -454,6 +454,21 @@ export interface ExchangeKeysJson { // Optional option, if not given there is no limit. // Currency must match currency. wallet_balance_limit_without_kyc?: AmountString[]; + + // Array of limits that apply to all accounts. + // All of the given limits will be hard limits. + // Wallets and merchants are expected to obey them + // and not even allow the user to cross them. + // Since protocol **v21**. + hard_limits?: AccountLimit[]; + + // Array of limits with a soft threshold of zero + // that apply to all accounts without KYC. + // Wallets and merchants are expected to trigger + // a KYC process before attempting any zero-limited + // operations. + // Since protocol **v21**. + zero_limits?: ZeroLimitedOperation[]; } export interface ExchangeMeltRequest { @@ -877,6 +892,8 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => .property("global_fees", codecForList(codecForGlobalFees())) .property("accounts", codecForList(codecForExchangeWireAccount())) .property("wire_fees", codecForMap(codecForList(codecForWireFeesJson()))) + .property("zero_limits", codecOptional(codecForList(codecForZeroLimitedOperation()))) + .property("hard_limits", codecOptional(codecForList(codecForAccountLimit()))) .property("denominations", codecForList(codecForNgDenominations)) .property( "wallet_balance_limit_without_kyc", @@ -1687,11 +1704,12 @@ export interface AccountKycStatus { // request that would cause it to exceed hard limits). limits?: AccountLimit[]; } + +export type LimitOperationType = "WITHDRAW" | "DEPOSIT" | "MERGE" | "AGGREGATE" | "BALANCE" | "REFUND" | "CLOSE" | "TRANSACTION"; + export interface AccountLimit { // Operation that is limited. - // Must be one of "WITHDRAW", "DEPOSIT", "P2P-RECEIVE" - // or "WALLET-BALANCE". - operation_type: "WITHDRAW" | "DEPOSIT" | "P2P-RECEIVE" | "WALLET-BALANCE"; + operation_type: LimitOperationType; // Timeframe during which the limit applies. timeframe: RelativeTime; @@ -2104,8 +2122,7 @@ export interface ExchangeKeysResponse { extensions_sig?: EddsaSignature; } -interface ZeroLimitedOperation { - +export interface ZeroLimitedOperation { // Operation that is limited to an amount of // zero until the client has passed some KYC check. // Must be one of "WITHDRAW", "DEPOSIT", @@ -2113,7 +2130,6 @@ interface ZeroLimitedOperation { // (reserve) "CLOSE", "AGGREGATE", // "TRANSACTION" or "REFUND". operation_type: string; - } interface ExtensionManifest { @@ -2486,7 +2502,7 @@ export const codecForLegitimizationNeededResponse = .property("hint", codecOptional(codecForString())) .property("h_payto", codecForString()) .property("account_pub", codecOptional(codecForString())) - .property("requirement_row", (codecForNumber())) + .property("requirement_row", codecForNumber()) .property("bad_kyc_auth", codecOptional(codecForBoolean())) .build("TalerExchangeApi.LegitimizationNeededResponse"); @@ -2497,22 +2513,36 @@ export const codecForAccountKycStatus = (): Codec<AccountKycStatus> => .property("limits", codecOptional(codecForList(codecForAccountLimit()))) .build("TalerExchangeApi.AccountKycStatus"); +export const codecForOperationType = codecForEither( + codecForConstString("WITHDRAW"), + codecForConstString("DEPOSIT"), + codecForConstString("MERGE"), + codecForConstString("BALANCE"), + codecForConstString("CLOSE"), + codecForConstString("AGGREGATE"), + codecForConstString("TRANSACTION"), + codecForConstString("REFUND"), +); + export const codecForAccountLimit = (): Codec<AccountLimit> => buildCodecForObject<AccountLimit>() .property( "operation_type", - codecForEither( - codecForConstString("WITHDRAW"), - codecForConstString("DEPOSIT"), - codecForConstString("P2P-RECEIVE"), - codecForConstString("WALLET-BALANCE"), - ), + codecForOperationType, ) .property("timeframe", codecForDuration) .property("threshold", codecForAmountString()) .property("soft_limit", codecOptional(codecForBoolean())) .build("TalerExchangeApi.AccountLimit"); +export const codecForZeroLimitedOperation = (): Codec<ZeroLimitedOperation> => + buildCodecForObject<ZeroLimitedOperation>() + .property( + "operation_type", + codecForOperationType + ) + .build("TalerExchangeApi.ZeroLimitedOperation"); + export const codecForKycCheckPublicInformation = (): Codec<KycCheckPublicInformation> => buildCodecForObject<KycCheckPublicInformation>() diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts index 798197834..0461b74f9 100644 --- a/packages/taler-util/src/types-taler-wallet.ts +++ b/packages/taler-util/src/types-taler-wallet.ts @@ -57,7 +57,6 @@ import { WithdrawalOperationStatus, canonicalizeBaseUrl, } from "./index.js"; -import { VersionMatchResult } from "./libtool-version.js"; import { PaytoString, PaytoUri, codecForPaytoString } from "./payto.js"; import { QrCodeSpec } from "./qr.js"; import { AgeCommitmentProof } from "./taler-crypto.js"; @@ -1507,6 +1506,20 @@ export interface WithdrawalDetailsForAmount { * Scope info of the currency withdrawn. */ scopeInfo: ScopeInfo; + + /** + * KYC soft limit. + * + * Withdrawals over that amount will require KYC. + */ + kycSoftLimit?: AmountString; + + /** + * KYC soft limits. + * + * Withdrawals over that amount will be denied. + */ + kycHardLimit?: AmountString; } export interface DenomSelItem { @@ -1520,13 +1533,12 @@ export interface DenomSelItem { } /** - * Selected denominations withn some extra info. + * Selected denominations with some extra info. */ export interface DenomSelectionState { totalCoinValue: AmountString; totalWithdrawCost: AmountString; selectedDenoms: DenomSelItem[]; - earliestDepositExpiration: TalerProtocolTimestamp; hasDenomWithAgeRestriction: boolean; } @@ -1556,30 +1568,6 @@ export interface ExchangeWithdrawalDetails { termsOfServiceAccepted: boolean; /** - * The earliest deposit expiration of the selected coins. - */ - earliestDepositExpiration: TalerProtocolTimestamp; - - /** - * Result of checking the wallet's version - * against the exchange's version. - * - * Older exchanges don't return version information. - */ - versionMatch: VersionMatchResult | undefined; - - /** - * Libtool-style version string for the exchange or "unknown" - * for older exchanges. - */ - exchangeVersion: string; - - /** - * Libtool-style version string for the wallet. - */ - walletVersion: string; - - /** * Amount that will be subtracted from the reserve's balance. */ withdrawalAmountRaw: AmountString; @@ -1597,6 +1585,20 @@ export interface ExchangeWithdrawalDetails { ageRestrictionOptions?: number[]; scopeInfo: ScopeInfo; + + /** + * KYC soft limit. + * + * Withdrawals over that amount will require KYC. + */ + kycSoftLimit?: AmountString; + + /** + * KYC soft limits. + * + * Withdrawals over that amount will be denied. + */ + kycHardLimit?: AmountString; } export interface GetExchangeTosResult { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index a42548bb3..7f3b24250 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -29,6 +29,7 @@ import { } from "@gnu-taler/idb-bridge"; import { AbsoluteTime, + AccountLimit, AgeCommitmentProof, AmountString, Amounts, @@ -61,6 +62,7 @@ import { UnblindedSignature, WireInfo, WithdrawalExchangeAccountDetails, + ZeroLimitedOperation, codecForAny, j2s, stringifyScopeInfo, @@ -624,6 +626,10 @@ export interface ExchangeDetailsRecord { ageMask?: number; walletBalanceLimits?: AmountString[]; + + hardLimits?: AccountLimit[]; + + zeroLimits?: ZeroLimitedOperation[]; } export interface ExchangeDetailsPointer { diff --git a/packages/taler-wallet-core/src/denomSelection.ts b/packages/taler-wallet-core/src/denomSelection.ts index ecc1fa881..9e62857bf 100644 --- a/packages/taler-wallet-core/src/denomSelection.ts +++ b/packages/taler-wallet-core/src/denomSelection.ts @@ -123,9 +123,6 @@ export function selectWithdrawalDenominations( selectedDenoms, totalCoinValue: Amounts.stringify(totalCoinValue), totalWithdrawCost: Amounts.stringify(totalWithdrawCost), - earliestDepositExpiration: AbsoluteTime.toProtocolTimestamp( - earliestDepositExpiration, - ), hasDenomWithAgeRestriction, }; } @@ -191,9 +188,6 @@ export function selectForcedWithdrawalDenominations( selectedDenoms, totalCoinValue: Amounts.stringify(totalCoinValue), totalWithdrawCost: Amounts.stringify(totalWithdrawCost), - earliestDepositExpiration: AbsoluteTime.toProtocolTimestamp( - earliestDepositExpiration, - ), hasDenomWithAgeRestriction, }; } diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 2c0386d0c..4c2677482 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -26,6 +26,7 @@ import { AbsoluteTime, AccountKycStatus, + AccountLimit, AgeRestriction, Amount, AmountLike, @@ -91,6 +92,7 @@ import { WireFeeMap, WireFeesJson, WireInfo, + ZeroLimitedOperation, assertUnreachable, checkDbInvariant, checkLogicInvariant, @@ -854,6 +856,8 @@ export interface ExchangeKeysDownloadResult { wireFees: { [methodName: string]: WireFeesJson[] }; currencySpecification?: CurrencySpecification; walletBalanceLimits: AmountString[] | undefined; + hardLimits: AccountLimit[] | undefined; + zeroLimits: ZeroLimitedOperation[] | undefined; } /** @@ -1019,6 +1023,8 @@ async function downloadExchangeKeysInfo( currencySpecification: exchangeKeysJsonUnchecked.currency_specification, walletBalanceLimits: exchangeKeysJsonUnchecked.wallet_balance_limit_without_kyc, + hardLimits: exchangeKeysJsonUnchecked.hard_limits, + zeroLimits: exchangeKeysJsonUnchecked.zero_limits, }; } @@ -1259,6 +1265,8 @@ export interface ReadyExchangeSummary { protocolVersionRange: string; tosAcceptedTimestamp: TalerPreciseTimestamp | undefined; scopeInfo: ScopeInfo; + zeroLimits: ZeroLimitedOperation[]; + hardLimits: AccountLimit[]; } /** @@ -1423,6 +1431,8 @@ async function waitReadyExchange( exchange.tosAcceptedTimestamp, ), scopeInfo, + hardLimits: exchangeDetails.hardLimits ?? [], + zeroLimits: exchangeDetails.zeroLimits ?? [], }; if (options.expectedMasterPub) { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index c98c16f65..5cc0f228f 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -2190,14 +2190,6 @@ async function redenominateWithdrawal( .toString(), hasDenomWithAgeRestriction: prevHasDenomWithAgeRestriction || newSel.hasDenomWithAgeRestriction, - earliestDepositExpiration: AbsoluteTime.toProtocolTimestamp( - AbsoluteTime.min( - prevEarliestDepositExpiration, - AbsoluteTime.fromProtocolTimestamp( - newSel.earliestDepositExpiration, - ), - ), - ), }; wg.denomsSel = mergedSel; if (logger.shouldLogTrace()) { @@ -2574,14 +2566,10 @@ export async function getExchangeWithdrawalInfo( } const ret: ExchangeWithdrawalDetails = { - earliestDepositExpiration: selectedDenoms.earliestDepositExpiration, exchangePaytoUris: paytoUris, exchangeWireAccounts, exchangeCreditAccountDetails: withdrawalAccountsList, - exchangeVersion: exchange.protocolVersionRange || "unknown", selectedDenoms, - versionMatch, - walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION, termsOfServiceAccepted: tosAccepted, withdrawalAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue), withdrawalAmountRaw: Amounts.stringify(instructedAmount), @@ -2595,11 +2583,6 @@ export async function getExchangeWithdrawalInfo( return ret; } -export interface GetWithdrawalDetailsForUriOpts { - restrictAge?: number; - notifyChangeFromPendingTimeoutMs?: number; -} - async function getWithdrawalDetailsForBankInfo( wex: WalletExecutionContext, info: BankWithdrawDetails, @@ -2629,6 +2612,12 @@ async function getWithdrawalDetailsForBankInfo( } else { const listExchangesResp = await listExchanges(wex); + for (const exchange of listExchangesResp.exchanges) { + if (exchange.currency !== currency) { + continue; + } + } + possibleExchanges = listExchangesResp.exchanges.filter((x) => { return ( x.currency === currency && @@ -3993,7 +3982,7 @@ export async function getWithdrawalDetailsForAmount( ); } -async function internalGetWithdrawalDetailsForAmount( +export async function internalGetWithdrawalDetailsForAmount( wex: WalletExecutionContext, req: GetWithdrawalDetailsForAmountRequest, ): Promise<WithdrawalDetailsForAmount> { |