gnunet-svn
[Top][All Lists]
Advanced

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

[taler-cashless2ecash] branch master updated: code: implement wire gatew


From: gnunet
Subject: [taler-cashless2ecash] branch master updated: code: implement wire gateway
Date: Wed, 03 Apr 2024 19:13:05 +0200

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

joel-haeberli pushed a commit to branch master
in repository cashless2ecash.

The following commit(s) were added to refs/heads/master by this push:
     new dfdf9bb  code: implement wire gateway
dfdf9bb is described below

commit dfdf9bbb7c928827928f3855ff5afea91daa28b9
Author: Joel-Haeberli <haebu@rubigen.ch>
AuthorDate: Wed Apr 3 19:12:52 2024 +0200

    code: implement wire gateway
---
 .gitignore                                   |   2 +
 bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru |   2 +-
 c2ec/auth.go                                 |  34 +++
 c2ec/bank-integration.go                     |  59 ++---
 c2ec/c2ec-config.yaml                        |   1 +
 c2ec/config.go                               |   1 +
 c2ec/db.go                                   |  33 ++-
 c2ec/db/0000-c2ec_schema.sql                 |   5 +
 c2ec/db/0000-c2ec_transfers.sql              |  22 ++
 c2ec/main.go                                 |   9 +-
 c2ec/payto.go                                |  90 ++++++++
 c2ec/postgres.go                             | 191 ++++++++++++++++-
 c2ec/provider-client.go                      |   2 +-
 c2ec/simulation-attestor.go                  |   8 +-
 c2ec/simulation-client.go                    |   6 +-
 c2ec/wallee-attestor.go                      |   4 +-
 c2ec/wallee-client.go                        |   5 +-
 c2ec/wire-gateway.go                         | 308 ++++++++++++++++++++++++---
 18 files changed, 690 insertions(+), 92 deletions(-)

diff --git a/.gitignore b/.gitignore
index bcbd7bc..382b88a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
 schemaspy/*.jar
 schemaspy/Makefile
 infra/
+bruno/
+c2ec/c2ec
 
 LocalMakefile
 
diff --git a/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru 
b/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru
index 7de1e1b..3281b68 100644
--- a/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru      
+++ b/bruno/c2ec/(LOCAL-BIA) Withdrawal Status.bru      
@@ -5,7 +5,7 @@ meta {
 }
 
 get {
-  url: http://localhost:8081/c2ec/withdrawal-operation/WOPID
+  url: http://localhost:8081/c2ec/withdrawal-operation/
   body: none
   auth: none
 }
diff --git a/c2ec/auth.go b/c2ec/auth.go
index 8fdb761..a8e102e 100644
--- a/c2ec/auth.go
+++ b/c2ec/auth.go
@@ -7,6 +7,23 @@ import (
 const AUTHORIZATION_HEADER = "Authorization"
 const BEARER_TOKEN_PREFIX = "Bearer"
 
+// Authentication in C2EC requires following use cases:
+//  1. Wallet authenticating itself using the Bank-Integration API
+//  2. Provider authentication itself using the Bank-Integration API
+//  3. Exchange Wirewatch component, using the Wire-Gateway API
+//     3.1 The Wire-Gateway API specifies Basic-Auth (RFC7617)
+//
+// The Wire-Gateway API is the only API specification which makes
+// prescriptions concerning the authenticaion. For simplicity,
+// Basic-Auth will be applied to all client types (Exchange, Wallet, Providers)
+// To distinguish what client type wants to request, a special format
+// for the username type is created.
+//
+//     use `PROVIDER-[PROVIDER_ID]:[PROVIDER_SECRET]` for provider clients
+//     use `WALLET:`
+//
+// in case no prefix was specified, it is assumed that the request originates
+// from the exchange.
 func isAllowed(req *http.Request) bool {
 
        return true
@@ -20,3 +37,20 @@ func isAllowed(req *http.Request) bool {
 
        // return strings.EqualFold(token, "")
 }
+
+// Is this needed? Understand how the wallet authenticates itself at the 
exchange currently first.
+// https://docs.taler.net/design-documents/049-auth.html#dd48-token
+// https://docs.taler.net/core/api-corebank.html#authentication
+//
+// /accounts/$USERNAME/token
+//
+// The username in our case is the reserve public key
+// registered for withdrawal. At the initial registration
+// of the reserve public key we leverage a TOFU trust model.
+// during the registration of the reserve public key a new
+// access token will be created with a limited lifetime.
+// The token will not be refreshable and become invalid
+// only after a few minutes. Since the Wallet will register
+// a wopid and
+// func handleTokenRequest() {
+// }
diff --git a/c2ec/bank-integration.go b/c2ec/bank-integration.go
index 9b5305a..86fe78a 100644
--- a/c2ec/bank-integration.go
+++ b/c2ec/bank-integration.go
@@ -17,6 +17,7 @@ const WOPID_PARAMETER = "wopid"
 const BANK_INTEGRATION_CONFIG_PATTERN = BANK_INTEGRATION_CONFIG_ENDPOINT
 const WITHDRAWAL_OPERATION_PATTERN = WITHDRAWAL_OPERATION
 const WITHDRAWAL_OPERATION_BY_WOPID_PATTERN = WITHDRAWAL_OPERATION + "/{" + 
WOPID_PARAMETER + "}"
+const WITHDRAWAL_OPERATION_PAYMENT_PATTERN = 
WITHDRAWAL_OPERATION_BY_WOPID_PATTERN + "/payment"
 const WITHDRAWAL_OPERATION_ABORTION_PATTERN = 
WITHDRAWAL_OPERATION_BY_WOPID_PATTERN + "/abort"
 
 const DEFAULT_LONG_POLL_MS = 1000
@@ -42,10 +43,8 @@ type BankIntegrationConfig struct {
 }
 
 type C2ECWithdrawRegistration struct {
-       Wopid         WithdrawalIdentifier `json:"wopid"`
-       ReservePubKey EddsaPublicKey       `json:"reserve_pub_key"`
-       Amount        Amount               `json:"amount"`
-       TerminalId    uint64               `json:"terminal_id"`
+       ReservePubKey EddsaPublicKey `json:"reserve_pub_key"`
+       TerminalId    uint64         `json:"terminal_id"`
 }
 
 type C2ECWithdrawalStatus struct {
@@ -98,10 +97,27 @@ func handleWithdrawalRegistration(res http.ResponseWriter, 
req *http.Request) {
                return
        }
 
+       // read and validate the wopid path parameter
+       wopid := req.PathValue(WOPID_PARAMETER)
+       if _, ok := any(wopid).(WithdrawalIdentifier); !ok {
+
+               if wopid == "" {
+                       err := WriteProblem(res, HTTP_BAD_REQUEST, 
&RFC9457Problem{
+                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_INVALID_PATH_PARAMETER",
+                               Title:    "invalid request path parameter",
+                               Detail:   "the withdrawal status request path 
parameter 'wopid' is malformed",
+                               Instance: req.RequestURI,
+                       })
+                       if err != nil {
+                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       }
+                       return
+               }
+       }
+
        err = DB.RegisterWithdrawal(
-               registration.Wopid,
+               WithdrawalIdentifier(wopid),
                registration.ReservePubKey,
-               registration.Amount,
                registration.TerminalId,
        )
 
@@ -357,34 +373,3 @@ func getWithdrawalOrWriteError(wopid string, res 
http.ResponseWriter, reqUri str
                res.Write(withdrawalStatusBytes)
        }
 }
-
-// ----------------------
-// OFFICIAL MODELS
-// ----------------------
-// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationPostRequest
-type BankWithdrawalOperationPostRequest struct {
-       ReservePub       string `json:"reserve_pub"`
-       SelectedExchange string `json:"selected_exchange"`
-}
-
-// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationPostResponse
-type BankWithdrawalOperationPostResponse struct {
-       Status             WithdrawalOperationStatus `json:"status"`
-       ConfirmTransferUrl string                    
`json:"confirm_transfer_url"`
-       TransferDone       bool                      `json:"transfer_done"`
-}
-
-// 
https://docs.taler.net/core/api-bank-integration.html#tsref-type-BankWithdrawalOperationStatus
-type BankWithdrawalOperationStatus struct {
-       Status                  WithdrawalOperationStatus `json:"status"`
-       Amount                  Amount                    `json:"amount"`
-       SenderWire              string                    `json:"sender_wire"`
-       SuggestedExchange       string                    
`json:"suggested_exchange"`
-       ConfirmTransferUrl      string                    
`json:"confirm_transfer_url"`
-       WireTypes               []string                  `json:"wire_types"`
-       SelectedReservePub      string                    
`json:"selected_reserve_pub"`
-       SelectedExchangeAccount string                    
`json:"selected_exchange_account"`
-       Aborted                 bool                      `json:"aborted"`
-       SelectionDone           bool                      
`json:"selection_done"`
-       TransferDone            bool                      `json:"transfer_done"`
-}
diff --git a/c2ec/c2ec-config.yaml b/c2ec/c2ec-config.yaml
index 3032884..1938a3d 100644
--- a/c2ec/c2ec-config.yaml
+++ b/c2ec/c2ec-config.yaml
@@ -5,6 +5,7 @@ c2ec:
   unix-domain-socket: false
   unix-socket-path: "c2ec.sock"
   fail-on-missing-attestors: false # forced if prod=true
+  credit-account: "payto://iban/CH50030202099498" # this account must be 
specified at the providers backends as well
 db:
   host: "localhost"
   port: 5432
diff --git a/c2ec/config.go b/c2ec/config.go
index d4a3028..0ce723d 100644
--- a/c2ec/config.go
+++ b/c2ec/config.go
@@ -19,6 +19,7 @@ type C2ECServerConfig struct {
        UseUnixDomainSocket bool   `yaml:"unix-domain-socket"`
        UnixSocketPath      string `yaml:"unix-socket-path"`
        StrictAttestors     bool   `yaml:"fail-on-missing-attestors"`
+       CreditAccount       string `yaml:"credit-account"`
 }
 
 type C2ECDatabseConfig struct {
diff --git a/c2ec/db.go b/c2ec/db.go
index 12fe2e4..0a24202 100644
--- a/c2ec/db.go
+++ b/c2ec/db.go
@@ -7,6 +7,7 @@ import (
 const PROVIDER_TABLE_NAME = "c2ec.provider"
 const PROVIDER_FIELD_NAME_ID = "terminal_id"
 const PROVIDER_FIELD_NAME_NAME = "name"
+const PROVIDER_FIELD_NAME_PAYTO_TARGET_TYPE = "payto_target_type"
 const PROVIDER_FIELD_NAME_BACKEND_URL = "backend_base_url"
 const PROVIDER_FIELD_NAME_BACKEND_CREDENTIALS = "backend_credentials"
 
@@ -31,9 +32,14 @@ const WITHDRAWAL_FIELD_NAME_LAST_RETRY = "last_retry_ts"
 const WITHDRAWAL_FIELD_NAME_RETRY_COUNTER = "retry_counter"
 const WITHDRAWAL_FIELD_NAME_COMPLETION_PROOF = "completion_proof"
 
+const TRANSFER_TABLE_NAME = "c2ec.transfer"
+const TRANSFER_FIELD_NAME_ID = "request_uid"
+const TRANSFER_FIELD_NAME_HASH = "request_hash"
+
 type Provider struct {
        ProviderTerminalID int64  `db:"provider_id"`
        Name               string `db:"name"`
+       PaytoTargetType    string `db:"payto_target_type"`
        BackendBaseURL     string `db:"backend_base_url"`
        BackendCredentials string `db:"backend_credentials"`
 }
@@ -47,7 +53,7 @@ type Terminal struct {
 }
 
 type Withdrawal struct {
-       WithdrawalId          []byte                    `db:"withdrawal_id"`
+       WithdrawalId          uint64                    `db:"withdrawal_id"`
        Wopid                 uint64                    `db:"wopid"`
        ReservePubKey         []byte                    `db:"reserve_pub_key"`
        RegistrationTs        int64                     `db:"registration_ts"`
@@ -67,6 +73,11 @@ type TalerAmountCurrency struct {
        Curr string `db:"curr"`
 }
 
+type Transfer struct {
+       RequestId   HashCode `db:"request_uid"`
+       RequestHash string   `db:"request_hash"`
+}
+
 // C2ECDatabase defines the operations which a
 // C2EC compliant database interface must implement
 // in order to be bound to the c2ec API.
@@ -76,13 +87,15 @@ type C2ECDatabase interface {
        RegisterWithdrawal(
                wopid WithdrawalIdentifier,
                resPubKey EddsaPublicKey,
-               amount Amount,
                terminalId uint64,
        ) error
 
        // Get the withdrawal associated with the given wopid.
        GetWithdrawalByWopid(wopid string) (*Withdrawal, error)
 
+       // Get the withdrawal associated with the provider specific transaction 
id.
+       GetWithdrawalByProviderTransactionId(tid string) (*Withdrawal, error)
+
        // When the terminal receives the notification of the
        // Provider, that the payment went through, this will
        // save the provider specific transaction id in the database
@@ -110,12 +123,28 @@ type C2ECDatabase interface {
                completionProof []byte,
        ) error
 
+       // The wire gateway allows the exchange to retrieve transactions
+       // starting at a certain starting point up until a certain delta
+       // if the delta is negative, previous transactions relative to the
+       // starting point are considered. When start is negative, the latest
+       // id shall be used as starting point.
+       GetConfirmedWithdrawals(start int, delta int) ([]*Withdrawal, error)
+
        // Get a provider entry by its name
        GetTerminalProviderByName(name string) (*Provider, error)
 
+       // Get a provider entry by its name
+       GetTerminalProviderByPaytoTargetType(paytoTargetType string) 
(*Provider, error)
+
        // Get a terminal entry by its identifier
        GetTerminalById(id int) (*Terminal, error)
 
+       // Returns the transfer for the given hashcode.
+       GetTransferById(requestUid HashCode) (*Transfer, error)
+
+       // Inserts a new transfer into the database.
+       AddTransfer(requestId HashCode, requestHash string) error
+
        // This will listen for on the given channel
        // and write results to the out channels.
        // Errors will be propagated through the errs
diff --git a/c2ec/db/0000-c2ec_schema.sql b/c2ec/db/0000-c2ec_schema.sql
index 75f1f0e..6c85d52 100644
--- a/c2ec/db/0000-c2ec_schema.sql
+++ b/c2ec/db/0000-c2ec_schema.sql
@@ -35,6 +35,7 @@ COMMENT ON TYPE taler_amount_currency
 CREATE TABLE IF NOT EXISTS provider (
     provider_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
     name TEXT NOT NULL UNIQUE,
+    payto_target_type TEXT NOT NULL UNIQUE,
     backend_base_url TEXT NOT NULL,
     backend_credentials TEXT NOT NULL
 );
@@ -44,6 +45,10 @@ COMMENT ON COLUMN provider.provider_id
   IS 'Uniquely identifies a provider';
 COMMENT ON COLUMN provider.name
   IS 'Name of the provider, used for selection in transaction proofing';
+COMMENT ON COLUMN provider.payto_target_type
+  IS 'The Payto target type associated with the provider. Each payto target 
type
+  has exctly one provider. This is needed so that the attestor client can be 
dynamically
+  selected by C2EC.';
 COMMENT ON COLUMN provider.backend_base_url
   IS 'URL of the provider backend for transaction proofing';
 COMMENT ON COLUMN provider.backend_credentials
diff --git a/c2ec/db/0000-c2ec_transfers.sql b/c2ec/db/0000-c2ec_transfers.sql
new file mode 100644
index 0000000..6eb2fdb
--- /dev/null
+++ b/c2ec/db/0000-c2ec_transfers.sql
@@ -0,0 +1,22 @@
+BEGIN;
+
+SELECT _v.register_patch('0000-c2ec-transfers', ARRAY['0000-c2ec-schema'], 
NULL);
+
+SET search_path TO c2ec;
+
+CREATE TABLE IF NOT EXISTS transfer (
+    request_uid INT8 UNIQUE PRIMARY KEY,
+    request_hash TEXT NOT NULL
+);
+COMMENT ON TABLE transfer
+  IS 'Table storing transfers which are sent by the exchange.';
+COMMENT ON COLUMN transfers.request_uid
+  IS 'A unique identifier for the transfer. In the case of this
+  implementation its gonna be the wopid of the withdrawal which
+  is addressed by the transfer.';
+COMMENT ON COLUMN transfers.request_hash
+  IS 'Hash of the entire transfer request. Requests with the same 
+  request identifier must have the identical hash to be processed
+  further.';
+
+COMMIT;
\ No newline at end of file
diff --git a/c2ec/main.go b/c2ec/main.go
index 9eaf45e..120c267 100644
--- a/c2ec/main.go
+++ b/c2ec/main.go
@@ -21,6 +21,11 @@ const DEFAULT_C2EC_CONFIG_PATH = "c2ec-config.yaml"
 
 var DB C2ECDatabase
 
+// This map contains all clients initialized during the
+// startup of the application. The clients SHALL register
+// themselfs during the setup!!
+var PROVIDER_CLIENTS = map[string]ProviderClient{}
+
 // Starts the c2ec process.
 // The program takes following arguments (ordered):
 //  1. path to configuration file (.yaml) (optional)
@@ -154,7 +159,7 @@ func setupBankIntegrationRoutes(router *http.ServeMux) {
        )
 
        router.HandleFunc(
-               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_PATTERN,
+               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_BY_WOPID_PATTERN,
                handleWithdrawalRegistration,
        )
 
@@ -164,7 +169,7 @@ func setupBankIntegrationRoutes(router *http.ServeMux) {
        )
 
        router.HandleFunc(
-               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_BY_WOPID_PATTERN,
+               POST+BANK_INTEGRATION_API+WITHDRAWAL_OPERATION_PAYMENT_PATTERN,
                handlePaymentNotification,
        )
 
diff --git a/c2ec/payto.go b/c2ec/payto.go
new file mode 100644
index 0000000..a40266c
--- /dev/null
+++ b/c2ec/payto.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+)
+
+const PAYTO_PARTS_SEPARATOR = "/"
+
+const PAYTO_SCHEME_PREFIX = "payto://"
+const PAYTO_TAGRET_TYPE_IBAN = "iban"
+const PAYTO_TARGET_TYPE_WALLEE_TRANSACTION = "wallee-transaction"
+
+var REGISTERED_TARGET_TYPES = []string{
+       "ach",
+       "bic",
+       "iban",
+       "upi",
+       "bitcoin",
+       "ilp",
+       "void",
+       "ldap",
+       "eth",
+       "interac-etransfer",
+       "wallee-transaction",
+}
+
+// This method parses a payto-uri (RFC 8905: 
https://www.rfc-editor.org/rfc/rfc8905.html)
+// The method only parses the target type "wallee-transaction" as specified
+// in the payto GANA registry 
(https://gana.gnunet.org/payto-payment-target-types/payto_payment_target_types.html)
+func ParsePaytoWalleeTransaction(uri string) (string, int, error) {
+
+       if t, i, err := ParsePaytoUri(uri); err != nil {
+
+               tid, err := strconv.Atoi(i)
+               if err != nil {
+                       return "", -1, errors.New("invalid transaction-id for 
wallee-transaction")
+               }
+
+               return t, tid, nil
+       } else {
+               return t, -1, err
+       }
+}
+
+// returns the Payto Target Type and Target Identifier as string
+// if the uri is malformed, an error is returned (target type and
+// identifier will be empty strings).
+func ParsePaytoUri(uri string) (string, string, error) {
+
+       if raw, found := strings.CutPrefix(uri, PAYTO_SCHEME_PREFIX); found {
+
+               parts := strings.Split(raw, PAYTO_PARTS_SEPARATOR)
+               if len(parts) < 2 {
+                       return "", "", errors.New("invalid wallee-transaction 
payto-uri")
+               }
+
+               return parts[0], parts[1], nil
+       }
+       return "", "", errors.New("invalid payto-uri")
+}
+
+func FormatPaytoWalleeTransaction(tid int) string {
+       return fmt.Sprintf("%s%s/%d",
+               PAYTO_SCHEME_PREFIX,
+               PAYTO_TARGET_TYPE_WALLEE_TRANSACTION,
+               tid,
+       )
+}
+
+func ParsePaytoTargetType(uri string) (string, error) {
+
+       if raw, found := strings.CutPrefix(uri, PAYTO_SCHEME_PREFIX); found {
+
+               parts := strings.Split(raw, PAYTO_PARTS_SEPARATOR)
+               if len(parts) < 2 {
+                       return "", errors.New("invalid wallee-transaction 
payto-uri")
+               }
+
+               for _, target := range REGISTERED_TARGET_TYPES {
+                       if strings.EqualFold(target, parts[0]) {
+                               return parts[0], nil
+                       }
+               }
+               return "", errors.New("target type '" + parts[0] + "' is not 
registered")
+       }
+       return "", errors.New("invalid payto-uri")
+}
diff --git a/c2ec/postgres.go b/c2ec/postgres.go
index acd703e..e11a635 100644
--- a/c2ec/postgres.go
+++ b/c2ec/postgres.go
@@ -6,6 +6,7 @@ import (
        "encoding/base64"
        "errors"
        "fmt"
+       "math"
        "time"
 
        "github.com/jackc/pgx/v5"
@@ -14,14 +15,16 @@ import (
        "github.com/jackc/pgxlisten"
 )
 
+const PS_ASC_SELECTOR = "ASC"
+const PS_DESC_SELECTOR = "DESC"
+
 const PS_INSERT_WITHDRAWAL = "INSERT INTO " + WITHDRAWAL_TABLE_NAME + "  (" +
        WITHDRAWAL_FIELD_NAME_WOPID + "," +
        WITHDRAWAL_FIELD_NAME_RESPUBKEY + "," +
        WITHDRAWAL_FIELD_NAME_STATUS + "," +
        WITHDRAWAL_FIELD_NAME_TS + "," +
-       WITHDRAWAL_FIELD_NAME_AMOUNT + "," +
        WITHDRAWAL_FIELD_NAME_TERMINAL_ID + ")" +
-       " VALUES ($1, $2, $3, $4, $5, $6);"
+       " VALUES ($1, $2, $3, $4, $5);"
 
 const PS_GET_UNCONFIRMED_WITHDRAWALS = "SELECT * FROM " + 
WITHDRAWAL_TABLE_NAME +
        " WHERE " + WITHDRAWAL_FIELD_NAME_TRANSACTION_ID + " IS NOT NULL" +
@@ -40,15 +43,33 @@ const PS_FINALISE_PAYMENT = "UPDATE " + 
WITHDRAWAL_TABLE_NAME + " SET (" +
        " = ($1, $2)" +
        " WHERE " + WITHDRAWAL_FIELD_NAME_ID + "=$3"
 
+const PS_CONFIRMED_TRANSACTIONS = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
+       " LIMIT $1" +
+       " OFFSET $2" +
+       " ORDER BY " + WITHDRAWAL_FIELD_NAME_ID + " $3"
+
 const PS_GET_WITHDRAWAL_BY_WOPID = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
        " WHERE " + WITHDRAWAL_FIELD_NAME_WOPID + "=$1"
 
+const PS_GET_WITHDRAWAL_BY_PTID = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
+       " WHERE " + WITHDRAWAL_FIELD_NAME_TRANSACTION_ID + "=$1"
+
 const PS_GET_PROVIDER_BY_NAME = "SELECT * FROM " + PROVIDER_TABLE_NAME +
        " WHERE " + PROVIDER_FIELD_NAME_NAME + "=$1"
 
+const PS_GET_PROVIDER_BY_PAYTO_TARGET_TYPE = "SELECT * FROM " + 
PROVIDER_TABLE_NAME +
+       " WHERE " + PROVIDER_FIELD_NAME_PAYTO_TARGET_TYPE + "=$1"
+
 const PS_GET_TERMINAL_BY_ID = "SELECT * FROM " + TERMINAL_TABLE_NAME +
        " WHERE " + TERMINAL_FIELD_NAME_ID + "=$1"
 
+const PS_GET_TRANSFER_BY_ID = "SELECT * FROM " + TRANSFER_TABLE_NAME +
+       " WHERE " + TRANSFER_FIELD_NAME_ID + "=$1"
+
+const PS_ADD_TRANSFER = "INSERT INTO " + TRANSFER_TABLE_NAME +
+       " (" + TRANSFER_FIELD_NAME_ID + ", " + TRANSFER_FIELD_NAME_HASH + ")" +
+       " VALUES ($1, $2)"
+
 // Postgres implementation of the C2ECDatabase
 type C2ECPostgres struct {
        C2ECDatabase
@@ -92,7 +113,6 @@ func NewC2ECPostgres(cfg *C2ECDatabseConfig) (*C2ECPostgres, 
error) {
 func (db *C2ECPostgres) RegisterWithdrawal(
        wopid WithdrawalIdentifier,
        resPubKey EddsaPublicKey,
-       amount Amount,
        terminalId uint64,
 ) error {
 
@@ -104,7 +124,6 @@ func (db *C2ECPostgres) RegisterWithdrawal(
                resPubKey,
                SELECTED,
                ts.Unix(),
-               amount,
                terminalId,
        )
        if err != nil {
@@ -141,6 +160,32 @@ func (db *C2ECPostgres) GetWithdrawalByWopid(wopid string) 
(*Withdrawal, error)
        }
 }
 
+func (db *C2ECPostgres) GetWithdrawalByProviderTransactionId(tid string) 
(*Withdrawal, error) {
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_WITHDRAWAL_BY_PTID,
+               tid,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               withdrawals, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Withdrawal])
+               if err != nil {
+                       return nil, err
+               }
+
+               if len(withdrawals) < 1 {
+                       return nil, nil
+               }
+               return withdrawals[0], nil
+       }
+}
+
 func (db *C2ECPostgres) NotifyPayment(
        wopid WithdrawalIdentifier,
        providerTransactionId string,
@@ -209,6 +254,65 @@ func (db *C2ECPostgres) FinaliseWithdrawal(
        return nil
 }
 
+// The query at the postgres database works as specified by the
+// wire gateway api.
+func (db *C2ECPostgres) GetConfirmedWithdrawals(start int, delta int) 
([]*Withdrawal, error) {
+
+       sort := PS_ASC_SELECTOR
+       if delta < 0 {
+               sort = PS_DESC_SELECTOR
+       }
+
+       limit := math.Abs(float64(delta))
+       offset := start
+       if delta < 0 {
+               offset = start - int(limit)
+       }
+       if offset < 0 {
+               offset = 0
+       }
+
+       var row pgx.Rows
+       var err error
+       if start < 0 {
+               // use MAX(id) instead of a concrete id, because start
+               // identifier was negative. Inidicates to read the most
+               // recent ids.
+               row, err = db.pool.Query(
+                       db.ctx,
+                       PS_CONFIRMED_TRANSACTIONS,
+                       limit,
+                       "MAX("+WITHDRAWAL_FIELD_NAME_ID+")",
+                       sort,
+               )
+       } else {
+               row, err = db.pool.Query(
+                       db.ctx,
+                       PS_CONFIRMED_TRANSACTIONS,
+                       limit,
+                       offset,
+                       sort,
+               )
+       }
+
+       if err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               withdrawals, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Withdrawal])
+               if err != nil {
+                       return nil, err
+               }
+
+               return withdrawals, nil
+       }
+}
+
 func (db *C2ECPostgres) GetTerminalProviderByName(name string) (*Provider, 
error) {
 
        if row, err := db.pool.Query(
@@ -237,6 +341,34 @@ func (db *C2ECPostgres) GetTerminalProviderByName(name 
string) (*Provider, error
        }
 }
 
+func (db *C2ECPostgres) GetTerminalProviderByPaytoTargetType(paytoTargetType 
string) (*Provider, error) {
+
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_PROVIDER_BY_PAYTO_TARGET_TYPE,
+               paytoTargetType,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               provider, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Provider])
+               if err != nil {
+                       return nil, err
+               }
+
+               if len(provider) < 1 {
+                       return nil, nil
+               }
+
+               return provider[0], nil
+       }
+}
+
 func (db *C2ECPostgres) GetTerminalById(id int) (*Terminal, error) {
 
        if row, err := db.pool.Query(
@@ -252,13 +384,60 @@ func (db *C2ECPostgres) GetTerminalById(id int) 
(*Terminal, error) {
 
                defer row.Close()
 
-               terminal, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Terminal])
+               terminals, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Terminal])
+               if err != nil {
+                       return nil, err
+               }
+
+               if len(terminals) < 1 {
+                       return nil, nil
+               }
+
+               return terminals[0], nil
+       }
+}
+
+func (db *C2ECPostgres) GetTransferById(requestUid HashCode) (*Transfer, 
error) {
+
+       if row, err := db.pool.Query(
+               db.ctx,
+               PS_GET_TRANSFER_BY_ID,
+               requestUid,
+       ); err != nil {
+               if row != nil {
+                       row.Close()
+               }
+               return nil, err
+       } else {
+
+               defer row.Close()
+
+               transfers, err := pgx.CollectRows(row, 
pgx.RowToAddrOfStructByName[Transfer])
                if err != nil {
                        return nil, err
                }
 
-               return terminal[0], nil
+               if len(transfers) < 1 {
+                       return nil, nil
+               }
+               return transfers[0], nil
        }
+
+}
+
+func (db *C2ECPostgres) AddTransfer(requestId HashCode, requestHash string) 
error {
+
+       res, err := db.pool.Query(
+               db.ctx,
+               PS_ADD_TRANSFER,
+               requestId,
+               requestHash,
+       )
+       if err != nil {
+               return err
+       }
+       res.Close()
+       return nil
 }
 
 func (db *C2ECPostgres) ListenForWithdrawalStatusChange(
diff --git a/c2ec/provider-client.go b/c2ec/provider-client.go
index 39260ff..22da076 100644
--- a/c2ec/provider-client.go
+++ b/c2ec/provider-client.go
@@ -5,7 +5,7 @@ type ProviderTransaction interface {
        Bytes() []byte
 }
 
-type ProviderClient[T any] interface {
+type ProviderClient interface {
        SetupClient(provider *Provider) error
        GetTransaction(transactionId string) (ProviderTransaction, error)
        Refund(transactionId string) error
diff --git a/c2ec/simulation-attestor.go b/c2ec/simulation-attestor.go
index 30be17e..41b3214 100644
--- a/c2ec/simulation-attestor.go
+++ b/c2ec/simulation-attestor.go
@@ -12,9 +12,11 @@ import (
 )
 
 type SimulationAttestor struct {
+       Attestor[SimulationClient]
+
        listener       *pgxlisten.Listener
        provider       *Provider
-       providerClient ProviderClient[WalleeClient]
+       providerClient ProviderClient
 }
 
 func (wa *SimulationAttestor) Setup(p *Provider, cfg *C2ECDatabseConfig) (chan 
*pgconn.Notification, error) {
@@ -84,15 +86,11 @@ func (wa *SimulationAttestor) Attest(withdrawalId int, 
providerTransactionId str
 
                err = DB.FinaliseWithdrawal(withdrawalId, CONFIRMED, 
transaction.Bytes())
                if err != nil {
-                       // TODO : do we abort the withdrawal here??
                        errs <- err
                }
        } else {
-               // TODO : this might be too early ?! What if the payment was 
not yet
-               //            processed by the Wallee backend? Needs testing.
                err = DB.FinaliseWithdrawal(withdrawalId, ABORTED, 
transaction.Bytes())
                if err != nil {
-                       // TODO : do we abort the withdrawal here??
                        errs <- err
                }
        }
diff --git a/c2ec/simulation-client.go b/c2ec/simulation-client.go
index e7ae253..a9c8304 100644
--- a/c2ec/simulation-client.go
+++ b/c2ec/simulation-client.go
@@ -11,7 +11,7 @@ type SimulationTransaction struct {
 }
 
 type SimulationClient struct {
-       ProviderClient[SimulationTransaction]
+       ProviderClient
 
        // toggle this to simulate failed transactions.
        AllowNextWithdrawal bool
@@ -22,9 +22,11 @@ func (st *SimulationTransaction) AllowWithdrawal() bool {
        return st.allow
 }
 
-func (*SimulationClient) SetupClient(p *Provider) error {
+func (sc *SimulationClient) SetupClient(p *Provider) error {
 
        fmt.Println("setting up simulation client. probably not what you want 
in production")
+
+       PROVIDER_CLIENTS["Simulation"] = sc
        return nil
 }
 
diff --git a/c2ec/wallee-attestor.go b/c2ec/wallee-attestor.go
index b65ed21..efc3709 100644
--- a/c2ec/wallee-attestor.go
+++ b/c2ec/wallee-attestor.go
@@ -12,9 +12,11 @@ import (
 )
 
 type WalleeAttestor struct {
+       Attestor[WalleeClient]
+
        listener       *pgxlisten.Listener
        provider       *Provider
-       providerClient ProviderClient[WalleeClient]
+       providerClient ProviderClient
 }
 
 func (wa *WalleeAttestor) Setup(p *Provider, cfg *C2ECDatabseConfig) (chan 
*pgconn.Notification, error) {
diff --git a/c2ec/wallee-client.go b/c2ec/wallee-client.go
index 1c40af1..6347d0a 100644
--- a/c2ec/wallee-client.go
+++ b/c2ec/wallee-client.go
@@ -30,7 +30,7 @@ type WalleeCredentials struct {
 }
 
 type WalleeClient struct {
-       ProviderClient[WalleeTransaction]
+       ProviderClient
 
        name        string
        baseUrl     string
@@ -52,6 +52,9 @@ func (w *WalleeClient) SetupClient(p *Provider) error {
        w.name = p.Name
        w.baseUrl = p.BackendBaseURL
        w.credentials = creds
+
+       PROVIDER_CLIENTS[w.name] = w
+
        return nil
 }
 
diff --git a/c2ec/wire-gateway.go b/c2ec/wire-gateway.go
index e308a28..473ba9c 100644
--- a/c2ec/wire-gateway.go
+++ b/c2ec/wire-gateway.go
@@ -1,9 +1,13 @@
 package main
 
 import (
-       "bytes"
+       "context"
+       "crypto"
+       "encoding/base64"
        "log"
        http "net/http"
+       "strconv"
+       "time"
 )
 
 const WIRE_GATEWAY_CONFIG_ENDPOINT = "/config"
@@ -15,6 +19,8 @@ const WIRE_HISTORY_INCOMING_PATTERN = 
WIRE_GATEWAY_HISTORY_ENDPOINT + "/incoming
 const WIRE_HISTORY_OUTGOING_PATTERN = WIRE_GATEWAY_HISTORY_ENDPOINT + 
"/outgoing"
 const WIRE_ADMIN_ADD_INCOMING_PATTERN = "/admin/add-incoming"
 
+const INCOMING_RESERVE_TRANSACTION_TYPE = "RESERVE"
+
 // https://docs.taler.net/core/api-bank-wire.html#tsref-type-WireConfig
 type WireConfig struct {
        Name           string `json:"name"`
@@ -54,20 +60,21 @@ type IncomingReserveTransaction struct {
        ReservePub   EddsaPublicKey `json:"reserve_pub"`
 }
 
-// https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingHistory
-type OutgoingHistory struct {
-       OutgoingBankTransaction []OutgoingBankTransaction 
`json:"outgoing_bank_transaction"`
-       DebitAccount            string                    `json:"debit_account"`
-}
-
-// 
https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingBankTransaction
-type OutgoingBankTransaction struct {
-       RowId           int           `json:"row_id"`
-       Date            Timestamp     `json:"date"`
-       Amount          Amount        `json:"amount"`
-       CreditAccount   string        `json:"credit_account"`
-       Wtid            ShortHashCode `json:"wtid"`
-       ExchangeBaseUrl string        `json:"exchange_base_url"`
+func NewIncomingReserveTransaction(w *Withdrawal) *IncomingReserveTransaction {
+       t := new(IncomingReserveTransaction)
+       t.Amount = Amount{
+               Value:    uint64(w.Amount.Val),
+               Fraction: uint64(w.Amount.Frac),
+               Currency: w.Amount.Curr,
+       }
+       t.Date = Timestamp{
+               Ts: int(w.RegistrationTs),
+       }
+       t.DebitAccount = ""
+       t.ReservePub = EddsaPublicKey(w.ReservePubKey)
+       t.RowId = int(w.WithdrawalId)
+       t.Type = INCOMING_RESERVE_TRANSACTION_TYPE
+       return t
 }
 
 func wireGatewayConfig(res http.ResponseWriter, req *http.Request) {
@@ -90,14 +97,250 @@ func wireGatewayConfig(res http.ResponseWriter, req 
*http.Request) {
 
 func transfer(res http.ResponseWriter, req *http.Request) {
 
-       res.WriteHeader(HTTP_OK)
-       res.Write(bytes.NewBufferString("retrieved transfer request").Bytes())
+       jsonCodec := NewJsonCodec[TransferRequest]()
+       transfer, err := ReadStructFromBody(req, jsonCodec)
+       if err != nil {
+
+               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
+                       Title:    "invalid request",
+                       Detail:   "the transfer request is malformed (error: " 
+ err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
+       paytoTargetType, tid, err := 
ParsePaytoWalleeTransaction(transfer.CreditAccount)
+       if err != nil {
+               err := WriteProblem(res, HTTP_BAD_REQUEST, &RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
+                       Title:    "invalid payto-uri",
+                       Detail:   "the transfer request contains an invalid 
payto-uri (error: " + err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
+       p, err := DB.GetTerminalProviderByPaytoTargetType(paytoTargetType)
+       if err != nil {
+               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
+                       Title:    "database request failed",
+                       Detail:   "failed to retrieve the provider for the 
payto target type '" + paytoTargetType + "'",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
+       t, err := DB.GetTransferById(transfer.RequestUid)
+       if err != nil {
+               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
+                       Title:    "database request failed",
+                       Detail:   "there was an error processing the database 
query",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
+       body := make([]byte, req.ContentLength)
+       _, err = req.Body.Read(body)
+       if err != nil {
+               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_READ_BODY_FAILED",
+                       Title:    "reading body failed",
+                       Detail:   "there was an error processing the request 
body (error: " + err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+       requestHash := hashRequest(body)
+
+       if t == nil {
+               // no transfer for this request_id -> generate new
+               err := DB.AddTransfer(transfer.RequestUid, requestHash)
+               if err != nil {
+                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
+                               Title:    "database request failed",
+                               Detail:   "there was an error creating the 
transfer",
+                               Instance: req.RequestURI,
+                       })
+                       if err != nil {
+                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       }
+                       return
+               }
+       } else {
+               // the transfer is only processed if the body matches.
+               if requestHash != t.RequestHash {
+                       err := WriteProblem(res, HTTP_BAD_REQUEST, 
&RFC9457Problem{
+                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_TRANSFER_INVALID_REQ",
+                               Title:    "invalid request",
+                               Detail:   "the transfer request did not match 
previous request with the same request identifier",
+                               Instance: req.RequestURI,
+                       })
+                       if err != nil {
+                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       }
+                       return
+               }
+
+               ptid := strconv.Itoa(tid)
+               w, err := DB.GetWithdrawalByProviderTransactionId(ptid)
+               if err != nil || w == nil {
+                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
+                               Title:    "database request failed",
+                               Detail:   "there was an error processing the 
database query or no withdrawal could been found.",
+                               Instance: req.RequestURI,
+                       })
+                       if err != nil {
+                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       }
+                       return
+               }
+
+               refundClient := PROVIDER_CLIENTS[p.Name]
+               if refundClient == nil {
+                       err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                               TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_UNKNOWN_TRANSFER_MECHANISM",
+                               Title:    "unknown refund mechanism",
+                               Detail:   "the target type of the payto uri for 
the transfer is not registered",
+                               Instance: req.RequestURI,
+                       })
+                       if err != nil {
+                               res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+                       }
+                       return
+               }
+               refundClient.Refund(ptid)
+       }
 }
 
+// :query start: *Optional.*
+//
+//     Row identifier to explicitly set the *starting point* of the query.
+//
+// :query delta:
+//
+//     The *delta* value that determines the range of the query.
+//
+// :query long_poll_ms: *Optional.*
+//
+//     If this parameter is specified and the result of the query would be 
empty,
+//     the bank will wait up to ``long_poll_ms`` milliseconds for new 
transactions
+//     that match the query to arrive and only then send the HTTP response.
+//     A client must never rely on this behavior, as the bank may return a 
response
+//     immediately or after waiting only a fraction of ``long_poll_ms``.
 func historyIncoming(res http.ResponseWriter, req *http.Request) {
 
+       // read and validate request query parameters
+       shouldStartLongPoll := true
+       var longPollMilli int
+       if longPollMilliPtr, accepted := AcceptOptionalParamOrWriteResponse(
+               "long_poll_ms", strconv.Atoi, req, res,
+       ); accepted {
+       } else {
+               if longPollMilliPtr != nil {
+                       longPollMilli = *longPollMilliPtr
+               } else {
+                       // this means parameter was not given.
+                       // no long polling (simple get)
+                       shouldStartLongPoll = false
+               }
+       }
+
+       var start int
+       if startPtr, accepted := AcceptOptionalParamOrWriteResponse(
+               "start", strconv.Atoi, req, res,
+       ); accepted {
+       } else {
+               if startPtr != nil {
+                       start = *startPtr
+               }
+       }
+
+       var delta int
+       if deltaPtr, accepted := AcceptOptionalParamOrWriteResponse(
+               "delta", strconv.Atoi, req, res,
+       ); accepted {
+       } else {
+               if deltaPtr != nil {
+                       delta = *deltaPtr
+               } else {
+                       // this means parameter was not given.
+                       // no long polling (simple get)
+                       shouldStartLongPoll = false
+               }
+       }
+
+       if shouldStartLongPoll {
+
+               // wait for the completion of the context
+               waitMs, cancelFunc := context.WithTimeout(req.Context(), 
time.Duration(longPollMilli)*time.Millisecond)
+               defer cancelFunc()
+
+               // this will just wait / block until the milliseconds are 
exceeded.
+               <-waitMs.Done()
+       }
+
+       withdrawals, err := DB.GetConfirmedWithdrawals(start, delta)
+
+       if err != nil {
+               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_DATABASE_FAILURE",
+                       Title:    "database request failed",
+                       Detail:   "there was an error processing the database 
query",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
+       if len(withdrawals) < 1 {
+               res.WriteHeader(HTTP_NOT_FOUND)
+               return
+       }
+
+       transactions := make([]*IncomingReserveTransaction, len(withdrawals))
+       for _, w := range withdrawals {
+               transactions = append(transactions, 
NewIncomingReserveTransaction(w))
+       }
+
+       enc, err := 
NewJsonCodec[[]*IncomingReserveTransaction]().EncodeToBytes(&transactions)
+       if err != nil {
+               err := WriteProblem(res, HTTP_INTERNAL_SERVER_ERROR, 
&RFC9457Problem{
+                       TypeUri:  TALER_URI_PROBLEM_PREFIX + 
"/C2EC_RESPONSE_ENCODING_FAILED",
+                       Title:    "encoding failed",
+                       Detail:   "the encoding of the response failed (error:" 
+ err.Error() + ")",
+                       Instance: req.RequestURI,
+               })
+               if err != nil {
+                       res.WriteHeader(HTTP_INTERNAL_SERVER_ERROR)
+               }
+               return
+       }
+
        res.WriteHeader(HTTP_OK)
-       res.Write(bytes.NewBufferString("retrieved history incoming 
request").Bytes())
+       res.Write(enc)
 }
 
 // This method is currently dead and implemented for API conformance
@@ -107,25 +350,22 @@ func historyOutgoing(res http.ResponseWriter, req 
*http.Request) {
        res.WriteHeader(HTTP_BAD_REQUEST)
 }
 
-// ---------------------
-// TESTING (ONLY ADMINS)
-// ---------------------
-
-// https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingRequest
-type AddIncomingRequest struct {
-       Amount       Amount         `json:"amount"`
-       ReservcePub  EddsaPublicKey `json:"reserve_pub"`
-       DebitAccount string         `json:"debit_account"`
-}
-
-// 
https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingResponse
-type AddIncomingResponse struct {
-       Timestamp Timestamp `json:"timestamp"`
-}
-
 // This method is currently dead and implemented for API conformance
 func adminAddIncoming(res http.ResponseWriter, req *http.Request) {
 
        // not implemented, because not used
        res.WriteHeader(HTTP_BAD_REQUEST)
 }
+
+// hashes the request and encodes the request in base64.
+// use this function to hash a transfer request and compare
+// the result to the content of the database.
+func hashRequest(transferBytes []byte) string {
+
+       h := crypto.SHA256.New()
+       h.Reset()
+       h.Write(transferBytes)
+       result := make([]byte, 32)
+       result = h.Sum(result)
+       return base64.StdEncoding.EncodeToString(result)
+}

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