22.2. Wallet Developer Manual#
Note
This manual contains information for developers working on the wallet component of GNU Taler. It is not intended for a general audience.
The GNU Taler wallet allows customers to withdraw and spend digital cash.
Additional implementation notes about request retries, cancellation, and failure reporting are documented in Wallet Error Handling Notes.
22.2.2. WebExtension Wallet#
22.2.2.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
22.2.3. Android Wallet#
Please see Building apps from source in the General Developer Manual.
22.2.4. iOS Wallet#
Please see Building Taler Wallet for iOS from source in the General Developer Manual.
22.2.5. Command-line Wallet#
This section describes how to use the GNU Taler wallet command line interface (CLI).
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.
22.2.5.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.
22.2.5.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
22.2.5.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.
22.2.5.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
22.2.5.5. Resetting the wallet#
The wallet can be reset by deleting its database file. By default, the database file
is $HOME/.talerwalletdb.sqlite3.
22.2.5.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
22.2.5.7. Manual withdrawing#
$ taler-wallet-cli advanced withdraw-manually \
--exchange https://exchange.eurint.taler.net/ \
--amount EUR:5
22.2.5.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
22.2.5.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.
22.2.5.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
22.2.6. APIs and Data Formats#
22.2.6.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;
}
22.2.6.2. Withdrawal#
A typical API sequence for bank-integrated withdrawals can for example look like this:
"getWithdrawalDetailsForUri"returns an amount and default exchange"getWithdrawalDetailsForAmount"returns fee information and that ToS are not accepted"getExchangeTos"are shown to the user and return currentEtag"setExchangeTosAccepted"called with currentEtag after user accepted
"acceptWithdrawal"after the user confirmed withdrawal with associated fees
A typical API sequence for manual withdrawals can for example look like this:
"listExchanges"shows a list of exchanges to the user who picks one and an amount"getWithdrawalDetailsForAmount"returns fee information and that ToS are not accepted"getExchangeTos"are shown to the user and return currentEtag"setExchangeTosAccepted"called with currentEtag after user accepted
"acceptManualWithdrawal"after the user confirmed withdrawal with associated fees
22.2.7. Integration Tests#
22.2.7.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/
22.2.7.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):
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
22.2.8. Transaction lifecycle contract#
Wallet transactions expose their current state, user-visible information, and
permitted txActions through the wallet API. UIs MUST derive buttons from
txActions rather than infer them from a locally duplicated transition
table. The common lifecycle classes are:
pendingandfinalizing: wallet processing is active;dialog: an explicit user decision is required;suspended: processing was paused and may offer resume or deletion;aborting: compensating/refund work is active;done: the intended operation completed;failedoraborted: processing ended unsuccessfully, with the reason carried by the transaction response; anddeleted: the history entry is hidden, while cryptographic records still needed for spend, recoup, or auditing may remain.
Network retries and internal self-transitions need not change the lifecycle class. An implementation can expose error details while remaining pending. Deletion is not cancellation: a transaction must first use an offered abort action when protocol-side compensation is necessary.
22.2.9. KYC-gated operation retry#
Withdrawals, deposits, and peer-to-peer operations use one common retry algorithm when an exchange denies progress for KYC or AML reasons:
Attempt the operation unless the most recent denial is less than one hour old. Success ends processing; HTTP 451 records the denial and whether the account authorization was invalid; unrelated failures back off.
Query the operation’s
/kyc-check/endpoint. The first request does not long-poll. Later requests long-poll for account authorization, rule, or AML changes and includemin_rulewhen a rule generation is known.Compare the HTTP status, Taler error code, and rule generation with the previous response. An unchanged response backs off. HTTP 200 or 204 retries the operation; HTTP 202 evaluates the exposed limits; HTTP 403 can switch to a requested account key when the wallet owns it; HTTP 404 either requests new authorization or evaluates default limits.
A
verbotenlimit permanently fails the transaction. A time-window limit schedules the next attempt for the first permitted time. Otherwise the wallet immediately retries the operation.
Manual review of KYC instructions, a new authorization transfer, or relevant progress in another operation resets the retry delay. Repeated network timeouts still use exponential backoff even when the HTTP long-poll duration is shortened to fit middleware limits.
22.2.10. Exchange base-URL migration#
Wallet-core maintains explicit old URL -> new URL migration plans. A plan
is applied only after the old exchange is unavailable or reports a mismatching
canonical base_url and the new URL returns a valid /keys response whose
base_url matches that new URL. Applying a plan atomically replaces stored
references and records the old URL, new URL, and migration timestamp.
Operators must publish wallet support before moving the exchange, allow a
client-update grace period, migrate the database and optional security-module
keys, and keep a reverse proxy from the old URL for at least the validity of
the last old /keys response. Merchants must configure the new URL
explicitly; wallet migration plans do not rewrite merchant configuration.
22.2.11. IBAN and BBAN presentation#
The wallet API converts between the protocol’s payto://iban representation
and a user-facing account field. convertIbanAccountFieldToPayto accepts an
IBAN or the currency’s supported BBAN form and returns the normalized payto
URI plus the detected entry type. convertIbanPaytoToAccountField performs
the reverse conversion. Deposit wire types advertise preferredEntryType
so UIs do not maintain a separate currency table.
HUF accounts use BBAN entry/display for production iban wire targets. The
CHF BBAN mapping is reserved for the wallet development experiment. A BBAN
field must also accept a pasted full IBAN. Protocol messages continue to use
the normalized payto URI; BBAN is a presentation and entry convention only.
22.2.12. Diagnostics export privacy#
getDiagnostics produces support information suitable for saving or
sharing without exporting the wallet database. The response MUST NOT contain
private keys or other secret key material. Account identifiers such as IBANs
must be truncated to at most six characters, and user names or equivalent
identifiers must be removed or truncated. Adding fields to the response
requires a privacy review against these invariants. The diagnostics action is
available outside developer mode because it is the preferred alternative to a
database export.
22.2.13. Coin selection#
For one exchange, wallet-core selects eligible coins as follows:
Traverse denominations from smallest to largest and add coins until their value covers the target, using the earliest-expiring coins first within a denomination.
Traverse selected denominations from largest to smallest and remove coins whenever the remainder still covers the target. Obtain change from the smallest indispensable coin when needed.
When customer-paid fees remain above the merchant allowance and the wallet is not imbalanced, replace groups of small coins with equivalent larger coins until fees fall below the allowance or no useful replacement exists.
A wallet is considered imbalanced for this step when it holds, on average, more than five times the denomination ratio in coins per denomination, excluding the largest denomination. This keeps selection linear, spends old coins first, and limits small-coin accumulation. Multiple-exchange selection is outside this algorithm and must be handled by a higher-level decision.
22.2.14. Wallet color roles#
Wallet UIs use semantic theme roles instead of literal colors in components.
Use roles such as primary and onPrimary for an accent and its
foreground, the corresponding Container pair for filled surfaces, and
error, warning, and success roles for feedback. Surface,
background, outline, and foreground roles must likewise be paired by meaning;
do not choose a role merely because its current hex value looks suitable.
Every component must obtain roles from the platform theme so light and dark modes can switch without component-specific branches. New role pairs and overrides require WCAG 2.1 AA contrast verification in both modes, including disabled, selected, focused, and error states. Native and WebExtension implementations may encode themes differently, but should retain the same semantic names and intended hierarchy. The evolving palette and adoption status are recorded in DD 66: Wallet UI Color Scheme.
22.2.15. Wallet database migrations#
Wallet database upgrades are automatic and must preserve the previous data until the new representation is known to be usable. The browser wallet uses an IndexedDB metadata database to identify the active major database and a separately named major-version database for wallet records. This indirection allows a major migration to build a new database without destroying the old one first.
IndexedDB changes use three mechanisms:
A major migration creates a new major-version database and explicitly moves data. This is reserved for changes that cannot be expressed safely in an IndexedDB upgrade transaction; a backup export/import cycle is preferred when practical.
A minor schema migration adds or removes object stores or indexes by changing the schema declaration and its
versionAddedmetadata.An ordered fixup transforms stored values, such as when a mandatory field or serialized representation changes. Fixups can also repair data written by an already deployed buggy version.
The native SQLite backend has its own ordered schema migrations. Migration from the IndexedDB emulation to native SQLite is a separate, explicit path; backend versions must not be inferred to be interchangeable. Every migration needs tests starting from representative old data, including interrupted and retry scenarios. Current schema declarations and fixup registries in wallet-core are authoritative; the design history is in DD 34: Considerations for Wallet Database Migrations.
22.2.16. 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.
22.2.16.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
22.2.16.2. Faking Transactions#
Withdrawal Transaction:
taler://dev-experiment/add-fake-tx?txType=withdrawal&amountEffective=KUDOS:5
Options:
amountEffective: Mandatory. Effective amount of the withdrawal.tRel: Optional (defaults to0s). Relative time that indicates when in the past the transaction was started (and finished).exchangeBaseUrl. Optional (defaults tohttps://exchange.demo.taler.net). Exchange base URL used for the withdrwal.
Payment Transaction:
taler://dev-experiment/add-fake-tx?txType=payment&amountEffective=KUDOS:5
Options:
amountEffective: Mandatory. Effective amount of the withdrawal.tRel: Optional (defaults to0s). Relative time that indicates when in the past the transaction was started (and finished).exchangeBaseUrl. Optional (defaults tohttps://exchange.demo.taler.net). Exchange base URL used for the payment.merchantBaseUrl. Optional (defaults tohttps://backend.demo.taler.net/).merchantName. Optional (defaults toTest Merchant). Display name of the merchant in the contract terms of the transaction.summary. Optional (defaults toTest). Summary in the contract terms of the transaction.
Peer Push Credit Transaction:
taler://dev-experiment/add-fake-tx?txType=peer-push-credit&amountEffective=KUDOS:5
Options:
amountEffective: Mandatory. Effective amount of the withdrawal.tRel: Optional (defaults to0s). Relative time that indicates when in the past the transaction was started (and finished).exchangeBaseUrl. Optional (defaults tohttps://exchange.demo.taler.net). Exchange base URL used for the payment.summary. Optional (defaults toTest). Summary in the contract terms of the transaction.
Peer Push Debit Transaction:
taler://dev-experiment/add-fake-tx?txType=peer-push-debit&amountEffective=KUDOS:5
Options:
amountEffective: Mandatory. Effective amount of the withdrawal.tRel: Optional (defaults to0s). Relative time that indicates when in the past the transaction was started (and finished).exchangeBaseUrl. Optional (defaults tohttps://exchange.demo.taler.net). Exchange base URL used for the payment.summary. Optional (defaults toTest). Summary in the contract terms of the transaction.