046-mumimo-contracts.rst (27564B)
1 DD 46: Contract Format v1 2 ######################### 3 4 :Design status: Accepted 5 :Implementation status: Partial 6 :DD shepherd: TBD 7 :Historical contributors: Christian Blättler, Stefan Kügel, Florian Dold, Christian Grothoff 8 :First published: 2023-08-11 9 :Last substantive change: 2026-08-04 10 :Implementation evidence: merchant (2025-03-05), taler-typescript-core (2026-07-06) 11 :Normative references: ``core/api-merchant.rst``, ``wallet/wallet-core.md`` 12 13 .. note:: 14 15 The REST API specifications are normative for the implemented wire format. 16 Future-work and rationale sections in this DD do not extend those APIs. 17 18 Summary 19 ======= 20 21 The contract v1 format enables a multitude of advanced interactions between 22 merchants and wallets, including donations, subscriptions, coupons, currency 23 exchange and more. 24 25 Motivation 26 ========== 27 28 The existing v0 contract format is too simplistic to 29 support many frequenly requested types of contracts. 30 31 Requirements 32 ============ 33 34 We want Taler to support various interesting use-cases: 35 36 - Unlinkable, uncopyable subscriptions without accounts (reader can pay with 37 Taler to subscribe to online publication, read unlimited number of 38 articles during a certain period, transfer subscription to other devices, 39 maintain unlinkability / full anonymity amongst all anonymous 40 subscribers). 41 42 - Coupons, discounts and stamps -- like receiving a discount on a product, 43 product basket or subscription -- based on previous purchase(s). Again, 44 with unlinkability and anonymity (modulo there being other users eligible 45 for the discount). 46 47 - Subscription tokens lost (due to loss of device without backup) should 48 be recoverable from any previous backup of the subscription. 49 50 - Currency conversion, that is exchanging one currency for another. 51 52 - Donations, including privacy-preserving tax receipts that prove that the 53 user donated to an entity that is eligible for tax-deductions but without 54 revealing which entity the user donated to. At the same time, the entity 55 issuing the tax receipt must be transparent (to the state) with respect to 56 the amount of tax-deductable donations it has received. 57 58 - Throttled political donations where each individual is only allowed to 59 donate anonymously up to a certain amount per year or election cycle. 60 61 - Unlinkable gifts -- enabling the purchase of digital goods (such as 62 articles, albums, etc.) to be consumed by a third party. For example, a 63 newspaper subscription may include a fixed number of articles that can be 64 gifted to others each week, all while maintaining unlinkability and 65 anonymity between the giver and the recipient. 66 67 - Temporally-constrained, unlinkable event ticketing. Allowing visitors to 68 use Taler to purchase a ticket for an event. This ticket grants entry and 69 exit privileges to the event location during a specified time window, while 70 preserving the anonymity of the ticket holder (within the group of all the 71 ticket holders). 72 73 - Event deposit systems. A deposit mechanism for events where customers 74 receive a token alongside their cup or plate, which they are expected to 75 return. This system validates that the cup or plate was legitimately 76 acquired (i.e., not brought from home or stolen from a stack of dirty items) 77 and incentivizes return after use. 78 79 80 Proposed Solution 81 ================= 82 83 Merchants will also blindly sign tokens (not coins) to indicate the 84 eligibility of a user for certain special offers. Contracts will be modified 85 to allow requiring multiple inputs (to be *provisioned* to the merchant) and 86 multiple outputs (to be *yielded* by the merchant). The wallet will then allow 87 the user to select between the choices that the user could pay for, or possibly 88 make an automatic choice if the correct choice is obvious. One output option is 89 blindly signed coins from another exchange, possibly in a different currency. 90 Another output option is blindly signed donation receipts from a DONation 91 AUthority (DONAU). Subscriptions can be modeled by requiring the wallet to 92 provision a token of the same type that is also yielded by the contract. For 93 security, payments using subscription tokens (and possibly certain other special 94 tokens?) will be limited to a list of domains explicitly defined as trusted by 95 the token issuer. When paying for a contract, the wallet must additionally sign 96 over the selected sub-contract index and a hash committing it to the blinded 97 envelopes (if any). The merchant backend will (probably?) need to be changed 98 to truly support multiple currencies (ugh). 99 100 .. _contract-terms-v1: 101 102 New Contract Terms Format 103 ------------------------- 104 105 The contract terms v1 will have the following structure: 106 107 .. ts:def:: DDContractTermsV1 108 109 interface DDContractTermsV1 { 110 // This is version 1, the previous contract terms SHOULD 111 // be indicated using "0", but in v0 specifying the version 112 // is optional. 113 version: 1; 114 115 // Unique, free-form identifier for the proposal. 116 // Must be unique within a merchant instance. 117 // For merchants that do not store proposals in their DB 118 // before the customer paid for them, the ``order_id`` can be used 119 // by the frontend to restore a proposal from the information 120 // encoded in it (such as a short product identifier and timestamp). 121 order_id: Slug; 122 123 // URL where the same contract could be ordered again (if 124 // available). Returned also at the public order endpoint 125 // for people other than the actual buyer (hence public, 126 // in case order IDs are guessable). 127 public_reorder_url?: WebURL; 128 129 // Time when this contract was generated. 130 timestamp: Timestamp; 131 132 // After this deadline, the merchant won't accept payments for the contract. 133 pay_deadline: Timestamp; 134 135 // Transfer deadline for the exchange. Must be in the 136 // deposit permissions of coins used to pay for this order. 137 wire_transfer_deadline: Timestamp; 138 139 // Merchant's public key used to sign this proposal; this information 140 // is typically added by the backend. Note that this can be an ephemeral key. 141 merchant_pub: EddsaPublicKey; 142 143 // Base URL of the (public!) merchant backend API. 144 // Must be an absolute URL that ends with a slash. 145 merchant_base_url: WebURL; 146 147 // More info about the merchant (same as in v0). 148 merchant: Merchant; 149 150 // Human-readable description of the contract. 151 summary: string; 152 153 // Map from IETF BCP 47 language tags to localized summaries. 154 summary_i18n?: { [lang_tag: string]: string }; 155 156 // URL that will show that the order was successful after 157 // it has been paid for. Optional. When POSTing to the 158 // merchant, the placeholder "${ORDER_ID}" will be 159 // replaced with the actual order ID (useful if the 160 // order ID is generated server-side and needs to be 161 // in the URL). 162 // Note that this placeholder can only be used once. 163 // Because of that placeholder, the value given here is merely a 164 // string and not yet a `WebURL`; only after the substitution has 165 // been performed is the result a `WebURL`. 166 // Either fulfillment_url or fulfillment_message must be specified. 167 fulfillment_url?: string; 168 169 // Message shown to the customer after paying for the order. 170 // Either fulfillment_url or fulfillment_message must be specified. 171 fulfillment_message?: string; 172 173 // Map from IETF BCP 47 language tags to localized fulfillment 174 // messages. 175 fulfillment_message_i18n?: { [lang_tag: string]: string }; 176 177 // List of products that are part of the purchase (see `ProductSold`). 178 products: ProductSold[]; 179 180 // After this deadline has passed, no refunds will be accepted. 181 refund_deadline: Timestamp; 182 183 // Specifies for how long the wallet should try to get an 184 // automatic refund for the purchase. If this field is 185 // present, the wallet should wait for a few seconds after 186 // the purchase and then automatically attempt to obtain 187 // a refund. The wallet should probe until "delay" 188 // after the payment was successful (i.e. via long polling 189 // or via explicit requests with exponential back-off). 190 // 191 // In particular, if the wallet is offline 192 // at that time, it MUST repeat the request until it gets 193 // one response from the merchant after the delay has expired. 194 // If the refund is granted, the wallet MUST automatically 195 // recover the payment. This is used in case a merchant 196 // knows that it might be unable to satisfy the contract and 197 // desires for the wallet to attempt to get the refund without any 198 // customer interaction. Note that it is NOT an error if the 199 // merchant does not grant a refund. 200 auto_refund?: RelativeTime; 201 202 // Delivery location for (all!) products (same as in v0). 203 delivery_location?: Location; 204 205 // Time indicating when the order should be delivered. 206 // May be overwritten by individual products. 207 delivery_date?: Timestamp; 208 209 // Nonce generated by the wallet and echoed by the merchant 210 // in this field when the proposal is generated. 211 // Note: required in contract, absent in order! 212 nonce: string; 213 214 // Array of possible specific contracts the wallet/customer 215 // may choose from by selecting the respective index when 216 // signing the deposit confirmation. 217 choices: DDContractChoice[]; 218 219 // Map from token family slugs to meta data about the 220 // respective token family. 221 token_families: { [token_family_slug: Slug]: ContractTokenFamily }; 222 223 // Extra data that is only interpreted by the merchant frontend. 224 // Useful when the merchant needs to store extra information on a 225 // contract without storing it separately in their database. 226 extra?: any; 227 228 // Exchanges that the merchant accepts for this currency. 229 exchanges: Exchange[]; 230 } 231 232 .. ts:def:: DDContractChoice 233 234 interface DDContractChoice { 235 // Price to be paid for this choice. Could be 0. 236 // The price is in addition to other instruments, 237 // such as rations and tokens. 238 // The exchange will subtract deposit fees from that amount 239 // before transferring it to the merchant. 240 amount: Amount; 241 242 // List of inputs the wallet must provision (all of them) to 243 // satisfy the conditions for the contract. 244 inputs: DDContractInput[]; 245 246 // List of outputs the merchant promises to yield (all of them) 247 // once the contract is paid. 248 outputs: DDContractOutput[]; 249 250 // Maximum total deposit fee accepted by the merchant for this contract. 251 max_fee: Amount; 252 } 253 254 .. ts:def:: DDContractInput 255 256 type DDContractInput = 257 | DDContractInputRation 258 | DDContractInputToken; 259 260 .. ts:def:: DDContractInputRation 261 262 interface DDContractInputRation { 263 type: "coin"; 264 265 // Price to be paid for the transaction. 266 price: Amount; 267 268 // FIXME-DOLD: do we want to move this into a 'details' 269 // sub-structure as done with tokens below? 270 class: "ration"; 271 272 // Base URL of the ration authority. 273 ration_authority_url: WebURL; 274 }; 275 276 .. ts:def:: DDContractInputToken 277 278 interface DDContractInputToken { 279 type: "token"; 280 281 // Slug of the token family in the 282 // 'token_families' map on the order. 283 token_family_slug: Slug; 284 285 // Start of the validity period of the token. This is used to find the 286 // matching public key within the token family. 287 valid_after: Timestamp; 288 289 // Number of tokens of this type required. 290 // Defaults to one if the field is not provided. 291 number?: Integer; 292 }; 293 294 .. ts:def:: DDContractOutput 295 296 type DDContractOutput = 297 | DDContractOutputCoin 298 | DDContractOutputTaxReceipt 299 | DDContractOutputToken; 300 301 .. ts:def:: DDContractOutputCoin 302 303 interface DDContractOutputCoin { 304 type: "coins"; 305 306 // Amount of coins that will be yielded. 307 // This excludes any applicable withdraw fees. 308 brutto_yield: Amount; 309 310 // Base URL of the exchange that will issue the 311 // coins. 312 exchange_url: WebURL; 313 }; 314 315 .. ts:def:: DDContractOutputTaxReceipt 316 317 interface DDContractOutputTaxReceipt { 318 type: "tax-receipt"; 319 320 // Base URL of the donation authority that will 321 // issue the tax receipt. 322 donau_url: WebURL; 323 }; 324 325 .. ts:def:: DDContractOutputToken 326 327 interface DDContractOutputToken { 328 type: "token"; 329 330 // Slug of the token family in the 331 // 'token_families' map on the top-level. 332 token_family_slug: Slug; 333 334 // Start of the validity period of the token. This is used to find the 335 // matching public key within the token family. 336 valid_after: Timestamp; 337 338 // Number of tokens to be issued. 339 // Defaults to one if the field is not provided. 340 number?: Integer; 341 } 342 343 .. ts:def:: DDContractTokenDetails 344 345 type DDContractTokenDetails = 346 | DDContractSubscriptionTokenDetails 347 | DDContractDiscountTokenDetails 348 349 .. ts:def:: DDContractSubscriptionTokenDetails 350 351 interface DDContractSubscriptionTokenDetails { 352 class: "subscription"; 353 354 // Array of domain names where this subscription 355 // can be safely used (e.g. the issuer warrants that 356 // these sites will re-issue tokens of this type 357 // if the respective contract says so). May contain 358 // "*" for any domain or subdomain. 359 trusted_domains: string[]; 360 }; 361 362 .. ts:def:: DDContractDiscountTokenDetails 363 364 interface DDContractDiscountTokenDetails { 365 class: "discount"; 366 367 // Array of domain names where this discount token 368 // is intended to be used. May contain "*" for any 369 // domain or subdomain. Users should be warned about 370 // sites proposing to consume discount tokens of this 371 // type that are not in this list that the merchant 372 // is accepting a coupon from a competitor and thus 373 // may be attaching different semantics (like get 20% 374 // discount for my competitors 30% discount token). 375 expected_domains: string[]; 376 }; 377 378 .. ts:def:: DDContractTokenFamily 379 380 interface DDContractTokenFamily { 381 // Human-readable name of the token family. 382 name: string; 383 384 // Human-readable description of the semantics of 385 // this token family (for display). 386 description: string; 387 388 // Map from IETF BCP 47 language tags to localized descriptions. 389 description_i18n?: { [lang_tag: string]: string }; 390 391 // Public keys used to validate tokens issued by this token family. 392 keys: TokenIssuePublicKey[]; 393 394 // Class-specific information of the token 395 details: ContractTokenDetails; 396 397 // Must a wallet understand this token type to 398 // process contracts that consume or yield it? 399 critical: boolean; 400 401 // Number of tokens issued according to ASS authority 402 // FIXME: this is still rather speculative in the design... 403 ass?: Integer; 404 405 // Signature affirming sum of token issuance deposit (?) fees 406 // collected by an exchange according to the ASS authority. 407 // FIXME: this is still rather speculative in the design... 408 ass_cost?: Amount; 409 410 // Signature affirming the ass by the ASS authority. 411 // FIXME: this is still rather speculative in the design... 412 ass_sig?: EddsaSignature; 413 }; 414 415 .. ts:def:: DDTokenIssuePublicKey 416 417 type DDTokenIssuePublicKey = 418 | DDTokenIssueRsaPublicKey 419 | DDTokenIssueCsPublicKey; 420 421 .. ts:def:: DDTokenIssueRsaPublicKey 422 423 interface DDTokenIssueRsaPublicKey { 424 cipher: "RSA"; 425 426 // RSA public key. 427 rsa_pub: RsaPublicKey; 428 429 // Start time of this key's validity period. 430 valid_after: Timestamp; 431 432 // End time of this key's validity period. 433 valid_before: Timestamp; 434 } 435 436 .. ts:def:: DDTokenIssueCsPublicKey 437 438 interface DDTokenIssueCsPublicKey { 439 cipher: "CS"; 440 441 // CS public key. 442 cs_pub: Cs25519Point; 443 444 // Start time of this key's validity period. 445 valid_after: Timestamp; 446 447 // End time of this key's validity period. 448 valid_before: Timestamp; 449 } 450 451 452 Alternative Contracts 453 --------------------- 454 455 The contract terms object may contain any number of alternative contracts that 456 the user must choose between. The alternatives can differ by inputs, outputs 457 or other details. The wallet must filter the contracts by those that the user 458 can actually pay for, and move those that the user could currently not pay for 459 to the end of the rendered list. Similarly, the wallet must move up the 460 cheaper contracts, so if a contract has a definitively lower price and 461 consumes an available discount token, that contract should be moved up in the 462 list. 463 464 Which specific alternative contract was chosen by the user is indicated in the 465 ``choice_index`` field of the :ref:`TALER_DepositRequestPS <taler_depositrequestps>`. 466 467 468 Output Commitments 469 ------------------ 470 471 When a contract has outputs, the wallet must send an array of blinded tokens, 472 coins or tax receipts together with the payment request. The order in the 473 array must match the order in the outputs field of the contract. For currency 474 outputs, one array element must include all of the required planchets for a 475 batch withdrawal, but of course not the reserve signature. 476 477 .. note:: 478 479 We can probably spec this rather nicely if we first change the 480 batch-withdraw API to only use a single reserve signature. 481 482 This array of blinded values is hashed to create the output commitment hash 483 (``h_outputs``) in the :ref:`TALER_DepositRequestPS <taler_depositrequestps>`. 484 485 486 487 Subscriptions 488 ------------- 489 490 The user buys a subscription (and possibly at the same time an article) using 491 currency and the contract yields an additional subscription token as an 492 output. Active subscriptions are listed below the currencies in the wallet 493 under a new heading. Subscriptions are never auto-renewing, if the user wants 494 to extend the subscription they can trivially pay for it with one click. 495 496 When a contract consumes and yields exactly one subscription 497 token of the same type in a trusted domain, the wallet may automatically 498 approve the transaction without asking the user for confirmation (as it is free). 499 500 The token expiration for a subscription can be past the "end date" to enable a 501 previous subscription to be used to get a discount on renewing the 502 subscription. The wallet should show applicable contracts with a lower price 503 that only additionally consume subscription tokens after their end date before 504 higher-priced alternative offers. 505 506 Subscription tokens are "critical" in that a wallet implementation must 507 understand them before allowing a user to interact with this class of token. 508 Subscription token secrets should be derived from a master secret associated 509 with the subscription, so that the private keys are recoverable from backup. 510 To obtain the blind signatures, a merchant must offer an endpoint where 511 one can submit the public key of the N-1 subscription token and obtain the 512 blinded signature over the N-th subscription token. The wallet can then 513 effectively recover the subscription from backup using a binary search. 514 515 The merchant SPA should allow the administrator to create (maybe update) and 516 delete subscriptions. Each subscription is identified by a subscription 517 label and includes a validity period. 518 519 The merchant backend must then automatically manage (create, use, delete) the 520 respective signing keys. When creating an order, the frontend can just refer 521 to the subscription label (and possibly a start date) in the inputs or 522 outputs. The backend should then automatically substitute this with the 523 respective cryptographic fields for the respective time period and 524 subscription label. 525 526 527 528 529 Discounts 530 --------- 531 532 To offer a discount based on one or more previous purchases, a merchant must 533 yield some discount-specific token as an output with the previous purchase, 534 and then offer an alternative contract with a lower price that consumes currency 535 and the discount token. The wallet should show contracts with a lower price that 536 only additionally consume discount tokens 537 538 The merchant SPA should allow the administrator to create (maybe update) and 539 delete discount tokens. Each discount token is identified by a discount 540 label and includes an expiration time or validity duration. 541 542 The merchant backend must then automatically manage (create, use, delete) the 543 respective signing keys. When creating an order, the frontend can just refer 544 to the discount token label in the inputs or outputs. The backend should then 545 automatically substitute this with the respective cryptographic fields for the 546 respective discount token. 547 548 549 Donation Authority 550 ------------------ 551 552 A donation authority (DONAU) implements a service that is expected to be run 553 by a government authority that determines eligibility for tax deduction. A 554 DONAU blindly signs tax receipts using a protocol very close to that of the 555 Taler exchange's withdraw protocol, except that the reserves are not filled 556 via wire transfers but instead represent accounts of the organizations 557 eligible to issue tax deduction receipts. These accounts are basically 558 expected to have only negative balances, but the DONAU can set a negative 559 balance threshold per organization to limit the creation of tax deduction 560 receipts to a plausible amount. DONAU administrators are expected to be 561 able to add, update or remove these accounts using a SPA. Tax receipts are 562 blindly signed by keys that always have a usage period of one calendar year. 563 564 A stand-alone app for tax authorities can scan QR codes representing DONAU 565 signatures to validate that a given tax payer has donated a certain amount. 566 As RSA signatures are typically very large and a single donation may require 567 multiple blind signatures, CS blind signatures must also be supported. To 568 avoid encoding the public keys, QR codes with tax receipts should reference 569 the DONAU, the year and the amount, but not the specific public key. A 570 single donation may nevertheless be rendered using multiple QR codes. 571 572 Revocations, refresh, deposits, age-restrictions and other exchange features 573 are not applicable for a DONAU. 574 575 The merchant SPA should allow the administrator to manage DONAU accounts in 576 the merchant backend. Each DONAU account includes a base URL and a private 577 signing key for signing the requests to the DONAU on behalf of the eligible 578 organization. 579 580 When creating an order, the frontend must specify a configured DONAU base URL 581 in the outputs. The backend should then automatically interact with the DONAU 582 when the wallet supplies the payment request with the blinded tax receipts. 583 The DONAU interaction must only happen after the exchange confirmed that the 584 contract was successfully paid. A partial error must be returned if the 585 exchange interaction was successful but the DONAU interaction failed. In this 586 case, the fulfillment action should still be triggered, but the wallet should 587 display a warning that the donation receipt could not be obtained. The wallet 588 should then re-try the payment (in the background with an exponential 589 back-off) to possibly obtain the tax receipt at a later time. 590 591 592 Tax Receipts 593 ------------ 594 595 Tax receipts differ from coins and tokens in that what is blindly signed over 596 should be the taxpayer identification number of the tax payer. The format of 597 the taxpayer identification number should simply be a string, with the rest 598 being defined by the national authority. The DONAU should indicate in its 599 ``/config`` response what format this string should have, using possibly both 600 an Anastasis-style regex and an Anastasis-style function name (to check things 601 like checksums that cannot be validated using a regex). Wallets must then 602 validate the regex (if given) and if possible should implement the 603 Anastasis-style logic. 604 605 Wallets should collect tax receipts by year and offer an 606 export functionality. The export should generate either 607 608 (a) a JSON file, 609 (b) a PDF (with QR codes), or 610 (c) a series of screens with QR codes. 611 612 Wallets may only implement some of the above options due to resource 613 constraints. 614 615 The documents should encode the taxpayer ID, the amount and the DONAU 616 signature (including the year, excluding the exact public key as there should 617 only be one possible). 618 619 620 Rationing (future work) 621 ----------------------- 622 623 If per-capita rationing must be imposed on certain transactions, a rationing 624 authority (RA) must exist that identifies each eligible human and issues that 625 human a number of ration coins for the respective rationing period. An RA 626 largely functions like a regular exchange, except that eligible humans will 627 need to authenticate directly to withdraw rations (instead of transferring 628 fiat to an exchange). Merchants selling rationed goods will be (legally) 629 required to collect deposit confirmations in proportion to the amount of 630 rationed goods sold. A difference to regular exchanges is that RAs do not 631 charge any fees. RAs may or may not allow refreshing rations that are about 632 to expire for ration coins in the next period. 633 634 Once an RA is added to a wallet, it should automatically try to withdraw the 635 maximum amount of ration coins it is eligible for. Available rations should be 636 shown below the subscriptions by RA (if any). 637 638 ..note:: 639 640 RAs are considered an idea for future work and not part of our current timeline. 641 642 643 Limited Donations per Capita (future work) 644 ------------------------------------------ 645 646 If per-capita limitations must be imposed on anonymous donations (for example 647 for donations to political parties), an RA can be used to issue donation 648 rations that limit the amount of donations that can be made for the respective 649 period. 650 651 ..note:: 652 653 RAs are considered an idea for future work and not part of our current timeline. 654 655 656 657 Definition of Done 658 ================== 659 660 - [ ] Merchant backend support for multiple currencies 661 - [x] Merchant backend support for consuming and issuing tokens 662 - [ ] Merchant SPA support for configuring new tokens of different types 663 - [x] Wallet-core support for contract format v1 664 - [ ] Wallet-core filters for feasible contracts and automatic subscription execution 665 - [ ] Wallet-GUIs (WebEx, Android, iOS) render all new contract types 666 - [ ] Wallet-GUIs (WebEx, Android, iOS) allow users to select between multiple contracts 667 - [x] Implemented wire formats documented in the normative API specifications 668 - [ ] Token anonymity set size (ASS) authority implemented and documented 669 - [ ] Merchants report anonymity set size increases to ASS authority 670 - [ ] Wallets process anonymity set size reports from ASS authority 671 - [x] Bachelor thesis written on applications and design 672 - [x] Academic paper written on DONAU (requirements, design, implementation) 673 - [x] DONAU implemented and documented 674 - [ ] DONAU receipt-validation application completed 675 - [x] Integration tests exist in wallet-core 676 - [ ] Deliverables accepted by EC 677 678 While rationing is part of the design, we expect the actual implementation to 679 be done much later and thus should not consider it part of the "DONE" part. 680 Rationing is complex, especially as a refunded contract should probably also 681 refund the ration. 682 683 684 Alternatives 685 ============ 686 687 The first draft of this DD included the capability of paying with multiple 688 currencies for the same contract (for example, USD:1 and EUR:5) plus tokens 689 and rations. However, this is very complex, both for wallets (how to display), 690 for other merchant APIs (does the refund API have to become multi-currency as 691 well?) and there does not seem to be a good business case for it. So for now, 692 the price is always only in one currency. 693 694 695 Drawbacks 696 ========= 697 698 Significant change, but actually good ratio compared to use-cases covered. 699 700 701 Discussion / Q&A 702 ================ 703 704 (This should be filled in with results from discussions on mailing lists / personal communication.)