taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

064-kyc-operation-algo.rst (6325B)


      1 DD 64: Algorithm for transactions with KYC checks
      2 #################################################
      3 
      4 :Design status: Accepted
      5 :Implementation status: Implemented
      6 :DD shepherd: TBD
      7 :Historical contributors: Florian Dold, Christian Grothoff
      8 :First published: 2025-06-05
      9 :Last substantive change: 2025-07-30
     10 :Implementation evidence: taler-typescript-core (2025-07-08)
     11 :Normative references: :doc:`../developer/taler-wallet-developer`, :doc:`../wallet/wallet-core`, :doc:`../core/api-exchange`
     12 
     13 Summary
     14 =======
     15 
     16 This design document specifies the algorithm that wallets and merchants should
     17 use for processing and (re-)trying transactions where KYC checks may apply.
     18 
     19 Motivation
     20 ==========
     21 
     22 The exchange requires the customer to pass KYC checks and satisfy AML rules for
     23 various transactions.  However, the corresponding rules are dynamic and may also be
     24 hidden from the client for regulatory purposes.
     25 
     26 Requirements
     27 ============
     28 
     29 * Minimize the time the user has to wait (=> long-poll efficiently)
     30 * Minimize the number of requests that the exchange has do process (=> do not quickly
     31   retry when it's clear that the request is unlikely to succeed now)
     32 
     33 Proposed Solution
     34 =================
     35 
     36 
     37 Steps for processing operation of type ``op`` and amount ``amt``:
     38 
     39 
     40 Initialization
     41 ^^^^^^^^^^^^^^
     42 
     43 Initialize the following variables:
     44 
     45 * ``last_check_status := null``
     46 
     47   * Last HTTP status of the kyc-check request.
     48     A status of ``0`` indicates a network failure or request timeout
     49     and is distinct from ``null``, which indicates that no check
     50     request has been made.
     51 
     52 * ``last_check_code := null``
     53 
     54   * Taler error code of the last kyc-check request or ``null`` if no such
     55     request has been made or the last request didn't return an error code.
     56 
     57 * ``last_rule_gen := null``
     58 * ``last_aml_review := null``
     59 * ``last_deny := if isZeroLimited(op, amt) then now() else null``
     60 * ``last_bad_kyc_auth := false``
     61 * ``account_keypair := getCurrentAccountKeyPair(op)``
     62 
     63 Processing
     64 ^^^^^^^^^^
     65 
     66 1. If ``last_deny`` is ``null`` or more than ``1h`` ago,
     67    make a request for ``op`` at the exchange.  Let ``resp`` be the response.
     68 
     69    * If the request succeeds, *halt*.
     70    * If the request fails with ``451``, set ``last_deny := now()`` and ``last_bad_kyc_auth := resp.bad_kyc_auth``.
     71    * Otherwise, finish processing operation with result ``BACKOFF``.
     72 
     73 2. Request the ``/kyc-check/...`` endpoint applicable for ``op`` with ``account_keypair`` the following parameters:
     74 
     75    * If ``last_check_status == null``: Make request without long-polling.
     76    * If ``last_check_status in [403 Forbidden, 409 Conflict]`` or ``last_bad_kyc_auth == true && last_check_status == 404``: Long-poll. Add query parameter ``lpt=1``
     77    * If ``last_aml_review == true``: Long-poll. Add query parameter ``lpt=2``.  If ``last_rule_gen != null``, add
     78      query parameter ``min_rule=last_rule_gen``.
     79    * Otherwise: Long-poll. If ``last_rule_gen != null``, add ``min_rule=last_rule_gen``
     80      to the query parameters.
     81 
     82 3. Handle the ``/kyc-check/...`` response:
     83 
     84    * Set ``same_resp := resp.status == last_check_status and resp.code == last_check_code and resp.rule_gen == last_rule_gen``.
     85    * Set ``last_check_status := resp.status``, ``last_check_code := resp.code``, ``last_rule_gen := resp.rule_gen``
     86    * If ``same_resp == true``: finish processing operation with result ``BACKOFF``.
     87    * If ``resp.status == 204 No Content``: Set ``last_deny := null``. Finish processing operation with result ``PROGRESS`` (effectively
     88      re-trying at step 1).
     89    * If ``resp.status == 200 Ok``: Set ``last_deny := null``. Finish with result ``PROGRESS``
     90    * If ``resp.status == 202 Accepted``: Go to step 4.
     91    * If ``resp.status == 403 Forbidden``: Check if the private key for the
     92      indicated public key is available. If, set ``account_keypair`` to that key pair and finish with result ``PROGRESS``.
     93      Otherwise, finish with result ``BACKOFF``.
     94    * If ``resp.status == 404 Not Found``:
     95 
     96      * If ``last_bad_kyc_auth == true``, finish
     97        processing with result ``BACKOFF`` (transition asking the user for KYC auth).
     98      * Otherwise, go to step 4, with exposed limits set to the default limits.
     99 
    100    * Otherwise (unhandled status), finish processing with result ``BACKOFF``.
    101 
    102 4. Handle exposed limits applicable to the account:
    103 
    104    * If the exposed limits do not deny ``op``, set ``last_deny := null`` and finish processing operation with result ``PROGRESS``.
    105    * If the exposed limits deny ``op`` as ``verboten``, set ``last_deny := now()`` and transition the transaction
    106      to a ``failed`` state (Showing an error message
    107      indicating that the operation is forbidden due to legal
    108      restrictions on the payment service provider). Finish processing operation with result ``PROGRESS``.
    109    * Otherwise (merely a threshold over some timeframe is currently
    110      violated), compute the time ``t`` when the operation may be allowed again.
    111      Finish processing operation with result ``AGAIN_AT(t)``.
    112 
    113 **KYC auth state**: The user should be instructed to do a KYC auth transfer if
    114 ``last_check_status in [403 Forbidden, 409 Conflict]`` or ``last_bad_kyc_auth == true && last_check_status == 404``.
    115 
    116 Additional Considerations
    117 ^^^^^^^^^^^^^^^^^^^^^^^^^
    118 
    119 * Ensure the long-polling interval specified in the request
    120   works with the middleware. If we detect that a request timed
    121   out before the specified long-polling interval, use a shorter
    122   timeout argument in the HTTP request the next time
    123   (but do still keep exponentially
    124   backing off the actual request frequency).
    125 
    126 * Lower the retry back-off for the transaction to zero if:
    127 
    128   * the user manually reviews the KYC auth wire transfer instructions, or
    129   * the user manually reviews KYC information page instructions, or
    130   * if ``op`` is a deposit and ``lpt=1`` and a withdraw succeeded
    131     from the same account
    132 
    133 * Especially in step 4 (limit violated in timeframe),
    134   the user should be offered the option to retry.
    135 
    136 
    137 Definition of Done
    138 ==================
    139 
    140 * [x] shared transaction/KYC retry algorithm implemented in wallet-core
    141 * [x] deposit, peer-to-peer and withdrawal paths use the shared algorithm
    142 
    143 Alternatives
    144 ============
    145 
    146 N/A
    147 
    148 Drawbacks
    149 =========
    150 
    151 N/A
    152 
    153 Discussion / Q&A
    154 ================
    155 
    156 (This should be filled in with results from discussions on mailing lists / personal communication.)