exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-wire-gateway-client.c (21661B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2017-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-wire-gateway-client.c
     18  * @brief Execute wire transfer.
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include <gnunet/gnunet_json_lib.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h>
     26 #include "taler/taler_bank_service.h"
     27 
     28 /**
     29  * If set to #GNUNET_YES, then we'll ask the bank for a list
     30  * of incoming transactions from the account.
     31  */
     32 static int incoming_history;
     33 
     34 /**
     35  * If set to #GNUNET_YES, then we'll ask the bank for a list
     36  * of outgoing transactions from the account.
     37  */
     38 static int outgoing_history;
     39 
     40 /**
     41  * Amount to transfer.
     42  */
     43 static struct TALER_Amount amount;
     44 
     45 /**
     46  * Credit account payto://-URI.
     47  */
     48 static struct TALER_FullPayto credit_account;
     49 
     50 /**
     51  * Debit account payto://-URI.
     52  */
     53 static struct TALER_FullPayto debit_account;
     54 
     55 /**
     56  * Wire transfer subject.
     57  */
     58 static char *subject;
     59 
     60 /**
     61  * Which config section has the credentials to access the bank.
     62  */
     63 static char *account_section;
     64 
     65 /**
     66  * Starting row.
     67  */
     68 static unsigned long long start_row = UINT64_MAX;
     69 
     70 /**
     71  * Authentication data.
     72  */
     73 static struct TALER_BANK_AuthenticationData auth;
     74 
     75 /**
     76  * Return value from main().
     77  */
     78 static int global_ret = 1;
     79 
     80 /**
     81  * Main execution context for the main loop.
     82  */
     83 static struct GNUNET_CURL_Context *ctx;
     84 
     85 /**
     86  * Handle to ongoing credit history operation.
     87  */
     88 static struct TALER_BANK_CreditHistoryHandle *chh;
     89 
     90 /**
     91  * Handle to ongoing debit history operation.
     92  */
     93 static struct TALER_BANK_DebitHistoryHandle *dhh;
     94 
     95 /**
     96  * Handle for executing the wire transfer.
     97  */
     98 static struct TALER_BANK_TransferHandle *eh;
     99 
    100 /**
    101  * Handle to access the exchange.
    102  */
    103 static struct TALER_BANK_AdminAddIncomingHandle *op;
    104 
    105 /**
    106  * Context for running the CURL event loop.
    107  */
    108 static struct GNUNET_CURL_RescheduleContext *rc;
    109 
    110 
    111 /**
    112  * Function run when the test terminates (good or bad).
    113  * Cleans up our state.
    114  *
    115  * @param cls NULL
    116  */
    117 static void
    118 do_shutdown (void *cls)
    119 {
    120   (void) cls;
    121   if (NULL != op)
    122   {
    123     TALER_BANK_admin_add_incoming_cancel (op);
    124     op = NULL;
    125   }
    126   if (NULL != chh)
    127   {
    128     TALER_BANK_credit_history_cancel (chh);
    129     chh = NULL;
    130   }
    131   if (NULL != dhh)
    132   {
    133     TALER_BANK_debit_history_cancel (dhh);
    134     dhh = NULL;
    135   }
    136   if (NULL != eh)
    137   {
    138     TALER_BANK_transfer_cancel (eh);
    139     eh = NULL;
    140   }
    141   if (NULL != ctx)
    142   {
    143     GNUNET_CURL_fini (ctx);
    144     ctx = NULL;
    145   }
    146   if (NULL != rc)
    147   {
    148     GNUNET_CURL_gnunet_rc_destroy (rc);
    149     rc = NULL;
    150   }
    151   TALER_BANK_auth_free (&auth);
    152 }
    153 
    154 
    155 /**
    156  * Callback used to process the transaction
    157  * history returned by the bank.
    158  *
    159  * @param cls closure
    160  * @param reply response we got from the bank
    161  */
    162 static void
    163 credit_history_cb (void *cls,
    164                    const struct TALER_BANK_CreditHistoryResponse *reply)
    165 {
    166   (void) cls;
    167 
    168   chh = NULL;
    169   switch (reply->http_status)
    170   {
    171   case 0:
    172     fprintf (stderr,
    173              "Failed to obtain HTTP reply from `%s'\n",
    174              auth.wire_gateway_url);
    175     global_ret = 2;
    176     break;
    177   case MHD_HTTP_NO_CONTENT:
    178     fprintf (stdout,
    179              "No transactions.\n");
    180     global_ret = 0;
    181     break;
    182   case MHD_HTTP_OK:
    183     for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
    184     {
    185       const struct TALER_BANK_CreditDetails *cd =
    186         &reply->details.ok.details[i];
    187 
    188       /* If credit/debit accounts were specified, use as a filter */
    189       if ( (NULL != credit_account.full_payto) &&
    190            (0 != TALER_full_payto_cmp (credit_account,
    191                                        reply->details.ok.credit_account_uri) ) )
    192         continue;
    193       if ( (NULL != debit_account.full_payto) &&
    194            (0 != TALER_full_payto_cmp (debit_account,
    195                                        cd->debit_account_uri) ) )
    196         continue;
    197       switch (cd->type)
    198       {
    199       case TALER_BANK_CT_RESERVE:
    200         fprintf (stdout,
    201                  "%llu: %s->%s (%s) over %s at %s\n",
    202                  (unsigned long long) cd->serial_id,
    203                  cd->debit_account_uri.full_payto,
    204                  reply->details.ok.credit_account_uri.full_payto,
    205                  TALER_B2S (&cd->details.reserve.reserve_pub),
    206                  TALER_amount2s (&cd->amount),
    207                  GNUNET_TIME_timestamp2s (cd->execution_date));
    208         break;
    209       case TALER_BANK_CT_KYCAUTH:
    210         fprintf (stdout,
    211                  "%llu: %s->%s (KYC:%s) over %s at %s\n",
    212                  (unsigned long long) cd->serial_id,
    213                  cd->debit_account_uri.full_payto,
    214                  reply->details.ok.credit_account_uri.full_payto,
    215                  TALER_B2S (&cd->details.kycauth.account_pub),
    216                  TALER_amount2s (&cd->amount),
    217                  GNUNET_TIME_timestamp2s (cd->execution_date));
    218         break;
    219       case TALER_BANK_CT_WAD:
    220         GNUNET_break (0); // FIXME-#7271 (support wad payments)
    221         break;
    222       }
    223     }
    224     global_ret = 0;
    225     break;
    226   default:
    227     fprintf (stderr,
    228              "Failed to obtain credit history from `%s': HTTP status %u (%s)\n",
    229              auth.wire_gateway_url,
    230              reply->http_status,
    231              TALER_ErrorCode_get_hint (reply->ec));
    232     if (NULL != reply->response)
    233       json_dumpf (reply->response,
    234                   stderr,
    235                   JSON_INDENT (2));
    236     global_ret = 2;
    237     break;
    238   }
    239   GNUNET_SCHEDULER_shutdown ();
    240 }
    241 
    242 
    243 /**
    244  * Ask the bank the list of transactions for the bank account
    245  * mentioned in the config section given by the user.
    246  */
    247 static void
    248 execute_credit_history (void)
    249 {
    250   if (NULL != subject)
    251   {
    252     fprintf (stderr,
    253              "Specifying subject is not supported when inspecting credit history\n");
    254     GNUNET_SCHEDULER_shutdown ();
    255     return;
    256   }
    257   chh = TALER_BANK_credit_history (ctx,
    258                                    &auth,
    259                                    start_row,
    260                                    -10,
    261                                    GNUNET_TIME_UNIT_ZERO,
    262                                    &credit_history_cb,
    263                                    NULL);
    264   if (NULL == chh)
    265   {
    266     fprintf (stderr,
    267              "Could not request the credit transaction history.\n");
    268     GNUNET_SCHEDULER_shutdown ();
    269     return;
    270   }
    271 }
    272 
    273 
    274 /**
    275  * Function with the debit transaction history.
    276  *
    277  * @param cls closure
    278  * @param reply response details
    279  */
    280 static void
    281 debit_history_cb (void *cls,
    282                   const struct TALER_BANK_DebitHistoryResponse *reply)
    283 {
    284   (void) cls;
    285 
    286   dhh = NULL;
    287   switch (reply->http_status)
    288   {
    289   case 0:
    290     fprintf (stderr,
    291              "Failed to obtain HTTP reply from `%s'\n",
    292              auth.wire_gateway_url);
    293     global_ret = 2;
    294     break;
    295   case MHD_HTTP_NO_CONTENT:
    296     fprintf (stdout,
    297              "No transactions.\n");
    298     global_ret = 0;
    299     break;
    300   case MHD_HTTP_OK:
    301     for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
    302     {
    303       const struct TALER_BANK_DebitDetails *dd =
    304         &reply->details.ok.details[i];
    305 
    306       /* If credit/debit accounts were specified, use as a filter */
    307       if ( (NULL != credit_account.full_payto) &&
    308            (0 != TALER_full_payto_cmp (credit_account,
    309                                        dd->credit_account_uri) ) )
    310         continue;
    311       if ( (NULL != debit_account.full_payto) &&
    312            (0 != TALER_full_payto_cmp (debit_account,
    313                                        reply->details.ok.debit_account_uri) ) )
    314         continue;
    315       fprintf (stdout,
    316                "%llu: %s->%s (%s) over %s at %s\n",
    317                (unsigned long long) dd->serial_id,
    318                reply->details.ok.debit_account_uri.full_payto,
    319                dd->credit_account_uri.full_payto,
    320                TALER_B2S (&dd->wtid),
    321                TALER_amount2s (&dd->amount),
    322                GNUNET_TIME_timestamp2s (dd->execution_date));
    323     }
    324     global_ret = 0;
    325     break;
    326   default:
    327     fprintf (stderr,
    328              "Failed to obtain debit history from `%s': HTTP status %u (%s)\n",
    329              auth.wire_gateway_url,
    330              reply->http_status,
    331              TALER_ErrorCode_get_hint (reply->ec));
    332     if (NULL != reply->response)
    333       json_dumpf (reply->response,
    334                   stderr,
    335                   JSON_INDENT (2));
    336     global_ret = 2;
    337     break;
    338   }
    339   GNUNET_SCHEDULER_shutdown ();
    340 }
    341 
    342 
    343 /**
    344  * Ask the bank the list of transactions for the bank account
    345  * mentioned in the config section given by the user.
    346  */
    347 static void
    348 execute_debit_history (void)
    349 {
    350   if (NULL != subject)
    351   {
    352     fprintf (stderr,
    353              "Specifying subject is not supported when inspecting debit history\n");
    354     GNUNET_SCHEDULER_shutdown ();
    355     return;
    356   }
    357   dhh = TALER_BANK_debit_history (ctx,
    358                                   &auth,
    359                                   start_row,
    360                                   -10,
    361                                   GNUNET_TIME_UNIT_ZERO,
    362                                   &debit_history_cb,
    363                                   NULL);
    364   if (NULL == dhh)
    365   {
    366     fprintf (stderr,
    367              "Could not request the debit transaction history.\n");
    368     GNUNET_SCHEDULER_shutdown ();
    369     return;
    370   }
    371 }
    372 
    373 
    374 /**
    375  * Callback that processes the outcome of a wire transfer
    376  * execution.
    377  *
    378  * @param cls closure
    379  * @param tr response details
    380  */
    381 static void
    382 confirmation_cb (void *cls,
    383                  const struct TALER_BANK_TransferResponse *tr)
    384 {
    385   (void) cls;
    386   eh = NULL;
    387   if (MHD_HTTP_OK != tr->http_status)
    388   {
    389     fprintf (stderr,
    390              "The wire transfer didn't execute correctly (%u/%d).\n",
    391              tr->http_status,
    392              tr->ec);
    393     GNUNET_SCHEDULER_shutdown ();
    394     return;
    395   }
    396 
    397   fprintf (stdout,
    398            "Wire transfer #%llu executed successfully at %s.\n",
    399            (unsigned long long) tr->details.ok.row_id,
    400            GNUNET_TIME_timestamp2s (tr->details.ok.timestamp));
    401   global_ret = 0;
    402   GNUNET_SCHEDULER_shutdown ();
    403 }
    404 
    405 
    406 /**
    407  * Ask the bank to execute a wire transfer.
    408  */
    409 static void
    410 execute_wire_transfer (void)
    411 {
    412   struct TALER_WireTransferIdentifierRawP wtid;
    413   void *buf;
    414   size_t buf_size;
    415   char *params;
    416 
    417   if (NULL != debit_account.full_payto)
    418   {
    419     fprintf (stderr,
    420              "Invalid option -C specified, conflicts with -D\n");
    421     GNUNET_SCHEDULER_shutdown ();
    422     return;
    423   }
    424 
    425   /* See if subject was given as a payto-parameter. */
    426   if (NULL == subject)
    427     subject = TALER_payto_get_subject (credit_account);
    428   if (NULL != subject)
    429   {
    430     if (GNUNET_OK !=
    431         GNUNET_STRINGS_string_to_data (subject,
    432                                        strlen (subject),
    433                                        &wtid,
    434                                        sizeof (wtid)))
    435     {
    436       fprintf (stderr,
    437                "Error: wire transfer subject must be a WTID\n");
    438       GNUNET_SCHEDULER_shutdown ();
    439       return;
    440     }
    441   }
    442   else
    443   {
    444     /* pick one at random */
    445     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    446                                 &wtid,
    447                                 sizeof (wtid));
    448   }
    449   params = strchr (credit_account.full_payto,
    450                    (unsigned char) '&');
    451   if (NULL != params)
    452     *params = '\0';
    453   TALER_BANK_prepare_transfer (credit_account,
    454                                &amount,
    455                                "http://exchange.example.com/",
    456                                &wtid,
    457                                &buf,
    458                                &buf_size);
    459   eh = TALER_BANK_transfer (ctx,
    460                             &auth,
    461                             buf,
    462                             buf_size,
    463                             &confirmation_cb,
    464                             NULL);
    465   GNUNET_free (buf);
    466   if (NULL == eh)
    467   {
    468     fprintf (stderr,
    469              "Could not execute the wire transfer\n");
    470     GNUNET_SCHEDULER_shutdown ();
    471     return;
    472   }
    473 }
    474 
    475 
    476 /**
    477  * Function called with the result of the operation.
    478  *
    479  * @param cls closure
    480  * @param air response details
    481  */
    482 static void
    483 res_cb (void *cls,
    484         const struct TALER_BANK_AdminAddIncomingResponse *air)
    485 {
    486   (void) cls;
    487   op = NULL;
    488   switch (air->http_status)
    489   {
    490   case MHD_HTTP_OK:
    491     global_ret = 0;
    492     fprintf (stdout,
    493              "%llu\n",
    494              (unsigned long long) air->details.ok.serial_id);
    495     break;
    496   default:
    497     fprintf (stderr,
    498              "Operation failed with status code %u/%u\n",
    499              (unsigned int) air->ec,
    500              air->http_status);
    501     if (NULL != air->response)
    502       json_dumpf (air->response,
    503                   stderr,
    504                   JSON_INDENT (2));
    505     break;
    506   }
    507   GNUNET_SCHEDULER_shutdown ();
    508 }
    509 
    510 
    511 /**
    512  * Ask the bank to execute a wire transfer to the exchange.
    513  */
    514 static void
    515 execute_admin_transfer (void)
    516 {
    517   struct TALER_ReservePublicKeyP reserve_pub;
    518 
    519   if (NULL != subject)
    520   {
    521     if (GNUNET_OK !=
    522         GNUNET_STRINGS_string_to_data (subject,
    523                                        strlen (subject),
    524                                        &reserve_pub,
    525                                        sizeof (reserve_pub)))
    526     {
    527       fprintf (stderr,
    528                "Error: wire transfer subject must be a reserve public key\n");
    529       return;
    530     }
    531   }
    532   else
    533   {
    534     /* pick one that is kind-of well-formed at random */
    535     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    536                                 &reserve_pub,
    537                                 sizeof (reserve_pub));
    538   }
    539   op = TALER_BANK_admin_add_incoming (ctx,
    540                                       &auth,
    541                                       &reserve_pub,
    542                                       &amount,
    543                                       debit_account,
    544                                       &res_cb,
    545                                       NULL);
    546   if (NULL == op)
    547   {
    548     fprintf (stderr,
    549              "Could not execute the wire transfer to the exchange\n");
    550     GNUNET_SCHEDULER_shutdown ();
    551     return;
    552   }
    553 }
    554 
    555 
    556 /**
    557  * Main function that will be run.
    558  *
    559  * @param cls closure
    560  * @param args remaining command-line arguments
    561  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    562  * @param cfg configuration
    563  */
    564 static void
    565 run (void *cls,
    566      char *const *args,
    567      const char *cfgfile,
    568      const struct GNUNET_CONFIGURATION_Handle *cfg)
    569 {
    570   (void) cls;
    571   (void) args;
    572   (void) cfgfile;
    573   (void) cfg;
    574 
    575   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    576                                  NULL);
    577   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    578                           &rc);
    579   GNUNET_assert (NULL != ctx);
    580   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    581   if (NULL != account_section)
    582   {
    583     if (0 != strncasecmp ("exchange-accountcredentials-",
    584                           account_section,
    585                           strlen ("exchange-accountcredentials-")))
    586     {
    587       fprintf (stderr,
    588                "Error: invalid section specified, must begin with `%s`\n",
    589                "exchange-accountcredentials-");
    590       GNUNET_SCHEDULER_shutdown ();
    591       return;
    592     }
    593     if ( (NULL != auth.wire_gateway_url) ||
    594          (NULL != auth.details.basic.username) ||
    595          (NULL != auth.details.basic.password) )
    596     {
    597       fprintf (stderr,
    598                "Error: Conflicting authentication options provided. Please only use one method.\n");
    599       GNUNET_SCHEDULER_shutdown ();
    600       return;
    601     }
    602     if (GNUNET_OK !=
    603         TALER_BANK_auth_parse_cfg (cfg,
    604                                    account_section,
    605                                    &auth))
    606     {
    607       fprintf (stderr,
    608                "Error: Authentication information not found in configuration section `%s'\n",
    609                account_section);
    610       GNUNET_SCHEDULER_shutdown ();
    611       return;
    612     }
    613   }
    614   else
    615   {
    616     if ( (NULL != auth.wire_gateway_url) &&
    617          (NULL != auth.details.basic.username) &&
    618          (NULL != auth.details.basic.password) )
    619     {
    620       auth.method = TALER_BANK_AUTH_BASIC;
    621     }
    622     else if ( (NULL != auth.wire_gateway_url) &&
    623               (NULL != auth.details.bearer.token) )
    624     {
    625       auth.method = TALER_BANK_AUTH_BEARER;
    626     }
    627 
    628     else if (NULL == auth.wire_gateway_url)
    629     {
    630       fprintf (stderr,
    631                "Error: No account specified (use -b or -s options).\n");
    632       GNUNET_SCHEDULER_shutdown ();
    633       return;
    634     }
    635   }
    636   if ( (NULL == auth.wire_gateway_url) ||
    637        (0 == strlen (auth.wire_gateway_url)) ||
    638        (0 != strncasecmp ("http",
    639                           auth.wire_gateway_url,
    640                           strlen ("http"))) )
    641   {
    642     fprintf (stderr,
    643              "Error: Invalid wire gateway URL `%s' configured.\n",
    644              auth.wire_gateway_url);
    645     GNUNET_SCHEDULER_shutdown ();
    646     return;
    647   }
    648   if ( (GNUNET_YES == incoming_history) &&
    649        (GNUNET_YES == outgoing_history) )
    650   {
    651     fprintf (stderr,
    652              "Error: Please specify only -i or -o, but not both.\n");
    653     GNUNET_SCHEDULER_shutdown ();
    654     return;
    655   }
    656   if (GNUNET_YES == incoming_history)
    657   {
    658     execute_credit_history ();
    659     return;
    660   }
    661   if (GNUNET_YES == outgoing_history)
    662   {
    663     execute_debit_history ();
    664     return;
    665   }
    666   if (NULL != credit_account.full_payto)
    667   {
    668     execute_wire_transfer ();
    669     return;
    670   }
    671   if (NULL != debit_account.full_payto)
    672   {
    673     execute_admin_transfer ();
    674     return;
    675   }
    676 
    677   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    678               "No operation specified.\n");
    679   global_ret = 0;
    680   GNUNET_SCHEDULER_shutdown ();
    681 }
    682 
    683 
    684 /**
    685  * The main function of the taler-exchange-wire-gateway-client
    686  *
    687  * @param argc number of arguments from the command line
    688  * @param argv command line arguments
    689  * @return 0 ok, 1 on error
    690  */
    691 int
    692 main (int argc,
    693       char *const *argv)
    694 {
    695   const struct GNUNET_GETOPT_CommandLineOption options[] = {
    696     TALER_getopt_get_amount ('a',
    697                              "amount",
    698                              "VALUE",
    699                              "value to transfer",
    700                              &amount),
    701     GNUNET_GETOPT_option_string ('b',
    702                                  "bank",
    703                                  "URL",
    704                                  "Wire gateway URL to use to talk to the bank",
    705                                  &auth.wire_gateway_url),
    706     GNUNET_GETOPT_option_string ('C',
    707                                  "credit",
    708                                  "ACCOUNT",
    709                                  "payto URI of the bank account to credit (when making outgoing transfers)",
    710                                  &credit_account.full_payto),
    711     GNUNET_GETOPT_option_string ('D',
    712                                  "debit",
    713                                  "PAYTO-URL",
    714                                  "payto URI of the bank account to debit (when making incoming transfers)",
    715                                  &debit_account.full_payto),
    716     GNUNET_GETOPT_option_flag ('i',
    717                                "credit-history",
    718                                "Ask to get a list of 10 incoming transactions.",
    719                                &incoming_history),
    720     GNUNET_GETOPT_option_flag ('o',
    721                                "debit-history",
    722                                "Ask to get a list of 10 outgoing transactions.",
    723                                &outgoing_history),
    724     GNUNET_GETOPT_option_string ('p',
    725                                  "pass",
    726                                  "PASSPHRASE",
    727                                  "passphrase to use for authentication",
    728                                  &auth.details.basic.password),
    729     GNUNET_GETOPT_option_string ('s',
    730                                  "section",
    731                                  "ACCOUNT-SECTION",
    732                                  "Which config section has the credentials to access the bank. Conflicts with -b -u and -p options.\n",
    733                                  &account_section),
    734     GNUNET_GETOPT_option_string ('S',
    735                                  "subject",
    736                                  "SUBJECT",
    737                                  "specifies the wire transfer subject",
    738                                  &subject),
    739     GNUNET_GETOPT_option_string ('u',
    740                                  "user",
    741                                  "USERNAME",
    742                                  "username to use for authentication",
    743                                  &auth.details.basic.username),
    744     GNUNET_GETOPT_option_ulong ('w',
    745                                 "since-when",
    746                                 "ROW",
    747                                 "When asking the bank for transactions history, this option commands that all the results should have IDs settled after SW.  If not given, then the 10 youngest transactions are returned.",
    748                                 &start_row),
    749     GNUNET_GETOPT_OPTION_END
    750   };
    751   enum GNUNET_GenericReturnValue ret;
    752 
    753   global_ret = 1;
    754   ret = GNUNET_PROGRAM_run (
    755     TALER_EXCHANGE_project_data (),
    756     argc, argv,
    757     "taler-wire-gateway-client",
    758     gettext_noop ("Client tool of the Taler Wire Gateway"),
    759     options,
    760     &run, NULL);
    761   if (GNUNET_SYSERR == ret)
    762     return 3;
    763   if (GNUNET_NO == ret)
    764     return 0;
    765   return global_ret;
    766 }
    767 
    768 
    769 /* end taler-wire-gateway-client.c */