4. Wallet Developer Manual#

The GNU Taler wallet allows customers to withdraw and spend digital cash.

4.1. WebExtension Wallet#

4.1.1. Building from source#

$ git clone https://git.taler.net/wallet-core.git
$ cd wallet-core
$ ./configure
$ make webex-stable
# Packaged extension now available as:
# dist/taler-wallet-$VERSION.zip

4.2. Android Wallet#

Please see Building apps from source in the Developer’s Manual.

4.3. iOS Wallet#

Please see Building Taler Wallet for iOS from source in the Developer’s Manual.

4.4. Command-line Wallet#

This section describes how to use the GNU Taler wallet command line interface (CLI).

The the wallet CLI is targeted at developers and operators, but not meant to be used by customers. It exposes all functionality that the more user-friendly interfaces (Android app, browser extension) offer. However, it provides more diagnostics and advanced features as well.

4.4.1. Building from source#

The easiest way to install the wallet is via NPM. Note that a recent version of Node.JS (>=12.20.1) is required.

We recommend to install the wallet package on a per-user basis, thus setting $INSTALL_PREFIX to a directory in $HOME.

$ git clone https://git.taler.net/wallet-core.git
$ cd wallet-core
$ ./bootstrap
$ ./configure --prefix=$INSTALL_PREFIX
$ make && make install

The wallet command-line interface should then be available as taler-wallet-cli under $INSTALL_PREFIX/bin.

4.4.2. Installation via NPM#

The wallet can also obtained via NPM, the Node Package Manager.

To install the wallet as a global package, run:

$ npm install -g taler-wallet
# check if installation was successful
$ taler-wallet-cli --version

To install the wallet only for your user, run:

$ npm install -g --prefix=$HOME/local taler-wallet
# check if installation was successful
$ taler-wallet-cli --version
# If this fails, make sure that $HOME/local/bin is in your $PATH

To use the wallet as a library in your own project, run:

$ npm install taler-wallet

4.4.3. Getting Help#

The wallet CLI comes with built-in help. Invoke the wallet CLI (or any subcommand) with the --help flag to get help:

$ taler-wallet-cli --help
Usage: taler-wallet-cli COMMAND

Command line interface for the GNU Taler wallet.

Options:
  -h, --help             Show this message and exit.
  --wallet-db=VALUE      location of the wallet database file
  --timetravel=VALUE     modify system time by given offset in microseconds
  --inhibit=VALUE        Inhibit running certain operations, useful for debugging and testing.
  --no-throttle          Don't do any request throttling.
  -v, --version
  -V, --verbose          Enable verbose output.

Commands:
  advanced               Subcommands for advanced operations (only use if you know what you're doing!).
  api                    Call the wallet-core API directly.
  backup                 Subcommands for backups
  balance                Show wallet balance.
  deposit                Subcommands for depositing money to payto:// accounts
  exchanges              Manage exchanges.
  handle-uri             Handle a taler:// URI.
  pending                Show pending operations.
  run-pending            Run pending operations.
  run-until-done         Run until no more work is left.
  testing                Subcommands for testing GNU Taler deployments.
  transactions           Show transactions.

4.4.4. Completing operations#

Note that the CLI does not run as a background daemon. When starting operations that don’t immediately finish, the wallet needs to be run explicitly to finish any pending tasks:

# Do one attempt to finish all pending operations
$ taler-wallet-cli run-pending

# Run until all work is done
$ taler-wallet-cli run-until-done

4.4.5. Resetting the wallet#

The wallet can be reset by deleting its database file. By default, the database file is $HOME/.talerwalletdb.sqlite3.

4.4.6. Handling taler:// URIs#

Many interactions with the Taler wallet happen by scanning QR codes or special headers on Websites. To emulate this with the command line interface, run the following command:

$ taler-wallet-cli handle-uri $URI

4.4.7. Manual withdrawing#

$ taler-wallet-cli advanced withdraw-manually \
    --exchange https://exchange.eurint.taler.net/ \
    --amount EUR:5

4.4.8. P2P push payments#

The following code generates a P2P push transaction over 1 CHF with an expiration time of 30 days (assuming the wallet has a sufficient balance):

$ taler-wallet-cli p2p initiate-push-debit \
    --purse-expiration="30 d" \
    --summary="The summary" \
    CHF:1

The final URL can then be found in the transaction list:

$ taler-wallet-cli transactions

4.4.9. Background wallet#

A wallet can be launched in the background:

$ taler-wallet-cli advanced serve &

You can then run various Taler operations faster against this one persistent instance:

$ taler-wallet-cli --wallet-connection=wallet-core.sock ...

Here ... needs to be changed to the commando to run. Make sure to run

$ taler-wallet-cli --wallet-connection=wallet-core.sock \
    run-until-done

to wait for pending transactions to complete.

4.4.10. Testing an exchange deployment#

The following series of commands can be used to check that an exchange deployment is functional:

# This will now output a payto URI that money needs to be sent to in order to allow withdrawal
# of taler coins
$ taler-wallet-cli advanced withdraw-manually --exchange $EXCHANGE_URL --amount EUR:10.50

# Show the status of the manual withdrawal operation
$ taler-wallet-cli transactions

# Once the transfer has been made, try completing the withdrawal
$ taler-wallet-cli run-pending

# Check status of transactions and show balance
$ taler-wallet-cli transactions
$ taler-wallet-cli balance

# Now, directly deposit coins with the exchange into a target account
# (Usually, a payment is made via a merchant.  The wallet provides
# this functionality for testing.)
$ taler-wallet-cli deposit create EUR:5 payto://iban/$IBAN

# Check if transaction was successful.
# (If not, fix issue with exchange and run "run-pending" command again)
$ taler-wallet-cli transactions

# The wallet can also track if the exchange wired the money to the merchant account.
# The "deposit group id" can be found in the output of the transactions list.
$ taler-wallet-cli deposit track $DEPOSIT_GROUP_ID

4.5. APIs and Data Formats#

4.5.1. Envelope Format#

All API responses and notifications are returned in the following envelope:

type WalletResponseEnvelope =
 | WalletSuccess
 | WalletError
 | WalletNotification
export interface WalletSuccess {
  type: "response";
  operation: string;
  // ID to correlate success response to request
  id: string;
  // Result type depends on operation
  result: unknown;
}
export interface WalletError {
  type: "error";
  operation: string;
  // ID to correlate error response to request
  id: string;
  error: WalletErrorInfo;
}
export interface WalletSuccess {
  type: "notification";

  // actual type is WalletNotification,
  // to be documented here
  payload: any;
}
export interface WalletErrorInfo {
  // Numeric error code defined defined in the
  // GANA gnu-taler-error-codes registry.
  talerErrorCode: number;

  // English description of the error code.
  talerErrorHint: string;

  // English diagnostic message that can give details
  // for the instance of the error.
  message: string;

  // Error details, type depends
  // on talerErrorCode
  details: unknown;
}

4.5.2. Withdrawal#

A typical API sequence for bank-integrated withdrawals can for example look like this:

  1. "getWithdrawalDetailsForUri" returns an amount and default exchange

  2. "getWithdrawalDetailsForAmount" returns fee information and that ToS are not accepted

    1. "getExchangeTos" are shown to the user and return currentEtag

    2. "setExchangeTosAccepted" called with currentEtag after user accepted

  3. "acceptWithdrawal" after the user confirmed withdrawal with associated fees

A typical API sequence for manual withdrawals can for example look like this:

  1. "listExchanges" shows a list of exchanges to the user who picks one and an amount

  2. "getWithdrawalDetailsForAmount" returns fee information and that ToS are not accepted

    1. "getExchangeTos" are shown to the user and return currentEtag

    2. "setExchangeTosAccepted" called with currentEtag after user accepted

  3. "acceptManualWithdrawal" after the user confirmed withdrawal with associated fees

4.6. Integration Tests#

4.6.1. Integration Test Example#

Integration tests can be done with the low-level wallet commands. To select which coins and denominations to use, the wallet can dump the coins in an easy-to-process format (CoinDumpJson).

The database file for the wallet can be selected with the --wallet-db option. This option must be passed to the taler-wallet-cli command and not the subcommands. If the database file doesn’t exist, it will be created.

The following example does a simple withdrawal recoup:

# Withdraw digital cash
$ taler-wallet-cli --wallet-db=mydb.sqlite3 testing withdraw \
    -b https://bank.int.taler.net/ \
    -e https://exchange.int.taler.net/ \
    -a INTKUDOS:10

$ coins=$(taler-wallet-cli --wallet-db=mydb.sqlite3 advanced dump-coins)

# Find coin we want to revoke
$ rc=$(echo "$coins" | \
       jq -r '[.coins[] | select((.denom_value == "INTKUDOS:5"))][0] | .coin_pub')

# Find the denom
$ rd=$(echo "$coins" | \
       jq -r '[.coins[] | select((.denom_value == "INTKUDOS:5"))][0] | .denom_pub_hash')

# Find all other coins, which will be suspended
$ susp=$(echo "$coins" | \
         jq --arg rc "$rc" '[.coins[] | select(.coin_pub != $rc) | .coin_pub]')

# The exchange revokes the denom
$ taler-exchange-keyup -r $rd
$ taler-deployment-restart

# Now we suspend the other coins, so later we will pay with the recouped coin
$ taler-wallet-cli --wallet-db=mydb.sqlite3 advanced suspend-coins "$susp"

# Update exchange /keys so recoup gets scheduled
$ taler-wallet-cli --wallet-db=mydb.sqlite3 exchanges update -f https://exchange.int.taler.net/

# Block until scheduled operations are done
$ taler-wallet-cli --wallet-db=mydb.sqlite3 run-until-done

# Now we buy something, only the coins resulting from recouped will be
# used, as other ones are suspended
$ taler-wallet-cli --wallet-db=mydb.sqlite3 testing test-pay \
    -m https://backend.int.taler.net/ \
    -k sandbox \
    -a "INTKUDOS:1" \
    -s "foo"
$ taler-wallet-cli --wallet-db=mydb.sqlite3 run-until-done

To test refreshing, force a refresh:

$ taler-wallet-cli --wallet-db=mydb.sqlite3 advanced force-refresh "$coin_pub"

To test zombie coins, use the timetravel option. It must be passed to the top-level command and not the subcommand:

# Update exchange /keys with time travel, value in microseconds
$ taler-wallet-cli --timetravel=1000000 --wallet-db=mydb.sqlite3 \
    exchanges update -f https://exchange.int.taler.net/

4.6.2. Integration Test and Fault Injection Framework#

This section describes the current approach to integration testing in the wallet.

It’s all based on a TypeScript harness process, which itself implements the fault injection proxy (async and in-process)!

The new approach consists of the following parts:

1. A strongly typed, convenient helper library to easily set up and run arbitrary Taler deployments and run test cases. These components plug together as easily as lego bricks, even with multiple exchanges/merchants/banks/etc. Logs and clean shutdown (even on SIGINT or errors) are handled properly. (Support for auditors is still pending but needed to fully test the wallet.)

This is how a simple withdrawal and payment test case looks like: https://git.taler.net/taler-typescript-core.git/tree/packages/taler-harness/src/integrationtests/test-payment.ts

(What’s particularly nice is that all our docs contain TypeScript definitions for all API request bodies. So just copying them into the test harness gives us auto-completion and compile-time checks to avoid typos. The wallet’s JSON validation machinery is also re-used.)

2. A fault injection proxy that can be plugged between the services and/or the wallet. It runs alongside the test harness, and can thus can use arbitrary custom logic. There’s no dependency for it other than built-in Node.JS libraries. Simple fault injections are just as easy to set up as with the twister.

The following test case (a) logs all requests and responses to the test harness stdout and (b) at a certain point, starts dropping the next 10 requests to the exchange (testing the wallet’s retry logic):

https://git.taler.net/taler-typescript-core.git/tree/packages/taler-harness/src/integrationtests/test-payment-fault.ts#n165

3. All util functionality from JS wallet-core, such as the Taler crypto, amount/date/etc. handling and JSON parsing/validation (the wallet is now more modular and easier to use as a library) can be used in the integration tests, even if a different wallet (Kotlin, whatever) is tested via the CLI.

4. A bunch of test cases that use (1)-(3). These are significantly more readable and hackable than other test approaches we had, while allowing for more complex scenarios. There are still way too few tests though!

5. A test runner (written in bash) that runs test cases based on a glob pattern and reports the results.

Injecting a fault is as easy as:

// Set up test case
[...]

exchangeProxy.addFault({
  beforeResponse(ctx: FaultInjectionResponseContext) {
     if (cond1) { // Drop some responses
       ctx.dropResponse = true;
       return;
     } else if (cond2) { // modify some others
       ctx.responseBody = Buffer.from(`{"oops": true}`, "utf-8");
       return;
     }
     // Other things that can be modified:
     // - drop/modify the request, not just the response
     // - modify headers
     // - modify status codes
  }
});

await doSomethingWithTheWallet();

exchangeProxy.clearFault();

await doMoreWithTheWallet();

To make the configuration easy, an ExchangeService (or MerchantService, BankService etc.) can be wrapped in a FaultInjectedExchangeService, which implements the ExchangeServiceInterface:

// create exchange and two merchants
const exchange = await setupExchange(...);
const merchant1 = ...;
const merchant2 = ...;

// Add exchange to merchant-accepted exchanges.
// This will adjust the config.
merchant1.addExchange(exchange);

// Wrap exchange in fault injection proxy
const faultInjectedExchange: ExchangeServiceInterface
  = new FaultInjectedExchangeService(t, exchange1, 8085);

// Merchant 2 talks to the exchange over fault injection,
// and thus must use the "twisted" base URL.
merchant2.addExchange(faultInjectedExchange);

The package for the integration tests is here:

https://git.taler.net/wallet-core.git/tree/packages/taler-harness

The integration tests are run via the taler-harness tool.

./bootstrap && ./configure --prefix=... && make install
taler-harness run-integrationtests

4.7. Dev Experiments#

Dev experiments allow simulating certain scenarios that are difficult to reproduce otherwise. This allows more comprehensive (manual) testing of the UIs.

You can enable dev experiments by putting the wallet into dev mode and then scanning the QR code for a taler://dev-experiment URI that specifies the desired dev experiment.

4.7.1. Faking Protocol Versions#

The start-fakeprotover dev experiment can be used to fake the protocol version reported by Taler components. It mocks the version field in the response to /config or /keys.

Usage:

taler://dev-experiment/start-fakeprotover?base_url=...&fake_ver=...

Example:

# Fake version 10:0:0 for https://exchange.demo.taler.net/
taler://dev-experiment/start-fakeprotover?base_url=https%3A%2F%2Fexchange.demo.taler.net%2F&fake_ver=10%3A0%3A0