summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README33
-rw-r--r--_exts/ebicsdomain.py2
-rw-r--r--_exts/httpdomain/httpdomain.py1
-rw-r--r--_static/sample-pos-config.json2
-rw-r--r--backoffice-order-create.payment-section.svg2
-rw-r--r--backoffice-product-create.stock.svg2
-rw-r--r--backoffice-tip-create.confirmation.svg2
-rw-r--r--cf/captcha-payment.txt2
-rw-r--r--checklist-demo-upgrade.rst56
-rw-r--r--checklist-release.rst176
-rw-r--r--conf.py615
-rw-r--r--core/api-auditor.rst6
-rw-r--r--core/api-bank-access.rst141
-rw-r--r--core/api-bank-integration.rst2
-rw-r--r--core/api-common.rst413
-rw-r--r--core/api-error.rst4
-rw-r--r--core/api-exchange.rst2894
-rw-r--r--core/api-mailbox.rst212
-rw-r--r--core/api-merchant.rst525
-rw-r--r--core/api-sync.rst19
-rw-r--r--core/api-taldir.rst254
-rw-r--r--core/api-wire.rst2
-rw-r--r--core/index.rst2
-rw-r--r--core/taler-uri.rst20
-rw-r--r--core/tos.rst42
-rw-r--r--core/wireformats.rst2
-rw-r--r--demo-deployment.rst10
-rw-r--r--design-documents/001-new-browser-integration.rst4
-rw-r--r--design-documents/002-wallet-exchange-management.rst6
-rw-r--r--design-documents/003-tos-rendering.rst4
-rw-r--r--design-documents/004-wallet-withdrawal-flow.rst4
-rw-r--r--design-documents/005-wallet-backup-sync.rst8
-rw-r--r--design-documents/006-extensions.rst174
-rw-r--r--design-documents/007-payment.rst4
-rw-r--r--design-documents/008-fees.rst4
-rw-r--r--design-documents/009-backup.rst4
-rw-r--r--design-documents/010-exchange-helpers.rst10
-rw-r--r--design-documents/011-auditor-db-sync.rst14
-rw-r--r--design-documents/012-fee-schedule-metrics.rst8
-rw-r--r--design-documents/013-peer-to-peer-payments.rst39
-rw-r--r--design-documents/014-merchant-backoffice-ui.rst4
-rw-r--r--design-documents/015-merchant-backoffice-routing.rst29
-rw-r--r--design-documents/016-backoffice-order-management.rst8
-rw-r--r--design-documents/017-backoffice-inventory-management.rst20
-rw-r--r--design-documents/018-contract-json.rst8
-rw-r--r--design-documents/019-wallet-backup-merge.rst4
-rw-r--r--design-documents/020-backoffice-tips-management.rst6
-rw-r--r--design-documents/021-exchange-key-continuity.rst4
-rw-r--r--design-documents/023-taler-kyc.rst497
-rw-r--r--design-documents/024-age-restriction.rst769
-rw-r--r--design-documents/025-withdraw-from-wallet.rst66
-rw-r--r--design-documents/026-refund-fees.rst61
-rw-r--r--design-documents/027-sandboxing-taler.rst165
-rw-r--r--design-documents/028-deposit-policies.rst188
-rw-r--r--design-documents/029-mobile-ui.rst41
-rw-r--r--design-documents/030-offline-payments.rst99
-rw-r--r--design-documents/031-invoicing.rst202
-rw-r--r--design-documents/032-brandt-vickrey-auctions.rst312
-rw-r--r--design-documents/033-database.rst152
-rw-r--r--design-documents/_svgs/escrow-flow.svg1
-rw-r--r--design-documents/index.rst9
-rw-r--r--extract-tsdefs/.gitignore3
-rw-r--r--extract-tsdefs/README.md8
-rw-r--r--extract-tsdefs/extract.ts341
-rw-r--r--extract-tsdefs/myout.md2517
-rw-r--r--extract-tsdefs/package.json19
-rw-r--r--extract-tsdefs/pnpm-lock.yaml35
-rw-r--r--extract-tsdefs/tsconfig.json103
-rw-r--r--frags/common-conf-syntax.rst32
-rw-r--r--frags/install-before-check.rst10
-rw-r--r--frags/installing-trisquel.rst4
-rw-r--r--frags/installing-ubuntu.rst7
-rw-r--r--frags/list-of-dependencies.rst8
-rw-r--r--frags/using-taler-config.rst6
-rw-r--r--index.rst3
-rw-r--r--libeufin/api-nexus.rst26
-rw-r--r--libeufin/api-sandbox-future.rst135
-rw-r--r--libeufin/api-sandbox.rst921
-rw-r--r--libeufin/demo-deployment-gv.rst49
-rw-r--r--libeufin/ebics.rst5
-rw-r--r--libeufin/index.rst1
-rw-r--r--libeufin/int-deployment-gv.rst71
-rw-r--r--libeufin/iso20022.rst11
-rw-r--r--libeufin/nexus-tutorial.rst339
-rw-r--r--manpages/TDM.el172
-rw-r--r--manpages/TEMPLATE-148
-rw-r--r--manpages/libeufin-cli.1.rst1006
-rw-r--r--manpages/libeufin-nexus.1.rst150
-rw-r--r--manpages/libeufin-sandbox.1.rst218
-rw-r--r--manpages/sync-config.1.rst101
-rw-r--r--manpages/sync-dbinit.1.rst65
-rw-r--r--manpages/sync-httpd.1.rst78
-rw-r--r--manpages/sync.conf.5.rst92
-rw-r--r--manpages/taler-auditor-offline.1.rst2
-rw-r--r--manpages/taler-auditor.1.rst5
-rw-r--r--manpages/taler-config.1.rst2
-rw-r--r--manpages/taler-exchange-aggregator.1.rst6
-rw-r--r--manpages/taler-exchange-drain.1.rst56
-rw-r--r--manpages/taler-exchange-expire.1.rst66
-rw-r--r--manpages/taler-exchange-httpd.1.rst7
-rw-r--r--manpages/taler-exchange-kyc-tester.1.rst85
-rw-r--r--manpages/taler-exchange-offline.1.rst71
-rw-r--r--manpages/taler-exchange-router.1.rst67
-rw-r--r--manpages/taler-exchange-secmod-cs.1.rst76
-rw-r--r--manpages/taler-exchange-wirewatch.1.rst5
-rw-r--r--manpages/taler-fakebank-run.1.rst6
-rw-r--r--manpages/taler-helper-auditor-purses.1.rst75
-rw-r--r--manpages/taler-merchant-httpd.1.rst5
-rw-r--r--manpages/taler.conf.5.rst149
-rw-r--r--merchant-spec/public-orders-get.ts2
-rw-r--r--python-guidelines.rst2
-rw-r--r--taler-auditor-manual.rst34
-rw-r--r--taler-backoffice-manual.rst135
-rw-r--r--taler-bank-manual.rst71
-rw-r--r--taler-developer-manual.rst354
-rw-r--r--taler-exchange-manual.rst32
-rw-r--r--taler-exchange-setup-guide.rst44
-rw-r--r--taler-mcig.rst12
-rw-r--r--taler-merchant-api-tutorial.rst14
-rw-r--r--taler-merchant-manual.rst58
-rw-r--r--taler-nfc-guide.rst10
-rw-r--r--taler-wallet-cli-manual.rst2
-rw-r--r--taler-wallet.rst947
-rw-r--r--wallet-confirm-withdraw.svg16
-rw-r--r--wallet-mobile-overview.svg103
-rw-r--r--wallet-start-manual-withdraw.svg16
-rw-r--r--wallet/wallet-core.md3135
128 files changed, 17561 insertions, 3064 deletions
diff --git a/Makefile b/Makefile
index 5b6a8ddd..6bb8992e 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. You can find Sphinx at http://sphinx-doc.org/)
endif
# Internal variables.
diff --git a/README b/README
index b7f64144..206d3055 100644
--- a/README
+++ b/README
@@ -1,10 +1,33 @@
This repository contains the documentation for all main GNU Taler components.
-Before building the documentation, make sure that you have the required
-dependencies installed using pip3:
+To build things on a Debian (-based) system, install these packages:
+ - make
+ - python3-sphinx
+ - python3-recommonmark
+ - graphviz
+ - texlive-latex-extra
+ - dvipng
-$ pip3 install --user --upgrade recommonmark sphinx
+Then, do "make html" for HTML, "make texinfo" for Texinfo, etc.
+(Do "make" w/o a target to see a list of possible targets.)
-Note by Buck: when setting up on VM, I also needed:
+The output is in subdir ‘_build’.
-# apt install texlive-latex-extra graphviz
+
+Branch ‘prebuilt’ is special. Its contents (or a subset) are used as
+submodules in other Git repos (e.g., Exchange, Merchant, Sync).
+
+One approach that works well (so far) for ttn is to do
+(presuming that the current directory is named ‘docs’):
+
+ $ cd ..
+ $ git clone -b prebuilt --reference docs \
+ git+ssh://git@git.taler.net/docs.git \
+ docs-prebuilt
+
+This creates Git repo docs-prebuilt as a peer to the current repo.
+
+This way, you can build in this directory (i.e., "make man" or whatever)
+and then copy to ../docs-prebuilt/man/* what files need to be updated.
+In that repo, you can commit changes and push to git.taler.net as normal,
+all without having to do "git checkout" in either repo.
diff --git a/_exts/ebicsdomain.py b/_exts/ebicsdomain.py
index 4dcc30c1..3424cb57 100644
--- a/_exts/ebicsdomain.py
+++ b/_exts/ebicsdomain.py
@@ -169,6 +169,8 @@ class EbicsOrders(SphinxDirective):
items.sort(key=lambda x:
unicodedata.normalize('NFD', x[0][0].lower()))
+ node["sorted"] = 'sorted' in self.options
+
dlist = nodes.definition_list()
dlist['classes'].append('glossary')
dlist.extend(item[1] for item in items)
diff --git a/_exts/httpdomain/httpdomain.py b/_exts/httpdomain/httpdomain.py
index 59665a05..b31142b1 100644
--- a/_exts/httpdomain/httpdomain.py
+++ b/_exts/httpdomain/httpdomain.py
@@ -219,6 +219,7 @@ HTTP_STATUS_CODES = {
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
+ 425: 'Too Early', # RFC 8470
426: 'Upgrade Required',
429: 'Too Many Requests',
449: 'Retry With', # proprietary MS extension
diff --git a/_static/sample-pos-config.json b/_static/sample-pos-config.json
index f0789104..db4dca6e 100644
--- a/_static/sample-pos-config.json
+++ b/_static/sample-pos-config.json
@@ -1,7 +1,7 @@
{
"config": {
"base_url": "https://backend.demo.taler.net/instances/pos",
- "api_key": "sandbox"
+ "api_key": "secret-token:sandbox"
},
"categories": [
{
diff --git a/backoffice-order-create.payment-section.svg b/backoffice-order-create.payment-section.svg
index 2e319727..b2816c7d 100644
--- a/backoffice-order-create.payment-section.svg
+++ b/backoffice-order-create.payment-section.svg
@@ -13,4 +13,4 @@
}
</style>
</defs>
- <rect x="0" y="0" width="812" height="755.4705463593664" fill="#ffffff"></rect><g transform="translate(10 57.28872817754791) rotate(0 396 344.09090909090924)"><path d="M0 0 C163.13469960540533 0, 326.26939921081066 0, 792 0 M0 0 C197.6944362178445 0, 395.388872435689 0, 792 0 M792 0 C792 158.56476755186247, 792 317.12953510372495, 792 688.1818181818186 M792 0 C792 266.1385155277837, 792 532.2770310555674, 792 688.1818181818186 M792 688.1818181818186 C541.9932290330529 688.1818181818186, 291.9864580661058 688.1818181818186, 0 688.1818181818186 M792 688.1818181818186 C500.6049041256308 688.1818181818186, 209.20980825126162 688.1818181818186, 0 688.1818181818186 M0 688.1818181818186 C0 513.5637151258577, 0 338.94561206989687, 0 0 M0 688.1818181818186 C0 501.1274236320436, 0 314.07302908226865, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(49 89.78872817754723) rotate(0 355.94636678200686 308.9999999999999)"><path d="M0 0 C196.37916639762955 0, 392.7583327952591 0, 711.8927335640136 0 M0 0 C222.65387708782745 0, 445.3077541756549 0, 711.8927335640136 0 M711.8927335640136 0 C711.8927335640136 136.01048013232645, 711.8927335640136 272.0209602646529, 711.8927335640136 617.9999999999998 M711.8927335640136 0 C711.8927335640136 229.1515129242092, 711.8927335640136 458.3030258484184, 711.8927335640136 617.9999999999998 M711.8927335640136 617.9999999999998 C540.7157390920328 617.9999999999998, 369.5387446200519 617.9999999999998, 0 617.9999999999998 M711.8927335640136 617.9999999999998 C560.9805535182735 617.9999999999998, 410.06837347253344 617.9999999999998, 0 617.9999999999998 M0 617.9999999999998 C0 487.54947386719266, 0 357.0989477343856, 0 0 M0 617.9999999999998 C0 469.64537168405934, 0 321.29074336811885, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(49 134.42886658585485) rotate(0 355.35899653979254 0)"><path d="M0 0 C118.45299884659751 0, 592.2649942329875 0, 710.7179930795851 0 M0 0 C118.45299884659751 0, 592.2649942329875 0, 710.7179930795851 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(77.19377162629803 96.83717108412088) rotate(0 45.22750865051904 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581313px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">payment</text></g><g transform="translate(686.884083044984 90.96346866197814) rotate(0 35.82958477508646 21.732698961937672)"><path d="M0 0 C25.693898552344557 0, 51.38779710468911 0, 71.65916955017302 0 M0 0 C18.423871399928533 0, 36.847742799857066 0, 71.65916955017302 0 M71.65916955017302 0 C71.65916955017302 14.811010985339712, 71.65916955017302 29.622021970679423, 71.65916955017302 43.46539792387543 M71.65916955017302 0 C71.65916955017302 9.170037636031626, 71.65916955017302 18.340075272063252, 71.65916955017302 43.46539792387543 M71.65916955017302 43.46539792387543 C48.850217413141436 43.46539792387543, 26.041265276109854 43.46539792387543, 0 43.46539792387543 M71.65916955017302 43.46539792387543 C50.35093945471351 43.46539792387543, 29.042709359254005 43.46539792387543, 0 43.46539792387543 M0 43.46539792387543 C0 27.43454704114484, 0 11.403696158414249, 0 0 M0 43.46539792387543 C0 26.72246210199255, 0 9.979526280109674, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(719.7768166089982 98.01191156855225) rotate(0 0 12.922145328719694)"><path d="M0 0 C0 4.307381776239908, 0 21.53690888119954, 0 25.844290657439448 M0 0 C0 4.307381776239908, 0 21.53690888119954, 0 25.844290657439448" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(719.7768166089982 98.01191156855225) rotate(0 0 12.922145328719694)"><path d="M-4.4196339974038406 13.701446047318425 C-3.1323002724435964 17.238366387949103, -1.8449665474833528 20.77528672857978, 0 25.844290657439448 M-4.4196339974038406 13.701446047318425 C-2.8705976157310924 17.957388527877995, -1.3215612340583442 22.213331008437564, 0 25.844290657439448" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(719.7768166089982 98.01191156855225) rotate(0 0 12.922145328719694)"><path d="M4.4196339974038406 13.701446047318425 C3.1323002724435964 17.238366387949103, 1.8449665474833528 20.77528672857978, 0 25.844290657439448 M4.4196339974038406 13.701446047318425 C2.8705976157310924 17.957388527877995, 1.3215612340583442 22.213331008437564, 0 25.844290657439448" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(122.41700882982559 491.89835384599746) rotate(0 41.70328719723187 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581313px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">max fee</text></g><g transform="translate(381.043964686653 486.02465142385245) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C44.61811129907696 0, 89.23622259815392 0, 199.70588235294125 0 M0 0 C79.29353130055485 0, 158.5870626011097 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 8.759946174821641, 199.70588235294125 17.519892349643282, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 10.044274510345245, 199.70588235294125 20.08854902069049, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C120.15937504977649 37.59169550173011, 40.61286774661173 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C131.51639080907717 37.59169550173011, 63.32689926521306 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 22.898467848158624, 0 8.205240194587144, 0 0 M0 37.59169550173011 C0 24.276735533449063, 0 10.961775565168022, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(122.41700882982559 548.2858970985903) rotate(0 66.37283737024222 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">max wire fee</text></g><g transform="translate(381.043964686653 537.7132327387317) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C73.07786602499513 0, 146.15573204999026 0, 199.70588235294125 0 M0 0 C66.5611449872856 0, 133.1222899745712 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 11.502282709924822, 199.70588235294125 23.004565419849644, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 11.270341817534508, 199.70588235294125 22.540683635069016, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C129.25950294366 37.59169550173011, 58.813123534378775 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C136.2727813783705 37.59169550173011, 72.83968040379972 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 24.7357254136908, 0 11.879755325651498, 0 0 M0 37.59169550173011 C0 25.50134813334909, 0 13.411000764968072, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(122.41700882982559 608.1976618044737) rotate(0 108.66349480968859 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">wire fee amortization</text></g><g transform="translate(381.043964686653 601.1492188978978) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C63.18052968749888 0, 126.36105937499777 0, 199.70588235294125 0 M0 0 C58.832443530780886 0, 117.66488706156177 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 12.2283371196383, 199.70588235294125 24.4566742392766, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 13.842964280734428, 199.70588235294125 27.685928561468856, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C137.16080129305453 37.59169550173011, 74.61572023316782 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C124.86287547313776 37.59169550173011, 50.01986859333428 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 25.7131450870561, 0 13.834594672382085, 0 0 M0 37.59169550173011 C0 29.765293690768495, 0 21.93889187980688, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(126.6112523026161 170.85274201837865) rotate(0 81.05709342560556 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581313px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">refund deadline</text></g><g transform="translate(389.96703685669763 170.11685268386054) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C38.588792372791914 0, 77.17758474558383 0, 139.51157167042618 0 M0 0 C51.270889713540704 0, 102.54177942708141 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 11.195579849357195, 139.51157167042618 22.39115969871439, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 7.478536088246398, 139.51157167042618 14.957072176492796, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C106.073634546001 36.06923440786065, 72.63569742157583 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C90.59517821844433 36.06923440786065, 41.678784766462485 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 28.587188228147372, 0 21.105142048434097, 0 0 M0 36.06923440786065 C0 24.459576137678855, 0 12.849917867497062, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(423.8180831341699 176.4274732975059) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(453.616574467806 175.93897343957906) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(540.5695491790709 169.09997542858218) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C12.350741138777865 0, 24.70148227755573 0, 34.68348991292074 0 M0 0 C13.090885427004103 0, 26.181770854008207 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 8.759752021302454, 34.68348991292074 17.519504042604908, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 12.827119610736222, 34.68348991292074 25.654239221472444, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C26.26168783725121 34.68348991292074, 17.83988576158168 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C25.047276653082115 34.68348991292074, 15.411063393243492 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 21.65963125803831, 0 8.635772603155882, 0 0 M0 34.68348991292074 C0 22.816433409973676, 0 10.949376907026608, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(541.5465488949278 170.07697514443726) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C8.416124992044198 0, 16.832249984088396 0, 34.19499005499228 0 M0 0 C12.612778286946597 0, 25.225556573893194 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 2.784839725361176, 34.19499005499228 5.569679450722352, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 2.6248021666892867, 34.19499005499228 5.249604333378573, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C22.07788480659337 8.792997442712293, 9.96077955819446 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C25.609229118108246 8.792997442712293, 17.023468181224214 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.374793220870361, 0 1.956588999028427, 0 0 M0 8.792997442712293 C0 6.7982943981253765, 0 4.80359135353846, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(548.874046763857 161.77247755965436) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C2.06572777238916 0, 4.13145554477832 0, 5.861998295141533 0 M0 0 C1.6284420677636307 0, 3.2568841355272613 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 4.0886379104374235, 5.861998295141533 8.177275820874847, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.405838529784974, 5.861998295141533 8.811677059569949, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C3.787286061220638 13.677996021996911, 1.7125738272997424 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.3944272776554225 13.677996021996911, 2.9268562601693118 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.10398929446179, 0 4.52998256692667, 0 0 M0 13.677996021996911 C0 9.353978362633276, 0 5.029960703269641, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(563.5290425017101 161.77247755965436) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.9136207457158667 0, 3.8272414914317334 0, 5.861998295141533 0 M0 0 C1.7752901701469925 0, 3.550580340293985 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 3.1488935786452767, 5.861998295141533 6.2977871572905535, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.449927547836239, 5.861998295141533 8.899855095672478, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.27455321609603 13.677996021996911, 2.6871081370505263 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.940863191629518 13.677996021996911, 2.019728088117503 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 10.13870173751964, 0 6.599407453042369, 0 0 M0 13.677996021996911 C0 9.428222501847815, 0 5.178448981698718, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(557.2990807517933 178.205813523291) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.13838525821577435 7.334418685436041, 0.2767705164315487 14.668837370872081, 0.4884998579284611 25.89049247020844 M0 0 C0.18365905060197285 9.733929681904561, 0.3673181012039457 19.467859363809122, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(548.2098876999967 177.0974105194373) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.18081889830845407 10.125858305273429, 0.36163779661690815 20.251716610546858, 0.4884998579284611 27.355992043993822 M0 0 C0.1435033739142469 8.036188939197826, 0.2870067478284938 16.072377878395653, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(567.129978871211 178.22712166566998) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.49131225684945856 8.352308366440798, 0.9826245136989171 16.704616732881597, 1.4654995737853833 24.913492754351527 M0 0 C0.3645462207600854 6.197285752921455, 0.7290924415201708 12.39457150584291, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.7498470395947 186.38910449486139) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-9.850046834715748 0, -19.700093669431496 0, -33.21799033913535 0 M0 0 C-11.978115758822755 0, -23.95623151764551 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.2745570852853 195.08467745817006) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-10.91708283817915 0.3032523010605322, -21.8341656763583 0.6065046021210644, -35.17198977084917 0.9769997158569222 M0 0 C-7.292502852220365 0.20256952367278808, -14.58500570444073 0.40513904734557615, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(126.6112523026161 236.4338141678163) rotate(0 66.37283737024222 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">pay deadline</text></g><g transform="translate(389.96703685669763 235.69792483329775) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C47.50166837735665 0, 95.0033367547133 0, 139.51157167042618 0 M0 0 C49.76376503488774 0, 99.52753006977548 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 8.446743562554808, 139.51157167042618 16.893487125109615, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 11.47235701590513, 139.51157167042618 22.94471403181026, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C96.27412265275322 36.06923440786065, 53.03667363508026 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C94.95422630662594 36.06923440786065, 50.39688094282572 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 26.300498294737366, 0 16.531762181614084, 0 0 M0 36.06923440786065 C0 28.404608233033215, 0 20.739982058205783, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(423.8180831341699 242.00854544694494) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(453.616574467806 241.52004558901763) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(540.5695491790709 234.68104757801984) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C10.984645583369092 0, 21.969291166738184 0, 34.68348991292074 0 M0 0 C10.353386324163305 0, 20.70677264832661 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 12.290206751947466, 34.68348991292074 24.58041350389493, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 8.708414664553807, 34.68348991292074 17.416829329107614, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C21.200573260099773 34.68348991292074, 7.717656607278801 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C26.81549457038345 34.68348991292074, 18.94749922784616 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 25.885278309906877, 0 17.08706670689301, 0 0 M0 34.68348991292074 C0 21.8895309879799, 0 9.095572063039057, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(541.5465488949278 235.65804729387492) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C10.22159477609356 0, 20.44318955218712 0, 34.19499005499228 0 M0 0 C11.014596324462437 0, 22.029192648924873 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 3.1120683508813407, 34.19499005499228 6.224136701762681, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 2.2013565262291643, 34.19499005499228 4.402713052458329, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C22.75997323615448 8.792997442712293, 11.324956417316677 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C23.38494590658319 8.792997442712293, 12.574901758174104 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.889444189066426, 0 2.9858909354205574, 0 0 M0 8.792997442712293 C0 5.741208897211333, 0 2.689420351710372, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(548.874046763857 227.35354970909202) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.3495258194194044 0, 2.699051638838809 0, 5.861998295141533 0 M0 0 C1.907111806215531 0, 3.814223612431062 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 3.7040385177728417, 5.861998295141533 7.408077035545683, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.482648574861369, 5.861998295141533 8.965297149722739, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.345157887508417 13.677996021996911, 2.828317479875301 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.040666786506207 13.677996021996911, 2.2193352778708797 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 8.942165936497862, 0 4.20633585099881, 0 0 M0 13.677996021996911 C0 9.845296872419517, 0 6.012597722842125, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(563.5290425017101 227.35354970909202) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.5320176410516326 0, 3.0640352821032653 0, 5.861998295141533 0 M0 0 C2.169100553518035 0, 4.33820110703607 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 4.243283857466258, 5.861998295141533 8.486567714932516, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.871870315718041, 5.861998295141533 9.743740631436083, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.453460484325019 13.677996021996911, 3.0449226735085038 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.658298036524886 13.677996021996911, 1.4545977779082389 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.954515139904878, 0 6.231034257812844, 0 0 M0 13.677996021996911 C0 9.474895524801425, 0 5.27179502760594, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(557.2990807517933 243.78688567272866) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.1131082943342409 5.994739599714768, 0.2262165886684818 11.989479199429535, 0.4884998579284611 25.89049247020844 M0 0 C0.11061345453975935 5.862513090607246, 0.2212269090795187 11.725026181214492, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(548.2098876999967 242.6784826688745) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.11589865333866953 6.490324586965493, 0.23179730667733905 12.980649173930987, 0.4884998579284611 27.355992043993822 M0 0 C0.14872427158435972 8.328559208724144, 0.29744854316871944 16.657118417448288, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(567.129978871211 243.80819381510855) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.29443945475956246 5.005470730912564, 0.5888789095191249 10.010941461825128, 1.4654995737853833 24.913492754351527 M0 0 C0.5640236075493323 9.588401328338653, 1.1280472150986647 19.176802656677307, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.7498470395947 251.97017664429995) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-7.961641167542139 0, -15.923282335084277 0, -33.21799033913535 0 M0 0 C-11.647093252965808 0, -23.294186505931616 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.2745570852853 260.66574960760863) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-8.236625563486276 0.22879515454128563, -16.473251126972553 0.45759030908257126, -35.17198977084917 0.9769997158569222 M0 0 C-11.186975000584102 0.3107493055717809, -22.373950001168204 0.6214986111435618, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(126.6112523026161 305.64423043891156) rotate(0 106.90138408304506 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581316px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">auto refund deadline</text></g><g transform="translate(389.96703685669763 304.90834110439346) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C54.233956780562394 0, 108.46791356112479 0, 139.51157167042618 0 M0 0 C31.64838368021466 0, 63.29676736042932 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 9.149735435384923, 139.51157167042618 18.299470870769845, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 13.305128884862302, 139.51157167042618 26.610257769724605, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C91.61872293837436 36.06923440786065, 43.72587420632256 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C96.71713609249963 36.06923440786065, 53.922700514573094 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 22.783353778422555, 0 9.497473148984461, 0 0 M0 36.06923440786065 C0 25.237278543595075, 0 14.405322679329501, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(423.8180831341699 311.21896171804246) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(453.616574467806 310.7304618601147) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(540.5695491790709 303.89146384911646) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C8.333848713997714 0, 16.667697427995428 0, 34.68348991292074 0 M0 0 C13.03855986348183 0, 26.07711972696366 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 9.4416836653048, 34.68348991292074 18.8833673306096, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 10.65786197503141, 34.68348991292074 21.31572395006282, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C21.040335615093984 34.68348991292074, 7.397181317267226 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C27.450049553381234 34.68348991292074, 20.216609193841723 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 23.797810389582665, 0 12.912130866244592, 0 0 M0 34.68348991292074 C0 25.594766385240447, 0 16.506042857560153, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(541.5465488949278 304.86846356497153) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C12.256912387278875 0, 24.51382477455775 0, 34.19499005499228 0 M0 0 C9.337819074833758 0, 18.675638149667517 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 2.953364940224322, 34.19499005499228 5.906729880448644, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 1.8197494907887624, 34.19499005499228 3.6394989815775247, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C24.437822160950887 8.792997442712293, 14.680654266909496 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C26.745579617251906 8.792997442712293, 19.29616917951153 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.897504324214696, 0 3.002011205717099, 0 0 M0 8.792997442712293 C0 6.010860718858778, 0 3.2287239950052617, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(548.874046763857 296.5639659801868) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.5383239337541497 0, 3.0766478675082993 0, 5.861998295141533 0 M0 0 C1.703193236340253 0, 3.406386472680506 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 5.346398526020947, 5.861998295141533 10.692797052041893, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.496599266354498, 5.861998295141533 8.993198532708996, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C3.891777796747413 13.677996021996911, 1.9215572983532936 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.038711915432156 13.677996021996911, 2.2154255357227792 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 10.407330957556045, 0 7.13666589311518, 0 0 M0 13.677996021996911 C0 8.615481990624176, 0 3.5529679592514416, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(563.5290425017101 296.5639659801868) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.992445036968253 0, 3.984890073936506 0, 5.861998295141533 0 M0 0 C2.1991607886562967 0, 4.398321577312593 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 3.9068180477119214, 5.861998295141533 7.813636095423843, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.436560428108088, 5.861998295141533 8.873120856216175, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.2584782283409455 13.677996021996911, 2.6549581615403572 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.951389670446835 13.677996021996911, 2.040781045752137 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.992197423731628, 0 6.306398825466346, 0 0 M0 13.677996021996911 C0 9.18606117187456, 0 4.694126321752208, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(557.2990807517933 312.9973019438262) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.13958349564106426 7.397925268976406, 0.2791669912821285 14.795850537952813, 0.4884998579284611 25.89049247020844 M0 0 C0.12864824285337711 6.818356871228986, 0.25729648570675423 13.636713742457973, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(548.2098876999967 311.88889893997066) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.13230184343367635 7.408903232285875, 0.2646036868673527 14.81780646457175, 0.4884998579284611 27.355992043993822 M0 0 C0.10380505322458383 5.813082980576695, 0.20761010644916766 11.62616596115339, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(567.129978871211 313.0186100862052) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.45543009671823337 7.742311644209971, 0.9108601934364667 15.484623288419941, 1.4654995737853833 24.913492754351527 M0 0 C0.4866783450781988 8.273531866329382, 0.9733566901563976 16.547063732658764, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.7498470395947 321.1805929153993) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-7.793431841498607 0, -15.586863682997214 0, -33.21799033913535 0 M0 0 C-10.464785813465676 0, -20.929571626931352 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.2745570852853 329.8761658787048) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-8.922130076295742 0.2478369465637708, -17.844260152591485 0.4956738931275416, -35.17198977084917 0.9769997158569222 M0 0 C-12.974155529517464 0.36039320915326317, -25.94831105903493 0.7207864183065263, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(18.929579257965088 10) rotate(0 316 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># all values (except auto refund) load default and are optional overriden</text></g><g transform="translate(122.83358089511557 657.9878683353941) rotate(0 66 13.5)"><text x="0" y="21" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">fullfilment url</text></g><g transform="translate(381.460536751943 650.9394254288181) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C48.22669300393149 0, 96.45338600786297 0, 199.70588235294125 0 M0 0 C45.50370675193915 0, 91.0074135038783 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 8.990837124474735, 199.70588235294125 17.98167424894947, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 14.383608090506296, 199.70588235294125 28.767216181012593, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C152.58380565441712 37.59169550173011, 105.46172895589298 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C158.28431744917356 37.59169550173011, 116.86275254540587 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 26.598232012306532, 0 15.604768522882956, 0 0 M0 37.59169550173011 C0 26.77625049695012, 0 15.960805492170131, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(124.43070263151071 362.6087328723743) rotate(0 66.5 13.5)"><text x="0" y="21" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581316px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">delivery date</text></g><g transform="translate(387.78648718559225 361.87284353785617) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C55.43980877578232 0, 110.87961755156464 0, 139.51157167042618 0 M0 0 C38.67123892787831 0, 77.34247785575663 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 12.794862940982673, 139.51157167042618 25.589725881965347, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 12.89935412691385, 139.51157167042618 25.7987082538277, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C88.24394089405709 36.06923440786065, 36.97631011768799 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C84.87579098476786 36.06923440786065, 30.240010299109528 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 24.201030776872482, 0 12.332827145884316, 0 0 M0 36.06923440786065 C0 25.001084232279638, 0 13.932934056698628, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(421.6375334630645 368.1834641515052) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(451.4360247967006 367.6949642935774) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(538.3889995079655 360.8559662825792) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C9.57520347866013 0, 19.15040695732026 0, 34.68348991292074 0 M0 0 C12.829012201846544 0, 25.658024403693087 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 13.387734282680508, 34.68348991292074 26.775468565361017, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 7.41516960577264, 34.68348991292074 14.83033921154528, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C27.256849805847033 34.68348991292074, 19.83020969877332 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C20.983841327597055 34.68348991292074, 7.2841927422733725 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 23.17218993515759, 0 11.660889957394435, 0 0 M0 34.68348991292074 C0 26.586830031075714, 0 18.490170149230686, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(539.3659992238224 361.83296599843425) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C12.285038274544192 0, 24.570076549088384 0, 34.19499005499228 0 M0 0 C9.276475028002112 0, 18.552950056004224 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 2.434006584223475, 34.19499005499228 4.86801316844695, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 2.781845972529476, 34.19499005499228 5.563691945058952, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C24.602459615257793 8.792997442712293, 15.009929175523308 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C21.001977236583443 8.792997442712293, 7.808964418174604 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.841320005988653, 0 2.8896425692650114, 0 0 M0 8.792997442712293 C0 5.403896656500538, 0 2.0147958702887827, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(546.6934970927516 353.52846841364953) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.4406882612954708 0, 2.8813765225909416 0, 5.861998295141533 0 M0 0 C1.4006236417656008 0, 2.8012472835312017 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 5.3156390105338645, 5.861998295141533 10.631278021067729, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.455722168129303, 5.861998295141533 8.911444336258606, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C3.5450520464645443 13.677996021996911, 1.2281057977875554 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.64303247747399 13.677996021996911, 1.4240666598064466 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 10.243041026991754, 0 6.808086031986596, 0 0 M0 13.677996021996911 C0 8.505417493634637, 0 3.3328389652723622, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(561.348492830605 353.52846841364953) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.4048860706216215 0, 2.809772141243243 0, 5.861998295141533 0 M0 0 C1.9050741409846754 0, 3.810148281969351 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 2.947701635698273, 5.861998295141533 5.895403271396546, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 3.884129481375102, 5.861998295141533 7.768258962750204, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.442701754505925 13.677996021996911, 3.0234052138703165 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.595862491244022 13.677996021996911, 3.32972668734651 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.63321077458749, 0 5.588425527178066, 0 0 M0 13.677996021996911 C0 9.014838182708136, 0 4.3516803434193605, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(555.118531080688 369.9618043772889) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.10953645508111119 5.805432119298893, 0.21907291016222238 11.610864238597786, 0.4884998579284611 25.89049247020844 M0 0 C0.12451699391601756 6.599400677548931, 0.24903398783203512 13.198801355097862, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(546.0293380288911 368.85340137343337) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.10716094582593776 6.001012966252514, 0.2143218916518755 12.002025932505028, 0.4884998579284611 27.355992043993822 M0 0 C0.15526133988273694 8.694635033433268, 0.3105226797654739 17.389270066866537, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(564.9494292001057 369.9831125196679) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.41616099285571023 7.074736878547077, 0.8323219857114205 14.149473757094153, 1.4654995737853833 24.913492754351527 M0 0 C0.45905133818462057 7.803872749138553, 0.9181026763692411 15.607745498277106, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(573.5692973684893 378.145095348862) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-7.2177111304083565 0, -14.435422260816713 0, -33.21799033913535 0 M0 0 C-11.641370495996155 0, -23.28274099199231 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(573.0940074141797 386.8406683121675) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-9.749330494322896 0.270814735953414, -19.498660988645792 0.541629471906828, -35.17198977084917 0.9769997158569222 M0 0 C-12.476582768321407 0.3465717435644838, -24.953165536642814 0.6931434871289676, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(124.43146930138187 430.1561042809583) rotate(0 71 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="19.825388200978065px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">delivery location</text></g><g transform="translate(638.9002931167688 425.1997572307075) rotate(0 30.233717006491474 18.33848408590461)"><path d="M0 0 C22.472148607424298 0, 44.944297214848596 0, 60.46743401298301 0 M0 0 C17.41463295067124 0, 34.82926590134248 0, 60.46743401298301 0 M60.46743401298301 0 C60.46743401298301 11.592106789816704, 60.46743401298301 23.184213579633408, 60.46743401298301 36.676968171809314 M60.46743401298301 0 C60.46743401298301 9.014323088289395, 60.46743401298301 18.02864617657879, 60.46743401298301 36.676968171809314 M60.46743401298301 36.676968171809314 C39.47376126896 36.676968171809314, 18.48008852493698 36.676968171809314, 0 36.676968171809314 M60.46743401298301 36.676968171809314 C43.95284995083415 36.676968171809314, 27.438265888685287 36.676968171809314, 0 36.676968171809314 M0 36.676968171809314 C0 26.68601243134865, 0 16.69505669088799, 0 0 M0 36.676968171809314 C0 22.77452447295343, 0 8.872080774097551, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(666.6558365981346 431.1473736909927) rotate(0 0 10.903963510537892)"><path d="M0 0 C0 3.634654503512637, 0 18.173272517563184, 0 21.807927021075823 M0 0 C0 3.634654503512637, 0 18.173272517563184, 0 21.807927021075823" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(666.6558365981346 431.1473736909927) rotate(0 0 10.903963510537892)"><path d="M-3.729375162692038 11.561552972904538 C-2.761486101766064 14.220806312335792, -1.7935970408400899 16.880059651767045, 0 21.807927021075823 M-3.729375162692038 11.561552972904538 C-2.7688984867987685 14.200440951834132, -1.8084218109054992 16.839328930763727, 0 21.807927021075823" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(666.6558365981346 431.1473736909927) rotate(0 0 10.903963510537892)"><path d="M3.729375162692038 11.561552972904538 C2.761486101766064 14.220806312335792, 1.7935970408400899 16.880059651767045, 0 21.807927021075823 M3.729375162692038 11.561552972904538 C2.7688984867987685 14.200440951834132, 1.8084218109054992 16.839328930763727, 0 21.807927021075823" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(102 423.93245619264553) rotate(0 299.5 19)"><path d="M0 0 C193.9767573282123 0, 387.9535146564246 0, 599.0000000000001 0 M0 0 C162.25038655549292 0, 324.50077311098585 0, 599.0000000000001 0 M599.0000000000001 0 C599.0000000000001 10.080071184039117, 599.0000000000001 20.160142368078233, 599.0000000000001 38 M599.0000000000001 0 C599.0000000000001 14.464655616879465, 599.0000000000001 28.92931123375893, 599.0000000000001 38 M599.0000000000001 38 C364.0274778440595 38, 129.05495568811892 38, 0 38 M599.0000000000001 38 C396.98877884596595 38, 194.97755769193174 38, 0 38 M0 38 C0 27.496936300396918, 0 16.99387260079384, 0 0 M0 38 C0 25.07144419848919, 0 12.142888396978378, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g></svg> \ No newline at end of file
+ <rect x="0" y="0" width="812" height="755.4705463593664" fill="#ffffff"></rect><g transform="translate(10 57.28872817754791) rotate(0 396 344.09090909090924)"><path d="M0 0 C163.13469960540533 0, 326.26939921081066 0, 792 0 M0 0 C197.6944362178445 0, 395.388872435689 0, 792 0 M792 0 C792 158.56476755186247, 792 317.12953510372495, 792 688.1818181818186 M792 0 C792 266.1385155277837, 792 532.2770310555674, 792 688.1818181818186 M792 688.1818181818186 C541.9932290330529 688.1818181818186, 291.9864580661058 688.1818181818186, 0 688.1818181818186 M792 688.1818181818186 C500.6049041256308 688.1818181818186, 209.20980825126162 688.1818181818186, 0 688.1818181818186 M0 688.1818181818186 C0 513.5637151258577, 0 338.94561206989687, 0 0 M0 688.1818181818186 C0 501.1274236320436, 0 314.07302908226865, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(49 89.78872817754723) rotate(0 355.94636678200686 308.9999999999999)"><path d="M0 0 C196.37916639762955 0, 392.7583327952591 0, 711.8927335640136 0 M0 0 C222.65387708782745 0, 445.3077541756549 0, 711.8927335640136 0 M711.8927335640136 0 C711.8927335640136 136.01048013232645, 711.8927335640136 272.0209602646529, 711.8927335640136 617.9999999999998 M711.8927335640136 0 C711.8927335640136 229.1515129242092, 711.8927335640136 458.3030258484184, 711.8927335640136 617.9999999999998 M711.8927335640136 617.9999999999998 C540.7157390920328 617.9999999999998, 369.5387446200519 617.9999999999998, 0 617.9999999999998 M711.8927335640136 617.9999999999998 C560.9805535182735 617.9999999999998, 410.06837347253344 617.9999999999998, 0 617.9999999999998 M0 617.9999999999998 C0 487.54947386719266, 0 357.0989477343856, 0 0 M0 617.9999999999998 C0 469.64537168405934, 0 321.29074336811885, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(49 134.42886658585485) rotate(0 355.35899653979254 0)"><path d="M0 0 C118.45299884659751 0, 592.2649942329875 0, 710.7179930795851 0 M0 0 C118.45299884659751 0, 592.2649942329875 0, 710.7179930795851 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(77.19377162629803 96.83717108412088) rotate(0 45.22750865051904 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581313px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">payment</text></g><g transform="translate(686.884083044984 90.96346866197814) rotate(0 35.82958477508646 21.732698961937672)"><path d="M0 0 C25.693898552344557 0, 51.38779710468911 0, 71.65916955017302 0 M0 0 C18.423871399928533 0, 36.847742799857066 0, 71.65916955017302 0 M71.65916955017302 0 C71.65916955017302 14.811010985339712, 71.65916955017302 29.622021970679423, 71.65916955017302 43.46539792387543 M71.65916955017302 0 C71.65916955017302 9.170037636031626, 71.65916955017302 18.340075272063252, 71.65916955017302 43.46539792387543 M71.65916955017302 43.46539792387543 C48.850217413141436 43.46539792387543, 26.041265276109854 43.46539792387543, 0 43.46539792387543 M71.65916955017302 43.46539792387543 C50.35093945471351 43.46539792387543, 29.042709359254005 43.46539792387543, 0 43.46539792387543 M0 43.46539792387543 C0 27.43454704114484, 0 11.403696158414249, 0 0 M0 43.46539792387543 C0 26.72246210199255, 0 9.979526280109674, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(719.7768166089982 98.01191156855225) rotate(0 0 12.922145328719694)"><path d="M0 0 C0 4.307381776239908, 0 21.53690888119954, 0 25.844290657439448 M0 0 C0 4.307381776239908, 0 21.53690888119954, 0 25.844290657439448" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(719.7768166089982 98.01191156855225) rotate(0 0 12.922145328719694)"><path d="M-4.4196339974038406 13.701446047318425 C-3.1323002724435964 17.238366387949103, -1.8449665474833528 20.77528672857978, 0 25.844290657439448 M-4.4196339974038406 13.701446047318425 C-2.8705976157310924 17.957388527877995, -1.3215612340583442 22.213331008437564, 0 25.844290657439448" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(719.7768166089982 98.01191156855225) rotate(0 0 12.922145328719694)"><path d="M4.4196339974038406 13.701446047318425 C3.1323002724435964 17.238366387949103, 1.8449665474833528 20.77528672857978, 0 25.844290657439448 M4.4196339974038406 13.701446047318425 C2.8705976157310924 17.957388527877995, 1.3215612340583442 22.213331008437564, 0 25.844290657439448" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(122.41700882982559 491.89835384599746) rotate(0 41.70328719723187 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581313px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">max fee</text></g><g transform="translate(381.043964686653 486.02465142385245) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C44.61811129907696 0, 89.23622259815392 0, 199.70588235294125 0 M0 0 C79.29353130055485 0, 158.5870626011097 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 8.759946174821641, 199.70588235294125 17.519892349643282, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 10.044274510345245, 199.70588235294125 20.08854902069049, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C120.15937504977649 37.59169550173011, 40.61286774661173 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C131.51639080907717 37.59169550173011, 63.32689926521306 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 22.898467848158624, 0 8.205240194587144, 0 0 M0 37.59169550173011 C0 24.276735533449063, 0 10.961775565168022, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(122.41700882982559 548.2858970985903) rotate(0 66.37283737024222 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">max wire fee</text></g><g transform="translate(381.043964686653 537.7132327387317) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C73.07786602499513 0, 146.15573204999026 0, 199.70588235294125 0 M0 0 C66.5611449872856 0, 133.1222899745712 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 11.502282709924822, 199.70588235294125 23.004565419849644, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 11.270341817534508, 199.70588235294125 22.540683635069016, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C129.25950294366 37.59169550173011, 58.813123534378775 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C136.2727813783705 37.59169550173011, 72.83968040379972 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 24.7357254136908, 0 11.879755325651498, 0 0 M0 37.59169550173011 C0 25.50134813334909, 0 13.411000764968072, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(122.41700882982559 608.1976618044737) rotate(0 108.66349480968859 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">wire fee amortization</text></g><g transform="translate(381.043964686653 601.1492188978978) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C63.18052968749888 0, 126.36105937499777 0, 199.70588235294125 0 M0 0 C58.832443530780886 0, 117.66488706156177 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 12.2283371196383, 199.70588235294125 24.4566742392766, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 13.842964280734428, 199.70588235294125 27.685928561468856, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C137.16080129305453 37.59169550173011, 74.61572023316782 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C124.86287547313776 37.59169550173011, 50.01986859333428 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 25.7131450870561, 0 13.834594672382085, 0 0 M0 37.59169550173011 C0 29.765293690768495, 0 21.93889187980688, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(126.6112523026161 170.85274201837865) rotate(0 81.05709342560556 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581313px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">refund deadline</text></g><g transform="translate(389.96703685669763 170.11685268386054) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C38.588792372791914 0, 77.17758474558383 0, 139.51157167042618 0 M0 0 C51.270889713540704 0, 102.54177942708141 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 11.195579849357195, 139.51157167042618 22.39115969871439, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 7.478536088246398, 139.51157167042618 14.957072176492796, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C106.073634546001 36.06923440786065, 72.63569742157583 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C90.59517821844433 36.06923440786065, 41.678784766462485 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 28.587188228147372, 0 21.105142048434097, 0 0 M0 36.06923440786065 C0 24.459576137678855, 0 12.849917867497062, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(423.8180831341699 176.4274732975059) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(453.616574467806 175.93897343957906) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(540.5695491790709 169.09997542858218) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C12.350741138777865 0, 24.70148227755573 0, 34.68348991292074 0 M0 0 C13.090885427004103 0, 26.181770854008207 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 8.759752021302454, 34.68348991292074 17.519504042604908, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 12.827119610736222, 34.68348991292074 25.654239221472444, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C26.26168783725121 34.68348991292074, 17.83988576158168 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C25.047276653082115 34.68348991292074, 15.411063393243492 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 21.65963125803831, 0 8.635772603155882, 0 0 M0 34.68348991292074 C0 22.816433409973676, 0 10.949376907026608, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(541.5465488949278 170.07697514443726) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C8.416124992044198 0, 16.832249984088396 0, 34.19499005499228 0 M0 0 C12.612778286946597 0, 25.225556573893194 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 2.784839725361176, 34.19499005499228 5.569679450722352, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 2.6248021666892867, 34.19499005499228 5.249604333378573, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C22.07788480659337 8.792997442712293, 9.96077955819446 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C25.609229118108246 8.792997442712293, 17.023468181224214 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.374793220870361, 0 1.956588999028427, 0 0 M0 8.792997442712293 C0 6.7982943981253765, 0 4.80359135353846, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(548.874046763857 161.77247755965436) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C2.06572777238916 0, 4.13145554477832 0, 5.861998295141533 0 M0 0 C1.6284420677636307 0, 3.2568841355272613 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 4.0886379104374235, 5.861998295141533 8.177275820874847, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.405838529784974, 5.861998295141533 8.811677059569949, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C3.787286061220638 13.677996021996911, 1.7125738272997424 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.3944272776554225 13.677996021996911, 2.9268562601693118 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.10398929446179, 0 4.52998256692667, 0 0 M0 13.677996021996911 C0 9.353978362633276, 0 5.029960703269641, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(563.5290425017101 161.77247755965436) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.9136207457158667 0, 3.8272414914317334 0, 5.861998295141533 0 M0 0 C1.7752901701469925 0, 3.550580340293985 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 3.1488935786452767, 5.861998295141533 6.2977871572905535, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.449927547836239, 5.861998295141533 8.899855095672478, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.27455321609603 13.677996021996911, 2.6871081370505263 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.940863191629518 13.677996021996911, 2.019728088117503 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 10.13870173751964, 0 6.599407453042369, 0 0 M0 13.677996021996911 C0 9.428222501847815, 0 5.178448981698718, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(557.2990807517933 178.205813523291) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.13838525821577435 7.334418685436041, 0.2767705164315487 14.668837370872081, 0.4884998579284611 25.89049247020844 M0 0 C0.18365905060197285 9.733929681904561, 0.3673181012039457 19.467859363809122, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(548.2098876999967 177.0974105194373) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.18081889830845407 10.125858305273429, 0.36163779661690815 20.251716610546858, 0.4884998579284611 27.355992043993822 M0 0 C0.1435033739142469 8.036188939197826, 0.2870067478284938 16.072377878395653, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(567.129978871211 178.22712166566998) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.49131225684945856 8.352308366440798, 0.9826245136989171 16.704616732881597, 1.4654995737853833 24.913492754351527 M0 0 C0.3645462207600854 6.197285752921455, 0.7290924415201708 12.39457150584291, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.7498470395947 186.38910449486139) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-9.850046834715748 0, -19.700093669431496 0, -33.21799033913535 0 M0 0 C-11.978115758822755 0, -23.95623151764551 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.2745570852853 195.08467745817006) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-10.91708283817915 0.3032523010605322, -21.8341656763583 0.6065046021210644, -35.17198977084917 0.9769997158569222 M0 0 C-7.292502852220365 0.20256952367278808, -14.58500570444073 0.40513904734557615, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(126.6112523026161 236.4338141678163) rotate(0 66.37283737024222 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">pay deadline</text></g><g transform="translate(389.96703685669763 235.69792483329775) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C47.50166837735665 0, 95.0033367547133 0, 139.51157167042618 0 M0 0 C49.76376503488774 0, 99.52753006977548 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 8.446743562554808, 139.51157167042618 16.893487125109615, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 11.47235701590513, 139.51157167042618 22.94471403181026, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C96.27412265275322 36.06923440786065, 53.03667363508026 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C94.95422630662594 36.06923440786065, 50.39688094282572 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 26.300498294737366, 0 16.531762181614084, 0 0 M0 36.06923440786065 C0 28.404608233033215, 0 20.739982058205783, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(423.8180831341699 242.00854544694494) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(453.616574467806 241.52004558901763) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(540.5695491790709 234.68104757801984) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C10.984645583369092 0, 21.969291166738184 0, 34.68348991292074 0 M0 0 C10.353386324163305 0, 20.70677264832661 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 12.290206751947466, 34.68348991292074 24.58041350389493, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 8.708414664553807, 34.68348991292074 17.416829329107614, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C21.200573260099773 34.68348991292074, 7.717656607278801 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C26.81549457038345 34.68348991292074, 18.94749922784616 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 25.885278309906877, 0 17.08706670689301, 0 0 M0 34.68348991292074 C0 21.8895309879799, 0 9.095572063039057, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(541.5465488949278 235.65804729387492) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C10.22159477609356 0, 20.44318955218712 0, 34.19499005499228 0 M0 0 C11.014596324462437 0, 22.029192648924873 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 3.1120683508813407, 34.19499005499228 6.224136701762681, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 2.2013565262291643, 34.19499005499228 4.402713052458329, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C22.75997323615448 8.792997442712293, 11.324956417316677 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C23.38494590658319 8.792997442712293, 12.574901758174104 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.889444189066426, 0 2.9858909354205574, 0 0 M0 8.792997442712293 C0 5.741208897211333, 0 2.689420351710372, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(548.874046763857 227.35354970909202) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.3495258194194044 0, 2.699051638838809 0, 5.861998295141533 0 M0 0 C1.907111806215531 0, 3.814223612431062 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 3.7040385177728417, 5.861998295141533 7.408077035545683, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.482648574861369, 5.861998295141533 8.965297149722739, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.345157887508417 13.677996021996911, 2.828317479875301 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.040666786506207 13.677996021996911, 2.2193352778708797 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 8.942165936497862, 0 4.20633585099881, 0 0 M0 13.677996021996911 C0 9.845296872419517, 0 6.012597722842125, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(563.5290425017101 227.35354970909202) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.5320176410516326 0, 3.0640352821032653 0, 5.861998295141533 0 M0 0 C2.169100553518035 0, 4.33820110703607 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 4.243283857466258, 5.861998295141533 8.486567714932516, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.871870315718041, 5.861998295141533 9.743740631436083, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.453460484325019 13.677996021996911, 3.0449226735085038 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.658298036524886 13.677996021996911, 1.4545977779082389 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.954515139904878, 0 6.231034257812844, 0 0 M0 13.677996021996911 C0 9.474895524801425, 0 5.27179502760594, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(557.2990807517933 243.78688567272866) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.1131082943342409 5.994739599714768, 0.2262165886684818 11.989479199429535, 0.4884998579284611 25.89049247020844 M0 0 C0.11061345453975935 5.862513090607246, 0.2212269090795187 11.725026181214492, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(548.2098876999967 242.6784826688745) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.11589865333866953 6.490324586965493, 0.23179730667733905 12.980649173930987, 0.4884998579284611 27.355992043993822 M0 0 C0.14872427158435972 8.328559208724144, 0.29744854316871944 16.657118417448288, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(567.129978871211 243.80819381510855) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.29443945475956246 5.005470730912564, 0.5888789095191249 10.010941461825128, 1.4654995737853833 24.913492754351527 M0 0 C0.5640236075493323 9.588401328338653, 1.1280472150986647 19.176802656677307, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.7498470395947 251.97017664429995) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-7.961641167542139 0, -15.923282335084277 0, -33.21799033913535 0 M0 0 C-11.647093252965808 0, -23.294186505931616 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.2745570852853 260.66574960760863) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-8.236625563486276 0.22879515454128563, -16.473251126972553 0.45759030908257126, -35.17198977084917 0.9769997158569222 M0 0 C-11.186975000584102 0.3107493055717809, -22.373950001168204 0.6214986111435618, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(126.6112523026161 305.64423043891156) rotate(0 106.90138408304506 13.50951557093424)"><text x="0" y="21.01903114186851" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581316px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">auto refund deadline</text></g><g transform="translate(389.96703685669763 304.90834110439346) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C54.233956780562394 0, 108.46791356112479 0, 139.51157167042618 0 M0 0 C31.64838368021466 0, 63.29676736042932 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 9.149735435384923, 139.51157167042618 18.299470870769845, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 13.305128884862302, 139.51157167042618 26.610257769724605, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C91.61872293837436 36.06923440786065, 43.72587420632256 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C96.71713609249963 36.06923440786065, 53.922700514573094 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 22.783353778422555, 0 9.497473148984461, 0 0 M0 36.06923440786065 C0 25.237278543595075, 0 14.405322679329501, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(423.8180831341699 311.21896171804246) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(453.616574467806 310.7304618601147) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(540.5695491790709 303.89146384911646) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C8.333848713997714 0, 16.667697427995428 0, 34.68348991292074 0 M0 0 C13.03855986348183 0, 26.07711972696366 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 9.4416836653048, 34.68348991292074 18.8833673306096, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 10.65786197503141, 34.68348991292074 21.31572395006282, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C21.040335615093984 34.68348991292074, 7.397181317267226 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C27.450049553381234 34.68348991292074, 20.216609193841723 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 23.797810389582665, 0 12.912130866244592, 0 0 M0 34.68348991292074 C0 25.594766385240447, 0 16.506042857560153, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(541.5465488949278 304.86846356497153) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C12.256912387278875 0, 24.51382477455775 0, 34.19499005499228 0 M0 0 C9.337819074833758 0, 18.675638149667517 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 2.953364940224322, 34.19499005499228 5.906729880448644, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 1.8197494907887624, 34.19499005499228 3.6394989815775247, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C24.437822160950887 8.792997442712293, 14.680654266909496 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C26.745579617251906 8.792997442712293, 19.29616917951153 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.897504324214696, 0 3.002011205717099, 0 0 M0 8.792997442712293 C0 6.010860718858778, 0 3.2287239950052617, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(548.874046763857 296.5639659801868) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.5383239337541497 0, 3.0766478675082993 0, 5.861998295141533 0 M0 0 C1.703193236340253 0, 3.406386472680506 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 5.346398526020947, 5.861998295141533 10.692797052041893, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.496599266354498, 5.861998295141533 8.993198532708996, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C3.891777796747413 13.677996021996911, 1.9215572983532936 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.038711915432156 13.677996021996911, 2.2154255357227792 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 10.407330957556045, 0 7.13666589311518, 0 0 M0 13.677996021996911 C0 8.615481990624176, 0 3.5529679592514416, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(563.5290425017101 296.5639659801868) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.992445036968253 0, 3.984890073936506 0, 5.861998295141533 0 M0 0 C2.1991607886562967 0, 4.398321577312593 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 3.9068180477119214, 5.861998295141533 7.813636095423843, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.436560428108088, 5.861998295141533 8.873120856216175, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.2584782283409455 13.677996021996911, 2.6549581615403572 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.951389670446835 13.677996021996911, 2.040781045752137 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.992197423731628, 0 6.306398825466346, 0 0 M0 13.677996021996911 C0 9.18606117187456, 0 4.694126321752208, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(557.2990807517933 312.9973019438262) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.13958349564106426 7.397925268976406, 0.2791669912821285 14.795850537952813, 0.4884998579284611 25.89049247020844 M0 0 C0.12864824285337711 6.818356871228986, 0.25729648570675423 13.636713742457973, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(548.2098876999967 311.88889893997066) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.13230184343367635 7.408903232285875, 0.2646036868673527 14.81780646457175, 0.4884998579284611 27.355992043993822 M0 0 C0.10380505322458383 5.813082980576695, 0.20761010644916766 11.62616596115339, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(567.129978871211 313.0186100862052) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.45543009671823337 7.742311644209971, 0.9108601934364667 15.484623288419941, 1.4654995737853833 24.913492754351527 M0 0 C0.4866783450781988 8.273531866329382, 0.9733566901563976 16.547063732658764, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.7498470395947 321.1805929153993) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-7.793431841498607 0, -15.586863682997214 0, -33.21799033913535 0 M0 0 C-10.464785813465676 0, -20.929571626931352 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(575.2745570852853 329.8761658787048) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-8.922130076295742 0.2478369465637708, -17.844260152591485 0.4956738931275416, -35.17198977084917 0.9769997158569222 M0 0 C-12.974155529517464 0.36039320915326317, -25.94831105903493 0.7207864183065263, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(18.929579257965088 10) rotate(0 316 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># all values (except auto refund) load default and are optional overridden</text></g><g transform="translate(122.83358089511557 657.9878683353941) rotate(0 66 13.5)"><text x="0" y="21" font-family="Helvetica, Segoe UI Emoji" font-size="23.49480968858131px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">fullfilment url</text></g><g transform="translate(381.460536751943 650.9394254288181) rotate(0 99.85294117647061 18.79584775086505)"><path d="M0 0 C48.22669300393149 0, 96.45338600786297 0, 199.70588235294125 0 M0 0 C45.50370675193915 0, 91.0074135038783 0, 199.70588235294125 0 M199.70588235294125 0 C199.70588235294125 8.990837124474735, 199.70588235294125 17.98167424894947, 199.70588235294125 37.59169550173011 M199.70588235294125 0 C199.70588235294125 14.383608090506296, 199.70588235294125 28.767216181012593, 199.70588235294125 37.59169550173011 M199.70588235294125 37.59169550173011 C152.58380565441712 37.59169550173011, 105.46172895589298 37.59169550173011, 0 37.59169550173011 M199.70588235294125 37.59169550173011 C158.28431744917356 37.59169550173011, 116.86275254540587 37.59169550173011, 0 37.59169550173011 M0 37.59169550173011 C0 26.598232012306532, 0 15.604768522882956, 0 0 M0 37.59169550173011 C0 26.77625049695012, 0 15.960805492170131, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(124.43070263151071 362.6087328723743) rotate(0 66.5 13.5)"><text x="0" y="21" font-family="Helvetica, Segoe UI Emoji" font-size="23.494809688581316px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">delivery date</text></g><g transform="translate(387.78648718559225 361.87284353785617) rotate(0 69.75578583521315 18.03461720393034)"><path d="M0 0 L139.51157167042618 0 L139.51157167042618 36.06923440786065 L0 36.06923440786065" stroke="none" stroke-width="0" fill="#fff"></path><path d="M0 0 C55.43980877578232 0, 110.87961755156464 0, 139.51157167042618 0 M0 0 C38.67123892787831 0, 77.34247785575663 0, 139.51157167042618 0 M139.51157167042618 0 C139.51157167042618 12.794862940982673, 139.51157167042618 25.589725881965347, 139.51157167042618 36.06923440786065 M139.51157167042618 0 C139.51157167042618 12.89935412691385, 139.51157167042618 25.7987082538277, 139.51157167042618 36.06923440786065 M139.51157167042618 36.06923440786065 C88.24394089405709 36.06923440786065, 36.97631011768799 36.06923440786065, 0 36.06923440786065 M139.51157167042618 36.06923440786065 C84.87579098476786 36.06923440786065, 30.240010299109528 36.06923440786065, 0 36.06923440786065 M0 36.06923440786065 C0 24.201030776872482, 0 12.332827145884316, 0 0 M0 36.06923440786065 C0 25.001084232279638, 0 13.932934056698628, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(421.6375334630645 368.1834641515052) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(451.4360247967006 367.6949642935774) rotate(0 4.884998579284627 12.212496448211482)"><text x="0" y="17.42499289642305" font-family="Virgil, Segoe UI Emoji" font-size="19.539994317138444px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(538.3889995079655 360.8559662825792) rotate(0 17.34174495646039 17.34174495646039)"><path d="M0 0 L34.68348991292074 0 L34.68348991292074 34.68348991292074 L0 34.68348991292074" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C9.57520347866013 0, 19.15040695732026 0, 34.68348991292074 0 M0 0 C12.829012201846544 0, 25.658024403693087 0, 34.68348991292074 0 M34.68348991292074 0 C34.68348991292074 13.387734282680508, 34.68348991292074 26.775468565361017, 34.68348991292074 34.68348991292074 M34.68348991292074 0 C34.68348991292074 7.41516960577264, 34.68348991292074 14.83033921154528, 34.68348991292074 34.68348991292074 M34.68348991292074 34.68348991292074 C27.256849805847033 34.68348991292074, 19.83020969877332 34.68348991292074, 0 34.68348991292074 M34.68348991292074 34.68348991292074 C20.983841327597055 34.68348991292074, 7.2841927422733725 34.68348991292074, 0 34.68348991292074 M0 34.68348991292074 C0 23.17218993515759, 0 11.660889957394435, 0 0 M0 34.68348991292074 C0 26.586830031075714, 0 18.490170149230686, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(539.3659992238224 361.83296599843425) rotate(0 17.097495027496166 4.396498721356124)"><path d="M0 0 L34.19499005499228 0 L34.19499005499228 8.792997442712293 L0 8.792997442712293" stroke="none" stroke-width="0" fill="#000"></path><path d="M0 0 C12.285038274544192 0, 24.570076549088384 0, 34.19499005499228 0 M0 0 C9.276475028002112 0, 18.552950056004224 0, 34.19499005499228 0 M34.19499005499228 0 C34.19499005499228 2.434006584223475, 34.19499005499228 4.86801316844695, 34.19499005499228 8.792997442712293 M34.19499005499228 0 C34.19499005499228 2.781845972529476, 34.19499005499228 5.563691945058952, 34.19499005499228 8.792997442712293 M34.19499005499228 8.792997442712293 C24.602459615257793 8.792997442712293, 15.009929175523308 8.792997442712293, 0 8.792997442712293 M34.19499005499228 8.792997442712293 C21.001977236583443 8.792997442712293, 7.808964418174604 8.792997442712293, 0 8.792997442712293 M0 8.792997442712293 C0 5.841320005988653, 0 2.8896425692650114, 0 0 M0 8.792997442712293 C0 5.403896656500538, 0 2.0147958702887827, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(546.6934970927516 353.52846841364953) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.4406882612954708 0, 2.8813765225909416 0, 5.861998295141533 0 M0 0 C1.4006236417656008 0, 2.8012472835312017 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 5.3156390105338645, 5.861998295141533 10.631278021067729, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 4.455722168129303, 5.861998295141533 8.911444336258606, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C3.5450520464645443 13.677996021996911, 1.2281057977875554 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C3.64303247747399 13.677996021996911, 1.4240666598064466 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 10.243041026991754, 0 6.808086031986596, 0 0 M0 13.677996021996911 C0 8.505417493634637, 0 3.3328389652723622, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(561.348492830605 353.52846841364953) rotate(0 2.9309991475707875 6.838998010998466)"><path d="M0 0 L5.861998295141533 0 L5.861998295141533 13.677996021996911 L0 13.677996021996911" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0 0 C1.4048860706216215 0, 2.809772141243243 0, 5.861998295141533 0 M0 0 C1.9050741409846754 0, 3.810148281969351 0, 5.861998295141533 0 M5.861998295141533 0 C5.861998295141533 2.947701635698273, 5.861998295141533 5.895403271396546, 5.861998295141533 13.677996021996911 M5.861998295141533 0 C5.861998295141533 3.884129481375102, 5.861998295141533 7.768258962750204, 5.861998295141533 13.677996021996911 M5.861998295141533 13.677996021996911 C4.442701754505925 13.677996021996911, 3.0234052138703165 13.677996021996911, 0 13.677996021996911 M5.861998295141533 13.677996021996911 C4.595862491244022 13.677996021996911, 3.32972668734651 13.677996021996911, 0 13.677996021996911 M0 13.677996021996911 C0 9.63321077458749, 0 5.588425527178066, 0 0 M0 13.677996021996911 C0 9.014838182708136, 0 4.3516803434193605, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(555.118531080688 369.9618043772889) rotate(0 0.24424992896422282 12.945246235104264)"><path d="M0 0 C0.10953645508111119 5.805432119298893, 0.21907291016222238 11.610864238597786, 0.4884998579284611 25.89049247020844 M0 0 C0.12451699391601756 6.599400677548931, 0.24903398783203512 13.198801355097862, 0.4884998579284611 25.89049247020844" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(546.0293380288911 368.85340137343337) rotate(0 0.24424992896422282 13.677996021996933)"><path d="M0 0 C0.10716094582593776 6.001012966252514, 0.2143218916518755 12.002025932505028, 0.4884998579284611 27.355992043993822 M0 0 C0.15526133988273694 8.694635033433268, 0.3105226797654739 17.389270066866537, 0.4884998579284611 27.355992043993822" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(564.9494292001057 369.9831125196679) rotate(0 0.7327497868926685 12.456746377175818)"><path d="M0 0 C0.41616099285571023 7.074736878547077, 0.8323219857114205 14.149473757094153, 1.4654995737853833 24.913492754351527 M0 0 C0.45905133818462057 7.803872749138553, 0.9181026763692411 15.607745498277106, 1.4654995737853833 24.913492754351527" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(573.5692973684893 378.145095348862) rotate(0 -16.60899516956772 0)"><path d="M0 0 C-7.2177111304083565 0, -14.435422260816713 0, -33.21799033913535 0 M0 0 C-11.641370495996155 0, -23.28274099199231 0, -33.21799033913535 0" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(573.0940074141797 386.8406683121675) rotate(0 -17.58599488542461 0.48849985792844564)"><path d="M0 0 C-9.749330494322896 0.270814735953414, -19.498660988645792 0.541629471906828, -35.17198977084917 0.9769997158569222 M0 0 C-12.476582768321407 0.3465717435644838, -24.953165536642814 0.6931434871289676, -35.17198977084917 0.9769997158569222" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(124.43146930138187 430.1561042809583) rotate(0 71 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="19.825388200978065px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">delivery location</text></g><g transform="translate(638.9002931167688 425.1997572307075) rotate(0 30.233717006491474 18.33848408590461)"><path d="M0 0 C22.472148607424298 0, 44.944297214848596 0, 60.46743401298301 0 M0 0 C17.41463295067124 0, 34.82926590134248 0, 60.46743401298301 0 M60.46743401298301 0 C60.46743401298301 11.592106789816704, 60.46743401298301 23.184213579633408, 60.46743401298301 36.676968171809314 M60.46743401298301 0 C60.46743401298301 9.014323088289395, 60.46743401298301 18.02864617657879, 60.46743401298301 36.676968171809314 M60.46743401298301 36.676968171809314 C39.47376126896 36.676968171809314, 18.48008852493698 36.676968171809314, 0 36.676968171809314 M60.46743401298301 36.676968171809314 C43.95284995083415 36.676968171809314, 27.438265888685287 36.676968171809314, 0 36.676968171809314 M0 36.676968171809314 C0 26.68601243134865, 0 16.69505669088799, 0 0 M0 36.676968171809314 C0 22.77452447295343, 0 8.872080774097551, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(666.6558365981346 431.1473736909927) rotate(0 0 10.903963510537892)"><path d="M0 0 C0 3.634654503512637, 0 18.173272517563184, 0 21.807927021075823 M0 0 C0 3.634654503512637, 0 18.173272517563184, 0 21.807927021075823" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(666.6558365981346 431.1473736909927) rotate(0 0 10.903963510537892)"><path d="M-3.729375162692038 11.561552972904538 C-2.761486101766064 14.220806312335792, -1.7935970408400899 16.880059651767045, 0 21.807927021075823 M-3.729375162692038 11.561552972904538 C-2.7688984867987685 14.200440951834132, -1.8084218109054992 16.839328930763727, 0 21.807927021075823" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(666.6558365981346 431.1473736909927) rotate(0 0 10.903963510537892)"><path d="M3.729375162692038 11.561552972904538 C2.761486101766064 14.220806312335792, 1.7935970408400899 16.880059651767045, 0 21.807927021075823 M3.729375162692038 11.561552972904538 C2.7688984867987685 14.200440951834132, 1.8084218109054992 16.839328930763727, 0 21.807927021075823" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(102 423.93245619264553) rotate(0 299.5 19)"><path d="M0 0 C193.9767573282123 0, 387.9535146564246 0, 599.0000000000001 0 M0 0 C162.25038655549292 0, 324.50077311098585 0, 599.0000000000001 0 M599.0000000000001 0 C599.0000000000001 10.080071184039117, 599.0000000000001 20.160142368078233, 599.0000000000001 38 M599.0000000000001 0 C599.0000000000001 14.464655616879465, 599.0000000000001 28.92931123375893, 599.0000000000001 38 M599.0000000000001 38 C364.0274778440595 38, 129.05495568811892 38, 0 38 M599.0000000000001 38 C396.98877884596595 38, 194.97755769193174 38, 0 38 M0 38 C0 27.496936300396918, 0 16.99387260079384, 0 0 M0 38 C0 25.07144419848919, 0 12.142888396978378, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g></svg> \ No newline at end of file
diff --git a/backoffice-product-create.stock.svg b/backoffice-product-create.stock.svg
index 3a60b19f..0ec29939 100644
--- a/backoffice-product-create.stock.svg
+++ b/backoffice-product-create.stock.svg
@@ -13,4 +13,4 @@
}
</style>
</defs>
- <rect x="0" y="0" width="2664.430097077814" height="3110.5102161527097" fill="#ffffff"></rect><g transform="translate(10 80.94624914970746) rotate(0 412 84.5)"><path d="M0 0 C236.0917525626719 0, 472.1835051253438 0, 824 0 M0 0 C176.35285029634835 0, 352.7057005926967 0, 824 0 M824 0 C824 49.4694261523895, 824 98.938852304779, 824 169 M824 0 C824 67.28842992009595, 824 134.5768598401919, 824 169 M824 169 C617.4895157001913 169, 410.9790314003825 169, 0 169 M824 169 C591.9370339371264 169, 359.8740678742528 169, 0 169 M0 169 C0 117.27438288601115, 0 65.5487657720223, 0 0 M0 169 C0 113.33390512643382, 0 57.66781025286764, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(103 150.94624914970746) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(195 136.19977029409756) rotate(0 91.5 26.5)"><path d="M0 0 C45.52723533092067 0, 91.05447066184134 0, 183 0 M0 0 C51.992949485499416 0, 103.98589897099883 0, 183 0 M183 0 C183 11.300868693832308, 183 22.601737387664617, 183 53 M183 0 C183 11.438832641299815, 183 22.87766528259963, 183 53 M183 53 C122.95858201207594 53, 62.91716402415186 53, 0 53 M183 53 C130.75365380672739 53, 78.50730761345476 53, 0 53 M0 53 C0 34.84640925144777, 0 16.692818502895534, 0 0 M0 53 C0 40.84659901196137, 0 28.69319802392274, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(223 151.19977029409756) rotate(0 62.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">manage stock</text></g><g transform="translate(907.7142857142858 69.91405600838334) rotate(0 412 208.375)"><path d="M0 0 C254.33701222166422 0, 508.67402444332845 0, 824 0 M0 0 C264.9951364375651 0, 529.9902728751302 0, 824 0 M824 0 C824 153.41329097186681, 824 306.82658194373363, 824 416.75 M824 0 C824 126.50023594351951, 824 253.00047188703903, 824 416.75 M824 416.75 C651.8076281763613 416.75, 479.6152563527226 416.75, 0 416.75 M824 416.75 C609.0305379532277 416.75, 394.06107590645547 416.75, 0 416.75 M0 416.75 C0 306.4687552821124, 0 196.1875105642248, 0 0 M0 416.75 C0 326.79617198703346, 0 236.8423439740669, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1000.7142857142858 139.91405600838334) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(999.7142857142858 194.41405600838334) rotate(0 57 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock</text></g><g transform="translate(1159.7142857142858 183.41405600838334) rotate(0 49.370370370370324 25)"><path d="M0 0 C20.526187548641513 0, 41.052375097283026 0, 98.74074074074065 0 M0 0 C33.9206452924413 0, 67.8412905848826 0, 98.74074074074065 0 M98.74074074074065 0 C98.74074074074065 15.900936177931726, 98.74074074074065 31.801872355863452, 98.74074074074065 50 M98.74074074074065 0 C98.74074074074065 17.552169584669176, 98.74074074074065 35.10433916933835, 98.74074074074065 50 M98.74074074074065 50 C77.60304118323788 50, 56.465341625735114 50, 0 50 M98.74074074074065 50 C65.00140776341016 50, 31.262074786079666 50, 0 50 M0 50 C0 34.59016109351069, 0 19.18032218702137, 0 0 M0 50 C0 38.90304414089769, 0 27.806088281795382, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1195.3068783068784 196.91405600838334) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(908.6428571428569 628.8069131512407) rotate(0 412 199)"><path d="M0 0 C171.06364584192636 0, 342.1272916838527 0, 824 0 M0 0 C255.8546619512141 0, 511.7093239024282 0, 824 0 M824 0 C824 81.9858877783641, 824 163.9717755567282, 824 397.9999999999999 M824 0 C824 144.49592706207184, 824 288.9918541241437, 824 397.9999999999999 M824 397.9999999999999 C531.5596520878374 397.9999999999999, 239.11930417567487 397.9999999999999, 0 397.9999999999999 M824 397.9999999999999 C640.076900806278 397.9999999999999, 456.153801612556 397.9999999999999, 0 397.9999999999999 M0 397.9999999999999 C0 254.37236361447714, 0 110.74472722895439, 0 0 M0 397.9999999999999 C0 268.26489560361944, 0 138.529791207239, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1001.6428571428569 698.8069131512407) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1020.6428571428569 753.3069131512407) rotate(0 57 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock</text></g><g transform="translate(1027.6428571428569 833.9735798179072) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1207.6428571428569 838.9735798179072) rotate(0 50 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">15/02/2021</text></g><g transform="translate(1160.6428571428569 827.4735798179072) rotate(0 94.5 23)"><path d="M0 0 C49.514051240589474 0, 99.02810248117895 0, 189 0 M0 0 C62.65167563119903 0, 125.30335126239807 0, 189 0 M189 0 C189 11.8154281610623, 189 23.6308563221246, 189 46 M189 0 C189 13.85625107046217, 189 27.71250214092434, 189 46 M189 46 C139.20686839828267 46, 89.41373679656535 46, 0 46 M189 46 C135.00017379960045 46, 81.0003475992009 46, 0 46 M0 46 C0 35.174531353078784, 0 24.349062706157564, 0 0 M0 46 C0 35.074815454520284, 0 24.14963090904057, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1504.0367965367964 832.2565512018541) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.5074106075470783 4.36293480719127, 3.276278039155605 2.328085597306652, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.2573088406609494 4.650643978407226, 2.776074505383347 2.903503939738564, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.3959154886861533 8.287470801869468, 6.658685596504696 4.5340831498099305, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.647507052907955 7.998047814865392, 7.161868724948299 3.955237175801777, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.4980046620184115 11.966306174529965, 11.124320767230662 5.493969878055035, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.42039580787412 13.205953355679192, 8.969103058942078 7.973264240353487, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.6630406266275335 16.72309795439064, 13.059790491519845 9.36447900092326, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.7918771398395315 16.574888499895042, 13.31746351794384 9.068060091932072, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.53269558237936 21.819686714933805, 15.060557227084947 13.159872504933709, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.336812067357375 19.74428810766848, 18.668790197040977 9.00907529040306, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C6.6674860457231295 28.912008778352718, 13.591594977833937 20.94673261469564, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C11.063083258220995 23.855452614226436, 22.38278940282967 10.833620286443082, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.323628940968602 26.201636217315997, 26.5411014764243 12.1470417964374, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C11.888797744895843 29.002589102194243, 21.67143908427878 17.748947566193888, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C20.458527636515843 25.241254923280316, 33.82388345965617 9.866171833072332, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C19.6486159863538 26.172951698266964, 32.20406015933209 11.729565383045628, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C25.559914321986906 25.469793194119767, 38.38358239374518 10.717850579680285, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.845389402126045 28.59249690254184, 32.95453255402346 16.963257996524433, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C27.17710913200512 29.706435723176284, 36.630956605918996 18.83102826249961, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C28.21692052825332 28.510269543463995, 38.71057939841539 16.43869590307503, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C31.542013545691002 30.782199931955766, 40.37375002542815 20.622449304764856, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C34.79887591425044 27.035608356498003, 46.88747476254703 13.12926615384933, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C35.54102822993252 32.27887212601467, 42.72870495705808 24.010395897811723, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C40.35904046007414 26.73638307085531, 52.36472941734131 12.92541778749301, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C43.7970188346571 28.878453710849296, 54.25367075864462 16.849451692187266, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C41.24319660752628 31.81629011859939, 49.14602630438298 22.725124507687454, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C47.70826425099851 30.47609289782541, 56.43308715447433 20.43933227106855, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C50.55418139142306 27.20223972991225, 62.12492143532342 13.891625935242235, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C54.63078229458096 28.609659188894028, 65.2911078337766 16.346357477912075, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C53.15853815763985 30.30328233174744, 62.3466195598944 19.7336037636189, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C60.24594347822508 28.24716750859936, 70.87835576421173 16.0159763222518, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C59.758081658722524 28.80838833284445, 69.90263212520662 17.138417970741987, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C63.07351194298503 31.091434424308353, 71.54647728586903 21.344402778376075, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C61.491216262386075 32.91165738615166, 68.38188592467111 24.984848702062685, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C66.15965410531828 33.638246327390696, 72.07568717368243 26.832628789469823, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C69.49393434923996 29.802595673962045, 78.74424766152576 19.161327482612517, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C70.53531559049588 34.70163594097189, 75.839994736175 28.599300641338488, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C73.30286312200644 31.517936695239655, 81.37508979919612 22.23190214987402, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C75.35165630103094 35.25808209598322, 79.82960172039202 30.10679515629021, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C76.73927055609427 33.66181449554887, 82.60483023051864 26.9142599554215, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.66673358224277 36.39116586378327, 83.47274087495305 32.0128553165966, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C78.5739796905923 37.64823541760579, 81.2872330916521 34.52699442424164, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.39465042796638 38.19970044653909, 85.28550012954716 36.02452668703729, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.53537583556748 38.037814383541495, 85.56695094474935 35.7007545610421, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C27.382204342065425 0, 54.76440868413085 0, 85.70000000000013 0 M0 0 C32.83631051179958 0, 65.67262102359916 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 10.997829736489804, 85.70000000000013 21.995659472979607, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 11.259689829032869, 85.70000000000013 22.519379658065738, 85.70000000000013 39 M85.70000000000013 39 C64.52176900905567 39, 43.343538018111204 39, 0 39 M85.70000000000013 39 C67.73251752677383 39, 49.76503505354754 39, 0 39 M0 39 C0 27.597405128460377, 0 16.194810256920753, 0 0 M0 39 C0 27.376256355363875, 0 15.75251271072775, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1518.1367965367963 840.0565512018541) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(999.37012987013 903.4373024460815) rotate(0 288.49999999999994 28.5)"><path d="M0 0 C221.61191750867292 0, 443.22383501734583 0, 576.9999999999999 0 M0 0 C148.48949075983836 0, 296.9789815196767 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 18.23236775575206, 576.9999999999999 36.46473551150412, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 20.396188223082575, 576.9999999999999 40.79237644616515, 576.9999999999999 57 M576.9999999999999 57 C347.06813987987107 57, 117.13627975974231 57, 0 57 M576.9999999999999 57 C383.78149965358887 57, 190.5629993071779 57, 0 57 M0 57 C0 45.01051211776212, 0 33.02102423552424, 0 0 M0 57 C0 37.25637744097039, 0 17.51275488194078, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1494.3701298701299 903.4373024460815) rotate(0 41 29)"><path d="M0 0 C24.34247198160738 0, 48.68494396321476 0, 82 0 M0 0 C16.84486520532519 0, 33.68973041065038 0, 82 0 M82 0 C82 23.11392058711499, 82 46.22784117422998, 82 58 M82 0 C82 13.750576350279154, 82 27.501152700558308, 82 58 M82 58 C62.860811912454665 58, 43.72162382490933 58, 0 58 M82 58 C56.11744595523923 58, 30.234891910478467 58, 0 58 M0 58 C0 44.30942313093692, 0 30.61884626187384, 0 0 M0 58 C0 36.32924136202782, 0 14.658482724055645, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1533.3701298701299 915.4373024460815) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1533.3701298701299 915.4373024460815) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-3.6026222832168626 24.101876626037612, -1.3909021298973574 30.17852780543567, 0 34 M-5.814342436536368 18.025225446639556 C-3.699884998619726 23.834649511713405, -1.585427560703084 29.644073576787253, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1533.3701298701299 915.4373024460815) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C3.6026222832168626 24.101876626037612, 1.3909021298973574 30.17852780543567, 0 34 M5.814342436536368 18.025225446639556 C3.699884998619726 23.834649511713405, 1.585427560703084 29.644073576787253, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1030.3560452925694 919.4373024460815) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(979.594140530665 313.37777863655765) rotate(0 288.49999999999994 28.5)"><path d="M0 0 C183.09519919259472 0, 366.19039838518944 0, 576.9999999999999 0 M0 0 C191.24218815611673 0, 382.48437631223345 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 21.60299933133647, 576.9999999999999 43.20599866267294, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 18.814047324378045, 576.9999999999999 37.62809464875609, 576.9999999999999 57 M576.9999999999999 57 C375.89746348159383 57, 174.79492696318772 57, 0 57 M576.9999999999999 57 C408.40211005760347 57, 239.80422011520704 57, 0 57 M0 57 C0 39.55012987079098, 0 22.100259741581972, 0 0 M0 57 C0 34.29172115949913, 0 11.583442318998273, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1474.5941405306648 313.37777863655765) rotate(0 41 29)"><path d="M0 0 C23.301106604374947 0, 46.602213208749895 0, 82 0 M0 0 C20.47363549526781 0, 40.94727099053562 0, 82 0 M82 0 C82 17.889156678505245, 82 35.77831335701049, 82 58 M82 0 C82 14.935910755209626, 82 29.871821510419252, 82 58 M82 58 C53.102421215735376 58, 24.20484243147075 58, 0 58 M82 58 C60.69060039464384 58, 39.38120078928768 58, 0 58 M0 58 C0 35.32809809129685, 0 12.656196182593703, 0 0 M0 58 C0 39.14703913349658, 0 20.29407826699316, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1513.5941405306648 325.37777863655765) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1513.5941405306648 325.37777863655765) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.189382644804416 22.489765781944783, -2.5644228530724638 26.954306117250006, 0 34 M-5.814342436536368 18.025225446639556 C-4.477115144868706 21.699227235174916, -3.1398878532010444 25.37322902371028, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1513.5941405306648 325.37777863655765) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.189382644804416 22.489765781944783, 2.5644228530724638 26.954306117250006, 0 34 M5.814342436536368 18.025225446639556 C4.477115144868706 21.699227235174916, 3.1398878532010444 25.37322902371028, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1010.5800559531045 329.37777863655765) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(911.0870972286061 1169.8355253211091) rotate(0 412 210.2500000000001)"><path d="M0 0 C218.95360033735636 0, 437.9072006747127 0, 824 0 M0 0 C209.57695072665814 0, 419.1539014533163 0, 824 0 M824 0 C824 105.35239861519078, 824 210.70479723038156, 824 420.5 M824 0 C824 150.35835648789072, 824 300.71671297578143, 824 420.5 M824 420.5 C503.3142883516848 420.5, 182.62857670336962 420.5, 0 420.5 M824 420.5 C525.6960808418692 420.5, 227.3921616837382 420.5, 0 420.5 M0 420.5 C0 301.00011278777384, 0 181.50022557554766, 0 0 M0 420.5 C0 277.7357025532517, 0 134.97140510650348, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1004.0870972286061 1239.8355253211091) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1003.0870972286061 1294.3355253211091) rotate(0 57 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock</text></g><g transform="translate(1015.4669520449852 1414.5492479492834) rotate(0 288.5 28.499999999999886)"><path d="M0 0 C191.88294327305627 0, 383.76588654611254 0, 576.9999999999999 0 M0 0 C153.981226036977 0, 307.962452073954 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 18.94759962530807, 576.9999999999999 37.89519925061614, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 16.581719528418034, 576.9999999999999 33.16343905683607, 576.9999999999999 57 M576.9999999999999 57 C420.6865388090721 57, 264.37307761814435 57, 0 57 M576.9999999999999 57 C439.98495214777057 57, 302.96990429554126 57, 0 57 M0 57 C0 38.82633978752419, 0 20.65267957504838, 0 0 M0 57 C0 39.23593317987397, 0 21.471866359747942, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1510.4669520449852 1414.5492479492834) rotate(0 41 28.999999999999886)"><path d="M0 0 C20.85595868881792 0, 41.71191737763584 0, 82 0 M0 0 C20.54434408191592 0, 41.08868816383184 0, 82 0 M82 0 C82 20.739083653502167, 82 41.478167307004334, 82 58 M82 0 C82 22.57253795582801, 82 45.14507591165602, 82 58 M82 58 C52.31441581193357 58, 22.62883162386715 58, 0 58 M82 58 C58.69681153055281 58, 35.39362306110561 58, 0 58 M0 58 C0 38.30837276596576, 0 18.616745531931514, 0 0 M0 58 C0 42.259131410531694, 0 26.518262821063395, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1549.4669520449852 1426.5492479492834) rotate(0 0 16.999999999999886)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1549.4669520449852 1426.5492479492834) rotate(0 0 16.999999999999886)"><path d="M-5.814342436536368 18.025225446639556 C-4.113869358350213 22.697236831346512, -2.4133962801640587 27.369248216053464, 0 34 M-5.814342436536368 18.025225446639556 C-4.337132291635999 22.083826963542613, -2.85992214673563 26.142428480445666, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1549.4669520449852 1426.5492479492834) rotate(0 0 16.999999999999886)"><path d="M5.814342436536368 18.025225446639556 C4.113869358350213 22.697236831346512, 2.4133962801640587 27.369248216053464, 0 34 M5.814342436536368 18.025225446639556 C4.337132291635999 22.083826963542613, 2.85992214673563 26.142428480445666, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1046.4528674674248 1430.5492479492832) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(1412.0870972286057 122.20172403965671) rotate(0 83.875 26.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.7173956115841835 4.121374692556808, 3.6962480472298154 1.8449653680377276, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.53886432158337 4.326751448274156, 3.339185467228188 2.2557188794724237, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.454961933751928 8.219545636907089, 6.776778486636245 4.398232819885173, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.266605675249538 9.586593133211604, 4.400065969631465 7.1323278124942, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.998957222391963 11.390026175559758, 12.126225887977766 4.341409880114616, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.228720528642109 12.276082134131954, 10.585752500478057 6.1135217972590095, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.703031796699608 15.52672496855091, 15.139772831663993 6.971733029243808, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.370490594102075 18.21000667656041, 10.474690426468928 12.33829644526281, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.3456726381002575 22.034832001457943, 14.686511338526742 13.59016307798198, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.026548904733454 20.101205047875514, 18.048263871793136 9.722909170817125, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.850964347063032 25.24983591541769, 19.958551580513742 13.622386888825588, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C9.221483318128827 25.973971004048572, 18.699589522645333 15.07065706608735, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C13.353319137344942 27.317849960621473, 26.5686589561485 12.11534054238004, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C12.917833983670329 27.818818323222533, 25.697688648799275 13.11727726758216, 36.877284942009794 0.25662288638767805 M-0.12347750552006431 48.9181433959388 C10.175775454041249 37.07020817328198, 20.475028413602562 25.222272950625165, 42.520359378862906 -0.13797931854138312 M-0.12347750552006431 48.9181433959388 C9.746254045955597 37.5643160313688, 19.61598559743126 26.210488666798796, 42.520359378862906 -0.13797931854138312 M0.9271837283995019 53.806508252569145 C18.728587858763337 33.32833533682499, 36.52999198912717 12.850162421080846, 47.50737478672552 0.22212805675232872 M0.9271837283995019 53.806508252569145 C16.271137215299515 36.155308919370725, 31.615090702199527 18.504109586172298, 47.50737478672552 0.22212805675232872 M5.914199136262113 54.16661562786285 C23.49673440300238 33.94022253815562, 41.07926966974265 13.713829448448386, 53.15044922357863 -0.1724741481767289 M5.914199136262113 54.16661562786285 C22.864289477153022 34.66776720015996, 39.81437981804393 15.168918772457062, 53.15044922357863 -0.1724741481767289 M11.557273573115229 53.772013422933796 C22.848484740685947 40.7829608164994, 34.13969590825667 27.79390821006499, 58.13746463144125 0.1876332271169865 M11.557273573115229 53.772013422933796 C27.778619477682486 35.111489571715715, 43.99996538224974 16.450965720497635, 58.13746463144125 0.1876332271169865 M16.544288980977836 54.13212079822751 C27.664794442473266 41.33944264299447, 38.78529990396869 28.546764487761436, 63.78053906829436 -0.20696897781207468 M16.544288980977836 54.13212079822751 C31.45706359913156 36.976936013496115, 46.36983821728528 19.82175122876471, 63.78053906829436 -0.20696897781207468 M22.18736341783096 53.73751859329845 C35.13162478449539 38.84684926227623, 48.075886151159835 23.956179931254013, 68.76755447615697 0.1531383974816336 M22.18736341783096 53.73751859329845 C33.69096689012845 40.504136589569505, 45.19457036242595 27.27075458584056, 68.76755447615697 0.1531383974816336 M27.174378825693566 54.09762596859217 C43.810054153418044 34.96047063879192, 60.44572948114252 15.82331530899166, 74.41062891301009 -0.2414638074474169 M27.174378825693566 54.09762596859217 C44.820114437100386 33.798529199055054, 62.46585004850721 13.499432429517938, 74.41062891301009 -0.2414638074474169 M32.817453262546685 53.703023763663104 C49.97269154179106 33.968179628871866, 67.12792982103542 14.23333549408062, 79.3976443208727 0.11864356784629138 M32.817453262546685 53.703023763663104 C49.16607414542128 34.89608679837052, 65.51469502829588 16.089149833077933, 79.3976443208727 0.11864356784629138 M37.80446867040929 54.06313113895682 C53.664216718620374 35.81857803780972, 69.52396476683145 17.574024936662624, 85.04071875772581 -0.27595863708276624 M37.80446867040929 54.06313113895682 C53.71031346445203 35.76554979772929, 69.61615825849475 17.467968456501765, 85.04071875772581 -0.27595863708276624 M43.447543107262405 53.668528934027755 C54.41543216278025 41.05141587065494, 65.38332121829811 28.43430280728213, 90.02773416558841 0.08414873821094204 M43.447543107262405 53.668528934027755 C60.61312426753425 33.92178667566286, 77.7787054278061 14.175044417297954, 90.02773416558841 0.08414873821094204 M48.43455851512502 54.02863630932147 C66.93170216944668 32.750106625561266, 85.42884582376834 11.471576941801061, 95.67080860244155 -0.3104534667181156 M48.43455851512502 54.02863630932147 C59.88172162803239 40.86018151192713, 71.32888474093976 27.69172671453279, 95.67080860244155 -0.3104534667181156 M54.07763295197813 53.63403410439241 C70.07482934877166 35.23136476541137, 86.07202574556518 16.828695426430336, 100.65782401030415 0.049653908575599814 M54.07763295197813 53.63403410439241 C67.6147605641235 38.0613501748612, 81.15188817626887 22.488666245329977, 100.65782401030415 0.049653908575599814 M59.064648359840746 53.99414147968613 C69.32977228979794 42.185467214455, 79.59489621975513 30.37679294922387, 106.30089844715727 -0.3449482963534578 M59.064648359840746 53.99414147968613 C71.3619643394091 39.84769768317661, 83.65928031897745 25.701253886667097, 106.30089844715727 -0.3449482963534578 M64.70772279669386 53.59953927475706 C83.26681476468622 32.24974620806945, 101.82590673267859 10.899953141381843, 111.28791385501988 0.015159078940250481 M64.70772279669386 53.59953927475706 C78.26906752069534 37.99899674483243, 91.83041224469684 22.398454214907797, 111.28791385501988 0.015159078940250481 M69.69473820455647 53.959646650050786 C83.79703187510646 37.73681354209728, 97.89932554565644 21.513980434143782, 116.2749292628825 0.375266454233973 M69.69473820455647 53.959646650050786 C88.0232089730638 32.87515292528618, 106.35167974157113 11.790659200521574, 116.2749292628825 0.375266454233973 M75.3378126414096 53.56504444512172 C86.93044099155067 40.22925103446481, 98.52306934169175 26.8934576238079, 121.9180036997356 -0.019335750695091747 M75.3378126414096 53.56504444512172 C85.89947527144335 41.415241427804126, 96.46113790147712 29.265438410486535, 121.9180036997356 -0.019335750695091747 M80.3248280492722 53.92515182041544 C91.03408820637159 41.605557270977584, 101.74334836347099 29.285962721539736, 126.9050191075982 0.34077162459862365 M80.3248280492722 53.92515182041544 C97.90171544146804 33.70525586715205, 115.4786028336639 13.485359913888658, 126.9050191075982 0.34077162459862365 M85.96790248612533 53.53054961548638 C103.91797705566042 32.88135092343194, 121.8680516251955 12.23215223137749, 132.54809354445132 -0.05383058033042687 M85.96790248612533 53.53054961548638 C101.66089334007648 35.477828722292706, 117.35388419402764 17.425107829099034, 132.54809354445132 -0.05383058033042687 M90.95491789398793 53.89065699078009 C108.32841377881171 33.90473620189459, 125.70190966363549 13.9188154130091, 137.53510895231395 0.3062767949632672 M90.95491789398793 53.89065699078009 C108.67003675500621 33.511743922899626, 126.38515561602449 13.132830855019158, 137.53510895231395 0.3062767949632672 M96.59799233084104 53.49605478585103 C107.80858362942544 40.59974472969279, 119.01917492800983 27.703434673534552, 143.17818338916706 -0.0883254099657762 M96.59799233084104 53.49605478585103 C112.61753868732575 35.0676747593386, 128.63708504381046 16.639294732826166, 143.17818338916706 -0.0883254099657762 M101.58500773870365 53.85616216114475 C120.14001076986757 32.511072878203706, 138.69501380103148 11.165983595262652, 148.16519879702966 0.2717819653279321 M101.58500773870365 53.85616216114475 C115.72587718093787 37.588952704161514, 129.8667466231721 21.321743247178283, 148.16519879702966 0.2717819653279321 M107.22808217555678 53.46155995621568 C120.72671425385484 37.93316007264153, 134.2253463321529 22.404760189067368, 153.8082732338828 -0.12282023960112554 M107.22808217555678 53.46155995621568 C123.66510766343758 34.55292512627111, 140.10213315131838 15.644290296326545, 153.8082732338828 -0.12282023960112554 M112.21509758341939 53.8216673315094 C129.19705003766725 34.28616573521338, 146.1790024919151 14.750664138917358, 158.7952886417454 0.23728713569258275 M112.21509758341939 53.8216673315094 C125.31139171728925 38.75610430823168, 138.4076858511591 23.690541284953955, 158.7952886417454 0.23728713569258275 M117.20211299128198 54.181774706803104 C133.7292754332167 35.169449172591754, 150.25643787515145 16.157123638380398, 164.4383630785985 -0.15731506923647487 M117.20211299128198 54.181774706803104 C133.81536100577716 35.0704190496007, 150.42860902027232 15.959063392398306, 164.4383630785985 -0.15731506923647487 M122.8451874281351 53.78717250187405 C133.05985181490902 42.036545300963766, 143.27451620168293 30.285918100053483, 168.7693194574706 0.9575018862800135 M122.8451874281351 53.78717250187405 C140.28991833384504 33.71930519547341, 157.73464923955493 13.651437889072767, 168.7693194574706 0.9575018862800135 M127.83220283599772 54.14727987716777 C139.23328157673114 41.03183908558893, 150.6343603174646 27.916398294010087, 168.50786263340916 7.355285903355906 M127.83220283599772 54.14727987716777 C140.48760654926735 39.58890326479492, 153.143010262537 25.030526652422076, 168.50786263340916 7.355285903355906 M133.47527727285083 53.752677672238704 C144.62918479252608 40.92157484453947, 155.7830923122013 28.09047201684024, 168.90246483833823 12.998360340209018 M133.47527727285083 53.752677672238704 C144.07958096052596 41.55382172935999, 154.6838846482011 29.354965786481277, 168.90246483833823 12.998360340209018 M138.46229268071346 54.11278504753242 C146.9895790886307 44.30326416453928, 155.51686549654798 34.493743281546145, 168.6410080142768 19.39614435728491 M138.46229268071346 54.11278504753242 C147.05382327377777 44.229359683598474, 155.64535386684204 34.34593431966452, 168.6410080142768 19.39614435728491 M144.10536711756657 53.718182842603355 C151.0098749112423 45.77545520934758, 157.91438270491798 37.83272757609179, 169.03561021920584 25.03921879413801 M144.10536711756657 53.718182842603355 C150.88646680478465 45.91741999621139, 157.66756649200275 38.11665714981944, 169.03561021920584 25.03921879413801 M149.09238252542917 54.07829021789707 C153.03681122133102 49.54074406159562, 156.98123991723287 45.003197905294165, 168.7741533951444 31.437002811213915 M149.09238252542917 54.07829021789707 C155.1688205945165 47.088147834783996, 161.2452586636038 40.098005451670915, 168.7741533951444 31.437002811213915 M154.73545696228229 53.68368801296802 C159.36574426257135 48.35715178635883, 163.9960315628604 43.03061555974963, 168.51269657108293 37.83478682828981 M154.73545696228229 53.68368801296802 C159.73862405330902 47.928202655403005, 164.74179114433576 42.172717297838, 168.51269657108293 37.83478682828981 M159.72247237014489 54.04379538826172 C161.7409058583154 51.721853271393485, 163.7593393464859 49.39991115452525, 168.907298776012 43.47786126514291 M159.72247237014489 54.04379538826172 C162.50026141307723 50.848314631347684, 165.27805045600957 47.65283387443364, 168.907298776012 43.47786126514291 M165.36554680699803 53.64919318333267 C166.0601001263845 52.850200987579996, 166.75465344577094 52.05120879182733, 168.64584195195056 49.875645282218805 M165.36554680699803 53.64919318333267 C166.5819819107356 52.24984467055833, 167.79841701447322 50.850496157783994, 168.64584195195056 49.875645282218805" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C46.183000835520254 0, 92.36600167104051 0, 167.7500000000001 0 M0 0 C36.24056315112396 0, 72.48112630224792 0, 167.7500000000001 0 M167.7500000000001 0 C167.7500000000001 18.842396629508585, 167.7500000000001 37.68479325901717, 167.7500000000001 53 M167.7500000000001 0 C167.7500000000001 16.167132444027814, 167.7500000000001 32.33426488805563, 167.7500000000001 53 M167.7500000000001 53 C102.15453257735359 53, 36.55906515470707 53, 0 53 M167.7500000000001 53 C129.04311365347831 53, 90.33622730695649 53, 0 53 M0 53 C0 38.442475789133454, 0 23.884951578266914, 0 0 M0 53 C0 40.237482499238105, 0 27.474964998476207, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1442.0870972286054 137.2017240396567) rotate(0 58 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">without stock</text></g><g transform="translate(1412.0870972286061 1209.701724039657) rotate(0 79.5 26.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.6763342954413682 4.168610333406417, 3.614125414944185 1.9394366497369457, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.1857802863737288 4.732928167473438, 2.633017396808906 3.068072317870987, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.349949477829196 9.4907170557863, 4.566753574790781 6.940575657643593, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.9945686047466356 8.749167577490088, 5.85599182862566 5.457476701051167, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.2124176579807555 12.294836441487785, 10.55314675915535 6.151030411970671, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.955555809340896 12.590322197183463, 10.039423061875631 6.742001923362029, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.076761123300264 17.397535372819554, 11.887231484865305 10.713353837781092, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.779310011319332 16.589345327514167, 13.292329260903442 9.096973747170317, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C10.242782297882494 18.70208857658965, 20.480730658091215 6.924676228245396, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C7.852918654132539 21.451312209925682, 15.701003370591305 12.423123494917462, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C11.518475193802992 23.331584118629685, 23.293573273993662 9.785883295249576, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C6.37849692552965 29.24445273225389, 13.013616737446979 21.61162052249799, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C14.51001094359821 25.987228249816308, 28.882042568655038 9.45409712076971, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C13.87108758374623 26.722225497625526, 27.604195848951075 10.924091616388147, 36.877284942009794 0.25662288638767805 M-0.12347750552006431 48.9181433959388 C9.59161271356494 37.742210534601575, 19.306702932649944 26.56627767326435, 42.520359378862906 -0.13797931854138312 M-0.12347750552006431 48.9181433959388 C10.07668795276822 37.18419530429695, 20.276853411056504 25.450247212655093, 42.520359378862906 -0.13797931854138312 M0.9271837283995019 53.806508252569145 C19.295938194460234 32.67567343481305, 37.66469266052097 11.54483861705696, 47.50737478672552 0.22212805675232872 M0.9271837283995019 53.806508252569145 C15.31044142937061 37.26046300045369, 29.693699130341717 20.714417748338235, 47.50737478672552 0.22212805675232872 M5.914199136262113 54.16661562786285 C17.356716674322968 41.00350495300532, 28.79923421238382 27.840394278147787, 53.15044922357863 -0.1724741481767289 M5.914199136262113 54.16661562786285 C16.72681856170508 41.72811984152908, 27.53943798714805 29.289624055195297, 53.15044922357863 -0.1724741481767289 M11.557273573115229 53.772013422933796 C27.133354011702025 35.853782578050385, 42.709434450288825 17.935551733166967, 58.13746463144125 0.1876332271169865 M11.557273573115229 53.772013422933796 C28.228403248938907 34.59407253118161, 44.89953292476258 15.416131639429423, 58.13746463144125 0.1876332271169865 M16.544288980977836 54.13212079822751 C31.813370743359187 36.567051531509506, 47.082452505740534 19.001982264791508, 63.78053906829436 -0.20696897781207468 M16.544288980977836 54.13212079822751 C27.78688901826427 41.198988900311456, 39.0294890555507 28.2658570023954, 63.78053906829436 -0.20696897781207468 M22.18736341783096 53.73751859329845 C37.50897302708171 36.11202295104255, 52.83058263633245 18.48652730878665, 68.76755447615697 0.1531383974816336 M22.18736341783096 53.73751859329845 C39.292479008292844 34.06033401616755, 56.39759459875472 14.383149439036657, 68.76755447615697 0.1531383974816336 M27.174378825693566 54.09762596859217 C37.44142796809184 42.286736999791636, 47.70847711049011 30.4758480309911, 74.41062891301009 -0.2414638074474169 M27.174378825693566 54.09762596859217 C39.98323217008107 39.36272574848162, 52.79208551446856 24.627825528371073, 74.41062891301009 -0.2414638074474169 M32.817453262546685 53.703023763663104 C50.442127839427194 33.42815494286845, 68.0668024163077 13.153286122073801, 79.3976443208727 0.11864356784629138 M32.817453262546685 53.703023763663104 C45.6266603288556 38.96771663302921, 58.435867395164514 24.232409502395328, 79.3976443208727 0.11864356784629138 M37.80446867040929 54.06313113895682 C47.43497861338879 42.9844967551254, 57.065488556368294 31.905862371293974, 85.04071875772581 -0.27595863708276624 M37.80446867040929 54.06313113895682 C55.52889401123652 33.673512190721695, 73.25331935206376 13.283893242486577, 85.04071875772581 -0.27595863708276624 M43.447543107262405 53.668528934027755 C55.83551580208621 39.417796516385934, 68.22348849691002 25.167064098744106, 90.02773416558841 0.08414873821094204 M43.447543107262405 53.668528934027755 C53.30706930107239 42.32644149050074, 63.166595494882365 30.98435404697373, 90.02773416558841 0.08414873821094204 M48.43455851512502 54.02863630932147 C67.0908023826447 32.567082766716126, 85.74704625016437 11.10552922411079, 95.67080860244155 -0.3104534667181156 M48.43455851512502 54.02863630932147 C64.45882481017543 35.59482661459903, 80.48309110522584 17.161016919876587, 95.67080860244155 -0.3104534667181156 M54.07763295197813 53.63403410439241 C66.08133276375455 39.82535707116004, 78.08503257553096 26.016680037927674, 100.65782401030415 0.049653908575599814 M54.07763295197813 53.63403410439241 C72.70393937857786 32.206919648014434, 91.33024580517758 10.779805191636449, 100.65782401030415 0.049653908575599814 M59.064648359840746 53.99414147968613 C70.1902433741227 41.19560846372059, 81.31583838840466 28.397075447755043, 106.30089844715727 -0.3449482963534578 M59.064648359840746 53.99414147968613 C77.69525417082781 32.562081147338446, 96.32585998181489 11.130020814990765, 106.30089844715727 -0.3449482963534578 M64.70772279669386 53.59953927475706 C74.13910111598257 42.74997961969814, 83.5704794352713 31.90041996463922, 111.28791385501988 0.015159078940250481 M64.70772279669386 53.59953927475706 C76.55944342570267 39.96569429193582, 88.41116405471146 26.33184930911458, 111.28791385501988 0.015159078940250481 M69.69473820455647 53.959646650050786 C79.16741721515874 43.06257598450837, 88.64009622576101 32.16550531896595, 116.2749292628825 0.375266454233973 M69.69473820455647 53.959646650050786 C84.3289502908371 37.1249114014217, 98.96316237711773 20.290176152792604, 116.2749292628825 0.375266454233973 M75.3378126414096 53.56504444512172 C86.14180353400351 41.13647465037817, 96.94579442659742 28.70790485563462, 121.9180036997356 -0.019335750695091747 M75.3378126414096 53.56504444512172 C88.49005672829291 38.43511836351181, 101.64230081517624 23.305192281901906, 121.9180036997356 -0.019335750695091747 M80.3248280492722 53.92515182041544 C94.71918853078526 37.36633428033221, 109.11354901229831 20.807516740248992, 126.9050191075982 0.34077162459862365 M80.3248280492722 53.92515182041544 C94.9563456434856 37.09351623033399, 109.587863237699 20.261880640252535, 126.9050191075982 0.34077162459862365 M85.96790248612533 53.53054961548638 C98.10578744757035 39.56751022535702, 110.24367240901537 25.604470835227655, 132.54809354445132 -0.05383058033042687 M85.96790248612533 53.53054961548638 C103.80023219455592 33.01680089175919, 121.63256190298651 12.503052168031992, 132.54809354445132 -0.05383058033042687 M90.95491789398793 53.89065699078009 C109.39582213972629 32.67682334589489, 127.83672638546466 11.462989701009697, 137.53510895231395 0.3062767949632672 M90.95491789398793 53.89065699078009 C107.35244279276296 35.027462390609415, 123.74996769153798 16.164267790438743, 137.53510895231395 0.3062767949632672 M96.59799233084104 53.49605478585103 C109.90482235845376 38.18829792182548, 123.21165238606649 22.88054105779993, 143.17818338916706 -0.0883254099657762 M96.59799233084104 53.49605478585103 C112.77249588422322 34.88941689555622, 128.9469994376054 16.282779005261403, 143.17818338916706 -0.0883254099657762 M101.58500773870365 53.85616216114475 C112.7724984269507 40.98642631730611, 123.95998911519776 28.11669047346747, 148.16519879702966 0.2717819653279321 M101.58500773870365 53.85616216114475 C112.20006631530816 41.644934133818495, 122.81512489191269 29.433706106492235, 148.16519879702966 0.2717819653279321 M107.22808217555678 53.46155995621568 C120.61208073460944 38.06503085158998, 133.9960792936621 22.66850174696429, 153.8082732338828 -0.12282023960112554 M107.22808217555678 53.46155995621568 C123.95754253771364 34.2165172857343, 140.6870028998705 14.971474615252909, 153.8082732338828 -0.12282023960112554 M112.21509758341939 53.8216673315094 C123.90092493152434 40.3786607380102, 135.58675227962928 26.935654144510988, 158.7952886417454 0.23728713569258275 M112.21509758341939 53.8216673315094 C125.43723812719647 38.611334574112234, 138.65937867097352 23.401001816715073, 158.7952886417454 0.23728713569258275 M117.20211299128198 54.181774706803104 C130.99364885222906 38.316427565314, 144.7851847131761 22.4510804238249, 159.18989084667447 5.880361572545695 M117.20211299128198 54.181774706803104 C129.560359585114 39.96523825661208, 141.91860617894605 25.74870180642106, 159.18989084667447 5.880361572545695 M122.8451874281351 53.78717250187405 C136.18922449133777 38.43661383957947, 149.53326155454045 23.086055177284887, 158.928434022613 12.278145589621587 M122.8451874281351 53.78717250187405 C133.82487607659277 41.15648555956519, 144.80456472505045 28.525798617256328, 158.928434022613 12.278145589621587 M127.83220283599772 54.14727987716777 C135.85614577605108 44.916789417586315, 143.88008871610447 35.686298958004855, 159.32303622754208 17.921220026474714 M127.83220283599772 54.14727987716777 C134.7825839478684 46.15178102792611, 141.73296505973906 38.15628217868446, 159.32303622754208 17.921220026474714 M133.47527727285083 53.752677672238704 C142.9837100332666 42.81447702247116, 152.49214279368235 31.876276372703618, 159.0615794034806 24.319004043550592 M133.47527727285083 53.752677672238704 C138.9946597866402 47.40335440100733, 144.51404230042957 41.05403112977595, 159.0615794034806 24.319004043550592 M138.46229268071346 54.11278504753242 C146.49336568920856 44.87409238247427, 154.52443869770366 35.63539971741612, 158.80012257941917 30.716788060626484 M138.46229268071346 54.11278504753242 C144.5317253147659 47.13070149556227, 150.60115794881838 40.14861794359212, 158.80012257941917 30.716788060626484 M144.10536711756657 53.718182842603355 C149.59412307147716 47.4040913982784, 155.08287902538774 41.08999995395345, 159.19472478434824 36.3598624974796 M144.10536711756657 53.718182842603355 C149.94297087583203 47.00278790522013, 155.7805746340975 40.28739296783691, 159.19472478434824 36.3598624974796 M149.09238252542917 54.07829021789707 C151.6516127612986 51.13423260774811, 154.21084299716802 48.19017499759916, 158.9332679602868 42.75764651455549 M149.09238252542917 54.07829021789707 C151.84593396586163 50.91069163316567, 154.5994854062941 47.74309304843426, 158.9332679602868 42.75764651455549 M154.73545696228229 53.68368801296802 C156.05098012122784 52.17035173194947, 157.36650328017342 50.657015450930906, 159.32787016521584 48.400720951408616 M154.73545696228229 53.68368801296802 C156.24479352007694 51.947394921017356, 157.75413007787157 50.211101829066685, 159.32787016521584 48.400720951408616" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C42.85518687916917 0, 85.71037375833833 0, 158.9999999999999 0 M0 0 C57.662035526800864 0, 115.32407105360173 0, 158.9999999999999 0 M158.9999999999999 0 C158.9999999999999 16.40436417544261, 158.9999999999999 32.80872835088522, 158.9999999999999 53 M158.9999999999999 0 C158.9999999999999 13.331009528692814, 158.9999999999999 26.662019057385628, 158.9999999999999 53 M158.9999999999999 53 C107.5467047874815 53, 56.093409574963104 53, 0 53 M158.9999999999999 53 C99.94139154357828 53, 40.88278308715668 53, 0 53 M0 53 C0 37.42691702758893, 0 21.853834055177863, 0 0 M0 53 C0 38.142077192012216, 0 23.284154384024436, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1442.0870972286061 1224.701724039657) rotate(0 58 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">without stock</text></g><g transform="translate(63.87281151432046 18.772654820766093) rotate(0 239 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># create product without stock</text></g><g transform="translate(1402.7656686571772 674.1650017457355) rotate(0 79.5 26.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C0.9590294310660872 4.993775187729691, 2.1795156861936227 3.5897663583834936, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.6496073208572388 4.199356200588598, 3.560671465775926 2.000928384101308, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.2922858811843985 9.55705143561321, 4.451426381501186 7.073244417297413, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.0914745139615354 8.637690081056238, 6.04980364705546 5.234521708183471, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C3.3443144650777965 14.443843336032044, 6.816940373349432 10.449044201059191, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.01479641440859 12.522173676688933, 10.157904272011018 6.605704882372967, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.313581141842409 17.125105105291176, 12.360871521949596 10.168493302724338, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C4.782279493601126 18.886666143353406, 9.29826822546703 13.6916153788488, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C10.401758796254919 18.519207035371387, 20.798683654836065 6.5589131458088765, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.456501407525241 19.606601272058242, 18.90816887737671 8.733701619182579, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.005901199992277 26.221969861914648, 18.26842528637223 15.566654781819501, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.555575484081738 27.890378745678326, 15.367773854551155 18.903472549346855, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.642047587190632 28.136074280874105, 25.14611585583988 13.751789182885304, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C10.608185768210685 30.47576466208169, 21.078392217879987 18.431169945300475, 36.877284942009794 0.25662288638767805 M-0.12347750552006431 48.9181433959388 C14.406384704375723 32.203448948400236, 28.93624691427151 15.488754500861681, 42.520359378862906 -0.13797931854138312 M-0.12347750552006431 48.9181433959388 C9.233749970811559 38.15388452798652, 18.59097744714318 27.389625660034245, 42.520359378862906 -0.13797931854138312 M0.9271837283995019 53.806508252569145 C18.00889372145183 34.15624873525034, 35.090603714504155 14.505989217931528, 47.50737478672552 0.22212805675232872 M0.9271837283995019 53.806508252569145 C17.862422907835555 34.324744131814455, 34.797662087271604 14.842980011059765, 47.50737478672552 0.22212805675232872 M5.914199136262113 54.16661562786285 C23.081586803698528 34.41779522072842, 40.24897447113494 14.668974813593984, 53.15044922357863 -0.1724741481767289 M5.914199136262113 54.16661562786285 C24.35590728671535 32.951857196391245, 42.79761543716859 11.737098764919644, 53.15044922357863 -0.1724741481767289 M11.557273573115229 53.772013422933796 C21.88915933163768 41.886538459312895, 32.22104509016013 30.001063495691994, 58.13746463144125 0.1876332271169865 M11.557273573115229 53.772013422933796 C20.900653777763026 43.023684018852805, 30.244033982410823 32.27535461477181, 58.13746463144125 0.1876332271169865 M16.544288980977836 54.13212079822751 C33.39244654091159 34.750532621397895, 50.24060410084536 15.368944444568285, 63.78053906829436 -0.20696897781207468 M16.544288980977836 54.13212079822751 C30.442759793989424 38.14375906625568, 44.34123060700101 22.15539733428385, 63.78053906829436 -0.20696897781207468 M22.18736341783096 53.73751859329845 C40.118140206275996 33.110519458939486, 58.048916994721026 12.483520324580525, 68.76755447615697 0.1531383974816336 M22.18736341783096 53.73751859329845 C33.500898195362026 40.722785611230535, 44.81443297289309 27.708052629162626, 68.76755447615697 0.1531383974816336 M27.174378825693566 54.09762596859217 C41.51903694549283 37.59598445518877, 55.86369506529209 21.094342941785364, 74.41062891301009 -0.2414638074474169 M27.174378825693566 54.09762596859217 C39.51854700250924 39.89728488456045, 51.86271517932491 25.696943800528732, 74.41062891301009 -0.2414638074474169 M32.817453262546685 53.703023763663104 C49.97093502899642 33.970200265697684, 67.12441679544617 14.237376767732272, 79.3976443208727 0.11864356784629138 M32.817453262546685 53.703023763663104 C45.83650989757114 38.726312318909905, 58.85556653259559 23.749600874156705, 79.3976443208727 0.11864356784629138 M37.80446867040929 54.06313113895682 C50.58084181837462 39.36559511067067, 63.357214966339946 24.66805908238452, 85.04071875772581 -0.27595863708276624 M37.80446867040929 54.06313113895682 C52.652311974553655 36.982641286501135, 67.50015527869802 19.90215143404545, 85.04071875772581 -0.27595863708276624 M43.447543107262405 53.668528934027755 C53.54115053169298 42.0571618380714, 63.63475795612354 30.445794742115048, 90.02773416558841 0.08414873821094204 M43.447543107262405 53.668528934027755 C59.36198264326729 35.36106047317875, 75.27642217927217 17.053592012329744, 90.02773416558841 0.08414873821094204 M48.43455851512502 54.02863630932147 C62.885471912113616 37.40476208193895, 77.33638530910221 20.780887854556426, 95.67080860244155 -0.3104534667181156 M48.43455851512502 54.02863630932147 C61.09681455113109 39.46237700135656, 73.75907058713716 24.896117693391645, 95.67080860244155 -0.3104534667181156 M54.07763295197813 53.63403410439241 C63.76684417702619 42.48787162020596, 73.45605540207424 31.341709136019503, 100.65782401030415 0.049653908575599814 M54.07763295197813 53.63403410439241 C63.522854684102754 42.76854942455894, 72.96807641622738 31.903064744725476, 100.65782401030415 0.049653908575599814 M59.064648359840746 53.99414147968613 C75.20503992215906 35.42674494621858, 91.34543148447739 16.859348412751032, 106.30089844715727 -0.3449482963534578 M59.064648359840746 53.99414147968613 C69.90665639563773 41.52183796446898, 80.74866443143469 29.049534449251826, 106.30089844715727 -0.3449482963534578 M64.70772279669386 53.59953927475706 C81.14042862926009 34.695873639816455, 97.57313446182631 15.792208004875853, 111.28791385501988 0.015159078940250481 M64.70772279669386 53.59953927475706 C77.6778807169985 38.679079366571244, 90.64803863730316 23.758619458385425, 111.28791385501988 0.015159078940250481 M69.69473820455647 53.959646650050786 C83.30804872765351 38.299324306590634, 96.92135925075056 22.639001963130486, 116.2749292628825 0.375266454233973 M69.69473820455647 53.959646650050786 C83.57195224873057 37.9957380333892, 97.44916629290466 22.031829416727625, 116.2749292628825 0.375266454233973 M75.3378126414096 53.56504444512172 C89.05065264842965 37.79022652776953, 102.76349265544968 22.015408610417342, 121.9180036997356 -0.019335750695091747 M75.3378126414096 53.56504444512172 C90.54478350832046 36.07142559029717, 105.75175437523133 18.577806735472613, 121.9180036997356 -0.019335750695091747 M80.3248280492722 53.92515182041544 C96.85611522661567 34.908081320901715, 113.38740240395916 15.891010821388, 126.9050191075982 0.34077162459862365 M80.3248280492722 53.92515182041544 C92.6511138377993 39.745382070916534, 104.97739962632642 25.56561232141764, 126.9050191075982 0.34077162459862365 M85.96790248612533 53.53054961548638 C95.70477304283412 42.32956134184813, 105.44164359954293 31.12857306820987, 132.54809354445132 -0.05383058033042687 M85.96790248612533 53.53054961548638 C102.12242584637285 34.946896308143835, 118.27694920662037 16.363243000801297, 132.54809354445132 -0.05383058033042687 M90.95491789398793 53.89065699078009 C105.38220157030383 37.29396564753089, 119.80948524661974 20.69727430428169, 137.53510895231395 0.3062767949632672 M90.95491789398793 53.89065699078009 C105.5442524583211 37.107547425593715, 120.13358702265427 20.324437860407343, 137.53510895231395 0.3062767949632672 M96.59799233084104 53.49605478585103 C110.83845913987416 37.11427166465995, 125.07892594890727 20.732488543468875, 143.17818338916706 -0.0883254099657762 M96.59799233084104 53.49605478585103 C109.71675322390044 38.40464671258903, 122.83551411695983 23.31323863932702, 143.17818338916706 -0.0883254099657762 M101.58500773870365 53.85616216114475 C111.81690229869126 42.085713913318486, 122.04879685867887 30.315265665492223, 148.16519879702966 0.2717819653279321 M101.58500773870365 53.85616216114475 C116.46409779057373 36.739727037277, 131.3431878424438 19.623291913409247, 148.16519879702966 0.2717819653279321 M107.22808217555678 53.46155995621568 C122.42569725688868 35.97870370154591, 137.6233123382206 18.495847446876148, 153.8082732338828 -0.12282023960112554 M107.22808217555678 53.46155995621568 C121.91485115558635 36.56636491743614, 136.60162013561592 19.6711698786566, 153.8082732338828 -0.12282023960112554 M112.21509758341939 53.8216673315094 C127.60625312960151 36.116168240557066, 142.99740867578362 18.41066914960473, 158.7952886417454 0.23728713569258275 M112.21509758341939 53.8216673315094 C128.57550998385707 35.00116577693906, 144.93592238429474 16.180664222368712, 158.7952886417454 0.23728713569258275 M117.20211299128198 54.181774706803104 C127.5022841883169 42.33278317276633, 137.80245538535183 30.483791638729556, 159.18989084667447 5.880361572545695 M117.20211299128198 54.181774706803104 C132.9196815579222 36.10078038941022, 148.6372501245624 18.019786072017332, 159.18989084667447 5.880361572545695 M122.8451874281351 53.78717250187405 C136.21592740610657 38.40589565004875, 149.586667384078 23.02461879822345, 158.928434022613 12.278145589621587 M122.8451874281351 53.78717250187405 C136.1507215140126 38.48090644827832, 149.45625559989006 23.17464039468259, 158.928434022613 12.278145589621587 M127.83220283599772 54.14727987716777 C139.49335374182792 40.73266028326407, 151.1545046476581 27.31804068936036, 159.32303622754208 17.921220026474714 M127.83220283599772 54.14727987716777 C134.99547975648431 45.90687241566457, 142.15875667697094 37.66646495416138, 159.32303622754208 17.921220026474714 M133.47527727285083 53.752677672238704 C139.90777337582978 46.35293737579948, 146.3402694788087 38.95319707936025, 159.0615794034806 24.319004043550592 M133.47527727285083 53.752677672238704 C141.77271026596918 44.2075728959221, 150.0701432590875 34.6624681196055, 159.0615794034806 24.319004043550592 M138.46229268071346 54.11278504753242 C143.373776160389 48.46276961992574, 148.2852596400646 42.81275419231905, 158.80012257941917 30.716788060626484 M138.46229268071346 54.11278504753242 C146.30494626051942 45.090844140544895, 154.14759984032534 36.06890323355738, 158.80012257941917 30.716788060626484 M144.10536711756657 53.718182842603355 C149.64530062640395 47.34521835593179, 155.18523413524136 40.97225386926022, 159.19472478434824 36.3598624974796 M144.10536711756657 53.718182842603355 C148.06447503732974 49.16375017092931, 152.0235829570929 44.60931749925527, 159.19472478434824 36.3598624974796 M149.09238252542917 54.07829021789707 C152.35916932678035 50.32028188849612, 155.62595612813152 46.56227355909516, 158.9332679602868 42.75764651455549 M149.09238252542917 54.07829021789707 C151.07904965559007 51.79289111569554, 153.065716785751 49.507492013494, 158.9332679602868 42.75764651455549 M154.73545696228229 53.68368801296802 C156.08904198267132 52.12656656902486, 157.44262700306038 50.5694451250817, 159.32787016521584 48.400720951408616 M154.73545696228229 53.68368801296802 C155.7443932571591 52.523039574443125, 156.7533295520359 51.36239113591823, 159.32787016521584 48.400720951408616" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C57.12026438964527 0, 114.24052877929054 0, 158.9999999999999 0 M0 0 C46.70137843983245 0, 93.4027568796649 0, 158.9999999999999 0 M158.9999999999999 0 C158.9999999999999 19.07754825735465, 158.9999999999999 38.1550965147093, 158.9999999999999 53 M158.9999999999999 0 C158.9999999999999 20.238926912005994, 158.9999999999999 40.47785382401199, 158.9999999999999 53 M158.9999999999999 53 C100.32112446175881 53, 41.64224892351773 53, 0 53 M158.9999999999999 53 C120.11725420458234 53, 81.23450840916479 53, 0 53 M0 53 C0 37.942913892026986, 0 22.885827784053976, 0 0 M0 53 C0 32.091631084028634, 0 11.183262168057261, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1432.7656686571772 689.1650017457355) rotate(0 58 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">without stock</text></g><g transform="translate(955.0750205101397 10) rotate(0 363 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># creating product with stock: unkown restock</text></g><g transform="translate(949.3607347958546 1107.5000000000002) rotate(0 388 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># creating product with stock: with restock never </text></g><g transform="translate(944.3607347958537 557.9401274238317) rotate(0 328 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># creating product next restock: with date</text></g><g transform="translate(1164.0591474942667 1281.4486080177874) rotate(0 49.370370370370324 25)"><path d="M0 0 C23.45212747769911 0, 46.90425495539822 0, 98.74074074074065 0 M0 0 C20.73102883723459 0, 41.46205767446918 0, 98.74074074074065 0 M98.74074074074065 0 C98.74074074074065 15.249146842397751, 98.74074074074065 30.498293684795502, 98.74074074074065 50 M98.74074074074065 0 C98.74074074074065 16.16197635885328, 98.74074074074065 32.32395271770656, 98.74074074074065 50 M98.74074074074065 50 C60.310699718686934 50, 21.88065869663322 50, 0 50 M98.74074074074065 50 C76.67712927301304 50, 54.613517805285426 50, 0 50 M0 50 C0 36.00903776939958, 0 22.018075538799163, 0 0 M0 50 C0 36.17254475597292, 0 22.345089511945844, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1199.6517400868593 1294.9486080177874) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1164.5591474942662 741.9486080177871) rotate(0 49.370370370370324 25)"><path d="M0 0 C36.75340074836791 0, 73.50680149673582 0, 98.74074074074065 0 M0 0 C27.905231700830686 0, 55.81046340166137 0, 98.74074074074065 0 M98.74074074074065 0 C98.74074074074065 10.219821208156645, 98.74074074074065 20.43964241631329, 98.74074074074065 50 M98.74074074074065 0 C98.74074074074065 19.624865702353418, 98.74074074074065 39.249731404706836, 98.74074074074065 50 M98.74074074074065 50 C63.61054871782516 50, 28.480356694909673 50, 0 50 M98.74074074074065 50 C59.59350222807924 50, 20.446263715417828 50, 0 50 M0 50 C0 37.69948286470026, 0 25.398965729400512, 0 0 M0 50 C0 35.27201617602259, 0 20.54403235204518, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1200.1517400868588 755.4486080177871) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1814.0809814929971 1842.1071341952047) rotate(0 412 266.56944444444457)"><path d="M0 0 C274.16651551425457 0, 548.3330310285091 0, 824 0 M0 0 C297.1355347424746 0, 594.2710694849492 0, 824 0 M824 0 C824 116.17392364551009, 824 232.34784729102017, 824 533.1388888888891 M824 0 C824 179.31330792878038, 824 358.62661585756075, 824 533.1388888888891 M824 533.1388888888891 C567.5864898949861 533.1388888888891, 311.17297978997226 533.1388888888891, 0 533.1388888888891 M824 533.1388888888891 C609.0388737887145 533.1388888888891, 394.0777475774288 533.1388888888891, 0 533.1388888888891 M0 533.1388888888891 C0 356.3190921438136, 0 179.49929539873796, 0 0 M0 533.1388888888891 C0 424.3866610642214, 0 315.6344332395537, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1907.0809814929967 1912.1071341952047) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1941.4976481596632 1976.507927845998) rotate(0 31 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current</text></g><g transform="translate(1892.9034923155082 2261.8565711090932) rotate(0 288.5 28.5)"><path d="M0 0 C210.84164893589912 0, 421.68329787179823 0, 576.9999999999999 0 M0 0 C226.9310409609228 0, 453.8620819218456 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 18.367664065584542, 576.9999999999999 36.735328131169084, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 15.9439185295254, 576.9999999999999 31.8878370590508, 576.9999999999999 57 M576.9999999999999 57 C425.9349190238862 57, 274.8698380477726 57, 0 57 M576.9999999999999 57 C374.58122126422813 57, 172.16244252845638 57, 0 57 M0 57 C0 42.26120472885668, 0 27.522409457713366, 0 0 M0 57 C0 40.81852394007146, 0 24.637047880142923, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2387.903492315508 2261.8565711090932) rotate(0 41 29)"><path d="M0 0 C25.642520541697746 0, 51.28504108339549 0, 82 0 M0 0 C26.927581327408557 0, 53.855162654817114 0, 82 0 M82 0 C82 13.545655617862941, 82 27.091311235725883, 82 58 M82 0 C82 15.152590277045967, 82 30.305180554091933, 82 58 M82 58 C58.220881395787 58, 34.441762791574 58, 0 58 M82 58 C60.07624772265554 58, 38.15249544531107 58, 0 58 M0 58 C0 41.12247482761741, 0 24.244949655234812, 0 0 M0 58 C0 44.12997278943658, 0 30.259945578873158, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2426.903492315508 2273.8565711090932) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2426.903492315508 2273.8565711090932) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.643740285856778 21.241428422796748, -3.4731381351771877 24.45763139895394, 0 34 M-5.814342436536368 18.025225446639556 C-4.192122791573714 22.482237290570144, -2.5699031466110593 26.939249134500734, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2426.903492315508 2273.8565711090932) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.643740285856778 21.241428422796748, 3.4731381351771877 24.45763139895394, 0 34 M5.814342436536368 18.025225446639556 C4.192122791573714 22.482237290570144, 2.5699031466110593 26.939249134500734, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1923.8894077379477 2277.8565711090932) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(2122.486963969733 1971.636797748869) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1943.9162053758716 2213.340383000729) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(2298.630491090157 2204.5546687150145) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.2611187238108525 4.646261209196374, 2.7836942716831534 2.8947384013168596, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.684746007607977 4.158933765479314, 3.6309488392774023 1.920083513882739, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.739643965731985 7.892056421213751, 7.346142550596359 3.743254388498496, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.495653567887535 9.3231036737803, 4.858161754907459 6.605348893631596, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.036540063134039 13.647528877358473, 8.201391569461917 8.85641528371205, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.724114151797874 12.856565368135819, 9.576539746789587 7.274488265266742, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.662661821618544 17.873902126926485, 11.059032881501865 11.666087345994956, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C8.512157091179688 14.59593279229763, 16.758023420624156 5.110148676737246, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C5.41527874113689 24.255496154016846, 10.825723544600008 18.031491383099794, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.2384643851211 19.857424174236503, 18.472094832568427 9.235347423539103, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C6.6630249272410005 28.91714070811543, 13.582672740869679 20.956996474221064, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C6.85865783887235 28.692090787162066, 13.973938564132379 20.506896632314337, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.757833347695543 28.0028779999832, 25.377687376849703 13.485396621103487, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C9.781914135509359 31.42628144412422, 19.425848952477335 20.332203509385533, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C15.385556051510763 31.07704116540005, 29.582471550560577 14.745358095306848, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C10.468500563770826 36.73346645504877, 19.748360575080703 26.058208674604284, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C23.047288933632743 28.36023806006382, 39.26286287795142 9.706354089563447, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C15.250952012991574 37.328897745620154, 23.67018903666908 27.643673460676105, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C22.597903379623546 34.974209350880756, 33.37707636207041 22.574189295903615, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C25.14197529521831 32.047589393482305, 38.46522019325994 16.72094938110671, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C33.226246254203865 28.844711833674296, 48.99068767437794 10.709796466419746, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C32.04277050544074 30.20614494576362, 46.62373617685169 13.432662690598391, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C34.020553773428105 34.02797790476389, 45.592287304963804 20.71622123330521, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C32.413442208827774 35.87674827555963, 42.37806417576315 24.413761974896698, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C37.03616338094111 36.65592823059203, 45.9804320831367 26.36672408989057, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C40.145889213497476 33.07859787770013, 52.199883748249434 19.212063384106777, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C41.38525638927088 37.749881380566954, 49.691602691933625 28.19452301454669, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C45.61547031299049 32.88357692693349, 58.152030539372845 18.461914107279764, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C49.12053993799855 34.948467912037486, 59.51909535253584 22.986298282416808, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C45.893685857880584 38.66053890051741, 53.065387192299916 30.41044025937665, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C53.860181013368766 35.593146904187826, 64.01136209541367 23.91554889142378, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.905471129667134 37.841783399680835, 60.10194232801041 28.412821882409805, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C56.96246633493155 38.12138822689979, 64.57285830168613 29.366633741776763, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C55.967382671880934 39.26610103541496, 62.5826909755849 31.656059358807116, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C59.720999826929734 41.045070794267275, 65.10290987781988 34.85389150121803, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.65157792303411 41.12493155927794, 64.96406607002864 35.01361303123936, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C63.66028229464649 42.610457043109555, 67.33840037640029 38.37926620383165, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.12857415865852 42.07174887739148, 68.27498410442436 37.30184987239548, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.63395468548822 44.13628221086212, 70.29872975022114 41.070809164043055, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.26606697808306 44.55948860686598, 69.56295433541081 41.91722195605079, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.80251865776677 46.58827866771744, 71.6488422869156 45.614694702459985, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.0006149851761 46.36039491107923, 72.04503494173426 45.15892718918357, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C16.193762657046317 0, 32.387525314092635 0, 72 0 M0 0 C17.504456117749214 0, 35.00891223549843 0, 72 0 M72 0 C72 18.31221368983388, 72 36.62442737966776, 72 46 M72 0 C72 9.369684558361769, 72 18.739369116723537, 72 46 M72 46 C56.27316487133503 46, 40.54632974267005 46, 0 46 M72 46 C54.11647951304913 46, 36.23295902609825 46, 0 46 M0 46 C0 29.97982518002391, 0 13.959650360047817, 0 0 M0 46 C0 28.27324437573552, 0 10.546488751471038, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(25.09190430596891 1844.8671778767057) rotate(0 412 84.5)"><path d="M0 0 C167.83794610202312 0, 335.67589220404625 0, 824 0 M0 0 C257.2014637738466 0, 514.4029275476933 0, 824 0 M824 0 C824 50.912549302354456, 824 101.82509860470891, 824 169 M824 0 C824 65.41591384075582, 824 130.83182768151164, 824 169 M824 169 C620.7830524712801 169, 417.5661049425602 169, 0 169 M824 169 C523.9873734682799 169, 223.97474693655965 169, 0 169 M0 169 C0 129.2093227174133, 0 89.41864543482662, 0 0 M0 169 C0 116.37540726996957, 0 63.75081453993916, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(118.09190430596891 1914.8671778767057) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(210.0919043059689 1900.1206990210958) rotate(0 57.125 26.5)"><path d="M0 0 C35.751818736735736 0, 71.50363747347147 0, 114.25 0 M0 0 C23.568781670089813 0, 47.137563340179625 0, 114.25 0 M114.25 0 C114.25 13.111394657567145, 114.25 26.22278931513429, 114.25 53 M114.25 0 C114.25 20.921167401596904, 114.25 41.84233480319381, 114.25 53 M114.25 53 C85.3933772421442 53, 56.536754484288394 53, 0 53 M114.25 53 C70.957347367052 53, 27.664694734103975 53, 0 53 M0 53 C0 40.39483490996063, 0 27.789669819921254, 0 0 M0 53 C0 38.70905513353645, 0 24.41811026707291, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(238.0919043059689 1915.1206990210958) rotate(0 29 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">infinite</text></g><g transform="translate(41.46471582028926 1777.6935835477643) rotate(0 328 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: product without stock</text></g><g transform="translate(1940.5139130676002 2023.2022267517714) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(2127.00322887767 2024.3310966546414) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1940.5139130676002 2074.2022267517714) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(2127.00322887767 2081.3310966546414) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1940.5139130676002 2138.2022267517714) rotate(0 53.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">after update</text></g><g transform="translate(2127.50322887767 2139.3310966546414) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(2093.0085709726354 2013.2666617032064) rotate(0 52.5 20.5)"><path d="M0 0 C29.524161050096158 0, 59.048322100192316 0, 105 0 M0 0 C36.504488250240684 0, 73.00897650048137 0, 105 0 M105 0 C105 10.325107226148248, 105 20.650214452296495, 105 41 M105 0 C105 9.495934232696891, 105 18.991868465393782, 105 41 M105 41 C74.71515481732786 41, 44.43030963465571 41, 0 41 M105 41 C82.80356870032847 41, 60.60713740065694 41, 0 41 M0 41 C0 29.32805828116834, 0 17.65611656233668, 0 0 M0 41 C0 30.19957923032343, 0 19.399158460646863, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2094.0085709726354 2070.2666617032064) rotate(0 51 22.5)"><path d="M0 0 C29.419563891738658 0, 58.839127783477316 0, 102 0 M0 0 C21.562247548252344 0, 43.12449509650469 0, 102 0 M102 0 C102 12.810667740181088, 102 25.621335480362177, 102 45 M102 0 C102 11.854120356962087, 102 23.708240713924173, 102 45 M102 45 C80.61069051995874 45, 59.22138103991747 45, 0 45 M102 45 C80.83884798511863 45, 59.67769597023725 45, 0 45 M0 45 C0 28.328645361587405, 0 11.65729072317481, 0 0 M0 45 C0 34.01418258436024, 0 23.028365168720484, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2315.630491090157 2215.0546687150145) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(2398.813380718231 2204.477938912777) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.2929012427776867 4.6096996034750255, 2.8472593096168217 2.8216151898741626, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.8517481006590948 5.117188240914534, 1.964953025379638 3.836592464753179, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.09839555532937 7.479359926500602, 8.06364572979113 2.9178613990721978, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.417184224681352 8.263003919919068, 6.701223068495093 4.4851493859091285, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.626918667928739 12.968375982103431, 9.382148779051317 7.498109493201968, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.0565887613896 13.62446548827937, 8.241488965973039 8.81028850555384, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.715422844976365 17.813207512523, 11.164554928217507 11.544698117187984, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.3614574419609475 18.220398129401186, 10.456624122186673 12.35907935094436, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.255578161251454 22.13847384128991, 14.506322384829135 13.797446757645918, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.11980589185277 19.993925156140836, 18.23477784603177 9.50834938734777, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.516160549036586 25.634983627284917, 19.28894398446085 14.392682312560037, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C8.576053189518198 26.716453433070832, 17.408729265424075 16.555621924131874, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C7.888108409659516 33.60485572055613, 15.638237500777649 24.689352062249355, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C10.083295149170755 31.079582247471905, 20.028610979800128 19.638805116080903, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C12.453225116675018 34.45030203235194, 23.717809680889086 21.491879829210635, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C17.347384032036103 28.820216236201507, 33.506127511611254 10.23170823690976, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C21.55381318289163 30.078285380667076, 36.27591137646919 13.142448730769956, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C17.43780191320385 34.813214709081535, 28.043888837093625 22.61230738759887, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C21.766510546829146 35.93061739971741, 31.71429069648162 24.487005393576922, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C25.628588071527656 31.48780542906593, 39.43844574587864 15.601381452273955, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C31.670289547372953 30.63463527221622, 45.87877426071611 14.289643343503592, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C27.76486623666902 35.1273108656745, 38.06792763930825 23.274994530420155, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C34.76571105880082 33.170772505260544, 47.08260187570924 19.001810434298527, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C35.18121105421159 32.69279443733952, 47.91360186653077 18.045854298456483, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C38.55263945950966 34.91142205950037, 49.01338424027381 22.87771174770723, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C44.16297574114715 28.457468447218805, 60.23405680354878 9.969804523144113, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C43.00925016649015 35.8816902457304, 52.939590246372155 24.458140744873575, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C44.183068593544675 34.53136661143301, 55.28722710048122 21.757493476278793, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C50.32497873871401 33.56291956726326, 61.92797295396676 20.215201592868357, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C50.67816756563262 33.15662229899264, 62.63435060780398 19.402607056327117, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C52.82528990392236 36.783652941408874, 61.94157987652086 26.296560965865872, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.6217935531243 38.168117121572735, 59.53458717492474 29.065489326193596, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C56.227503305490856 38.9668664764438, 63.10293224280475 31.057590240864783, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C58.04774158782605 36.87292186283115, 66.74340880747513 26.869701013639492, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C58.367247243970056 42.602384996897925, 62.39540471190053 37.96851990647933, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.4060130110888 41.40742167590182, 64.47293624613802 35.57859326448713, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C62.6812477262444 43.736707480176584, 65.3803312395961 40.63176707796569, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C63.23149924957844 43.10371551170787, 66.48083428626418 39.36578314102827, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.55133689041521 44.23132311218837, 70.13349416007512 41.26089096669556, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.69743126289549 44.06326076161429, 70.42568290503566 40.92476626554739, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.67077826831753 46.739828649694815, 71.38536150801714 45.91779466641475, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.17890829205336 46.15529192362867, 72.40162155548879 44.74872121428245, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C21.932444740086794 0, 43.86488948017359 0, 72 0 M0 0 C16.67752176448703 0, 33.35504352897406 0, 72 0 M72 0 C72 15.75052264314145, 72 31.5010452862829, 72 46 M72 0 C72 17.814064511843025, 72 35.62812902368605, 72 46 M72 46 C55.68650033399463 46, 39.37300066798925 46, 0 46 M72 46 C54.943963611871 46, 37.887927223742004 46, 0 46 M0 46 C0 35.37932818550617, 0 24.75865637101233, 0 0 M0 46 C0 32.96904833596199, 0 19.93809667192399, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2407.313380718231 2214.977938912777) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(2079.7394011263946 2209.0074728050386) rotate(0 71.39795918367349 18.459183673469397)"><path d="M-0.5332256704568863 0.6636613756418228 L142.39418197377609 -0.21745406091213226 L142.0709440778105 37.68443713169921 L-1.0454198271036148 37.45789322834838" stroke="none" stroke-width="0" fill="#fff"></path><path d="M1.2337845712900162 0.015040740370750427 C28.56659816368199 -0.6406339541427333, 60.50770338205053 0.5121108159073156, 142.37637738092826 0.3370445817708969 M0.969361238181591 0.0363282635807991 C53.000192554431926 -1.0081881181698065, 105.90986145613148 -1.295944322774909, 143.62886483921693 -0.8408563658595085 M144.35668972834037 -0.005633488297462463 C143.05124262072758 7.868321860368756, 144.06162193515019 15.354444567433411, 143.8131206403582 38.28928787093986 M142.8561464096515 -0.7321699187159538 C142.48485237584282 9.912379578227283, 143.74567300305534 21.5151276225338, 141.94549305691407 36.03820743989576 M143.7820804487078 35.948196714934 C103.69038879167367 34.684089546974484, 62.7665590731161 37.91535604554016, 0.4336971193552017 35.91201574188102 M143.5466149593799 37.79356136750806 C111.85944364060252 36.627684100235264, 80.07601225102437 37.33753226574735, -0.7850254252552986 36.95606484841932 M1.762398138642311 37.63891774993766 C1.6323946739687605 28.70091673362313, -1.345415708874257 16.862387763207035, 0.34800995886325836 -1.2112757116556168 M0.9106399342417717 36.41863313150037 C-0.325401134167095 27.893693413798267, -0.25188214269675807 20.403612399709445, -0.8587933257222176 -0.8126259371638298" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2114.387360310068 2215.4666564785084) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2144.887360310068 2214.9666564785084) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2233.887360310068 2207.9666564785084) rotate(0 17.75 17.75)"><path d="M-0.21745406091213226 -0.7249742895364761 L36.266069784760475 -1.0454198271036148 L36.039525881409645 34.953821524977684 L-0.5811678022146225 33.94901929795742" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-1.6772920042276382 -0.5623360723257065 C12.31542037986219 -0.07014615889638665, 26.016979809850454 1.8284014904871584, 36.63062797486782 0.5429748445749283 M-0.6205151602625847 -0.8873010352253914 C13.182961030676962 0.9743744572624564, 27.34160842522979 -0.8362084189429879, 36.01312632113695 -0.8793523982167244 M35.90208564698696 1.0762657076120377 C35.920972196497026 14.437571438774468, 36.869920579828324 26.54535238966346, 36.92520032823086 35.345044031739235 M34.882127709686756 0.6866742894053459 C35.69470356430858 10.01338423974812, 34.359919771812855 21.528746228665113, 35.70761031657457 35.05759137123823 M33.8974636644125 35.468544855713844 C24.72829246856272 36.09227961298078, 9.692773006111384 34.0602547525987, -0.8783871084451675 34.875888243317604 M35.65491981059313 35.63417714089155 C23.049795074388385 34.88912582490593, 8.907751698046923 35.3702452192083, 0.15197987109422684 35.72035758942366 M1.736088141798973 34.21069277822971 C-0.2767997700348496 22.069753262773155, -1.3391734081879259 6.453609279543162, -1.8685132414102554 0.9973238855600357 M-0.6993556544184685 34.90320556610823 C0.31328725669533014 20.75843490771949, -0.6352202906832098 7.467843631654976, 0.3318306878209114 -0.20086819678544998" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2234.887360310068 2208.9666564785084) rotate(0 17.5 4.5)"><path d="M-0.7249742895364761 0.7660697847604752 L33.954580172896385 0.5395258814096451 L34.453821524977684 8.418832197785378 L-1.5509807020425797 9.610531702637672" stroke="none" stroke-width="0" fill="#000"></path><path d="M-0.5623360723257065 -0.5245472341775894 C10.10678030923009 0.6305706825107336, 22.531649254262447 1.4241540756076574, 35.54297484457493 1.938722476363182 M-0.8873010352253914 -0.9082713648676872 C11.460095394402742 -0.6236760597676039, 20.276661440730095 0.7303066272288561, 34.120647601783276 0.7803856804966927 M35.48431956842542 0.18988746330142015 C35.46070591054857 3.314379166066647, 34.870041213855146 6.03627000823617, 34.93026981428265 9.054205238074065 M35.30900343023241 0.20458074845373636 C34.56563543990254 1.6449751149863008, 34.85883403733373 4.175518788769842, 34.800916117057206 9.221886468306185 M34.968544855713844 10.628730162978172 C26.631495725363493 7.713204536214471, 15.797269470989704 7.84116903282702, -0.6241117566823959 10.501393184065819 M35.13417714089155 9.864767976105213 C20.651795502752066 9.091919435188174, 7.569735825061798 9.206201804801822, 0.22035758942365646 9.881199069321156 M-0.580188249796629 8.932994066923857 C0.0806218392401934 5.4306782484054565, -0.7464274399727583 2.1106736786663536, 0.4487957485020161 0.8195759408175946 M-0.26855749525129796 9.06114672459662 C-0.326128788292408 6.384079122170806, -0.05964756757020948 4.6488453973084685, -0.0903906885534525 -0.04892716370522976" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2242.387360310068 2200.4666564785084) rotate(0 3 7)"><path d="M0.7660697847604752 -1.0454198271036148 L6.539525881409645 -0.546178475022316 L5.4188321977853775 12.44901929795742 L0.6105317026376724 12.975818023085594" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.1573641702532768 -0.1258622959256172 C1.3105869054794312 0.4605720810592175, 2.7581354543566703 0.1797625355422497, 6.581616742908954 0.021796958148479484 M-0.27248140946030613 0.24988394156098365 C1.308709875494242 -0.24005285918712616, 3.2758714668452744 -0.2105676573514938, 6.234115704149008 -0.0008450232446193806 M6.295380498468876 0.712041591107845 C6.987790362089871 4.039350315928459, 7.0257862250506875 8.383771772682667, 6.084319259226322 12.974962113797664 M6.318236719816923 -0.5952977173030376 C5.297385944426059 3.2569841168820863, 6.058825100958347 6.482704214006663, 6.345156728476286 13.660440278798342 M6.488619048893452 14.13010913580656 C3.6300677686929705 13.51955802336335, 1.6004303678870209 13.811822818666696, 0.4504179552197457 14.525116412341594 M6.259430392831564 13.76449237242341 C3.9169691093266006 13.916878819167614, 1.856913679093122 14.07415790528059, 0.2643597207963467 14.108082560449839 M-0.10423145145177837 14.243606971204281 C-0.9694191680848598 7.575133657455444, -1.4294854389131069 3.9342222943902008, 1.2748959079384803 -0.6996279016137122 M0.09511712715029719 13.398844671994448 C-0.4709283643960952 10.551809810847045, 0.3301912730932236 7.336269853264093, -0.07610892131924629 -0.2537410013377666" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2257.387360310068 2200.4666564785084) rotate(0 3 7)"><path d="M-1.0454198271036148 0.5395258814096451 L5.453821524977684 -0.5811678022146225 L4.44901929795742 14.610531702637672 L-1.0241819769144058 15.71179236471653" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.1258622959256172 0.10111337453126912 C1.7405143976211548 0.35492480948567384, 2.7565172508358957 0.5973531599342823, 6.02179695814848 -0.23903321474790573 M0.24988394156098365 -0.25225690975785253 C1.1343999825417996 0.20843417823314664, 2.591576088219881 0.288611986041069, 5.9991549767553805 0.009882958978414513 M6.712041591107845 0.9596443668007848 C5.8685684402287 5.4791001826524734, 5.966869421750307 9.047239165008069, 4.974962113797664 13.19619512706995 M5.404702282696962 -0.6161119349300861 C5.9569233903288845 3.4803060151636602, 5.786448360383511 7.015114315599203, 5.660440278798342 14.312697874754667 M6.130109135806561 13.698094518482685 C4.201262578368187 13.785345772355795, 3.1742882743477816 14.49927986010909, 0.5251164123415947 14.294340141117573 M5.76449237242341 14.011309250444175 C4.015876304358243 14.097480039894581, 2.2802035041153426 14.316245799362658, 0.10808256044983866 14.25327547416091 M0.24360697120428076 13.152107001841069 C-0.9526626242697239 7.276779031753539, 0.7883782254159449 4.444372509419917, -0.6996279016137122 -0.9384387955069542 M-0.6011553280055523 13.431161843985318 C-0.10307776391506192 11.011379397660493, -0.05705651700496668 7.482086911052464, -0.2537410013377666 0.26812442466616626" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2250.887360310068 2216.9666564785084) rotate(0 0.20799543491568784 12.861703209578991)"><path d="M0.3370445817708969 1.4790066629648209 C1.1970670645311474 7.748928401209415, 2.141103164218366 14.483514787964525, -0.2967773824930191 25.25896967947483 M-0.8408563658595085 -0.9776364043354988 C0.3780693576857448 4.715135126076639, 0.7509636652842164 11.190323036797345, 0.5329431965947151 26.70104282349348" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2241.387360310068 2215.9666564785084) rotate(0 0.250685129314661 14.525629587471485)"><path d="M1.4790066629648209 1.1306279748678207 C0.6211254256218672 9.60351373706013, 0.28769342735409736 14.533647750355303, -0.7410303205251694 26.225397929549217 M-0.9776364043354988 0.5131263211369514 C-0.5415409543365239 6.641430140696466, 0.43958113148808486 11.750827835164964, 0.7010428234934807 28.53813285380602" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2260.887360310068 2217.4666564785084) rotate(0 0.881765391677618 12.415816836059093)"><path d="M1.1306279748678207 0.5429748445749283 C2.400230086557567 7.176649219505489, 0.08326465371996172 12.978047614134848, -0.2746020704507828 23.683457270264626 M0.5131263211369514 -0.8793523982167244 C1.14090927798301 9.785861305184664, 0.9684087648615243 19.763734376095236, 2.038132853806019 25.71098607033491" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2269.887360310068 2225.4666564785084) rotate(0 -17.636783942580223 0.7824612371146031)"><path d="M0.5429748445749283 1.938722476363182 C-10.700319261848927 -0.6045706267654896, -22.78698189854622 -1.5823953147232532, -35.816542729735374 1.665892943739891 M-0.8793523982167244 0.7803856804966927 C-7.343905100226403 0.20740614578127864, -14.481133888661862 0.7125958029925823, -33.78901392966509 0.5086011365056038" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2269.387360310068 2234.4666564785084) rotate(0 -16.776338193565607 0.44000930051925025)"><path d="M1.938722476363182 0.0726565271615982 C-14.25333623595536 -0.7693445153534413, -28.6933729172498 -0.9709065914154053, -34.33410705626011 -0.681712731719017 M0.7803856804966927 -0.0028167441487312317 C-7.24243448458612 0.08671231970191007, -14.01775377176702 0.2988919261097909, -35.491398863494396 1.6854602620005608" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1017.5202442733594 258.5144794614512) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1197.5202442733594 263.5144794614512) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">unknown</text></g><g transform="translate(1150.5202442733594 252.01447946145117) rotate(0 94.5 23)"><path d="M0 0 C70.95094501152634 0, 141.9018900230527 0, 189 0 M0 0 C63.35328986272216 0, 126.70657972544431 0, 189 0 M189 0 C189 13.924661253392697, 189 27.849322506785395, 189 46 M189 0 C189 12.685054327547553, 189 25.370108655095105, 189 46 M189 46 C127.70845397338272 46, 66.41690794676542 46, 0 46 M189 46 C122.6631328381598 46, 56.32626567631959 46, 0 46 M0 46 C0 27.616077138483522, 0 9.232154276967044, 0 0 M0 46 C0 29.85423505157232, 0 13.708470103144641, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1493.9141836672989 256.7974508453981) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.832670802688257 3.9887657545743176, 3.9267984294379623 1.5797474920727463, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.9784212350667891 4.971467469048262, 2.2182992941950266 3.545150921020635, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.37460858018232 8.311981596267827, 6.6160717794970285 4.5831047386066475, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.1916574118686847 8.522442840360005, 6.250169442869758 5.004027226791005, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C3.473213240430706 14.295562257136579, 7.074737924055251 10.152482043268261, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.64777413008577 12.94438451731999, 9.423859703365379 7.450126563635084, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.737950658843504 17.787292227166486, 11.209610555951786 11.492867546474958, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.81762107869453 17.695641893179832, 11.368951395653838 11.30956687850165, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C8.842980156026423 20.312376736941175, 17.681126374379073 10.145252548948452, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C10.475407666861928 18.43448370139758, 20.945981396050083 6.389466477861255, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.226002889761762 25.968771831627873, 18.708628665911203 15.060258721245951, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C9.014899617064208 26.211618367200103, 18.286422120516093 15.545951792390408, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.395472293624229 26.118989894152126, 26.684788181735552 11.981749150109657, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C15.87821753425575 24.413286613372307, 29.650278662998595 8.570342588550023, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C20.763277283758065 24.89068055698112, 34.43338275414061 9.165023100473938, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C20.995815386415636 24.62317607020873, 34.89845895945576 8.630014126929158, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C26.521045039779274 24.364138781161774, 40.305843829329916 8.506541753764303, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.469745334088707 29.02462597077197, 32.20324441794878 17.827516132984698, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C31.71044703869748 24.49142701605997, 45.69763241930372 8.401010848266978, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C28.99194369025733 27.618707383030046, 40.26062572242342 14.65557158220713, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C32.31425868693094 29.893833518843415, 41.918240307908036 18.84571647854015, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C35.053310785473414 26.742914518947746, 47.396344504992975 12.543878478748816, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C37.94887564361349 29.508960531907263, 47.54439978442001 18.47057270959692, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C39.671166823849696 27.52769117012815, 50.988982144892425 14.508033986038686, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C42.910973032649395 29.897732808829765, 52.4815791546292 18.888009888148204, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C40.845781098123865 32.273464365155576, 48.35119528557814 23.639473000799825, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C47.85792328245242 30.303929876185542, 56.73240521738214 20.09500622778881, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C51.850272077486025 25.711257951772012, 64.71710280744935 10.909662378961755, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C57.216362961433376 25.635288875425577, 70.46226916748145 10.397616850975169, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C57.89790854275577 24.85126037059122, 71.82536033012623 8.829559841306455, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C58.74595402494579 29.972707986816566, 67.87837685765317 19.46705727868621, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C61.246167829479475 27.096541014783178, 72.87880446672051 13.714723334619432, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C62.96835188884398 31.212407228293863, 71.33615717758694 21.586348386347094, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C65.56105143686412 28.229847578835304, 76.52155627362723 15.62122908742997, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.82577589031709 30.571224056124393, 77.40793074368003 20.698584246937216, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C66.07067846212402 33.74060109633355, 71.89773588729389 27.037338327355528, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C71.06352488858407 34.09400065205085, 76.89641333235139 27.384030063496404, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C73.56723629186938 31.213810152912455, 81.903836138922 21.623649065219617, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C75.80055067590764 34.74168818894585, 80.7273904701454 29.074007342215474, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C76.02338835294613 34.485342765342246, 81.17306582422239 28.56131649500827, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C78.70849720079879 37.49349072364621, 81.55626811206508 34.217505036322486, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C80.34716357299014 35.60842069910181, 84.83360085644779 30.44736498723369, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C84.11595924225449 37.369929574732005, 86.72811775812337 34.36498494342312, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.01921927251676 38.6315845868548, 84.53463781864794 36.88829496766872, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C27.48174813024704 0, 54.96349626049408 0, 85.70000000000013 0 M0 0 C23.505595291927495 0, 47.01119058385499 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 11.344363928586246, 85.70000000000013 22.68872785717249, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 10.094785391539336, 85.70000000000013 20.189570783078672, 85.70000000000013 39 M85.70000000000013 39 C51.926018922403536 39, 18.15203784480694 39, 0 39 M85.70000000000013 39 C56.20642761819073 39, 26.71285523638133 39, 0 39 M0 39 C0 27.112638091295956, 0 15.225276182591912, 0 0 M0 39 C0 29.743110812455413, 0 20.48622162491083, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1508.0141836672988 264.59745084539827) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1031.2702442733594 1351.859549495717) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1211.2702442733594 1356.859549495717) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1164.2702442733594 1345.359549495717) rotate(0 94.5 23)"><path d="M0 0 C43.93486205711961 0, 87.86972411423922 0, 189 0 M0 0 C68.47068202123046 0, 136.94136404246092 0, 189 0 M189 0 C189 11.259486769139768, 189 22.518973538279536, 189 46 M189 0 C189 17.517849661409855, 189 35.03569932281971, 189 46 M189 46 C146.6980354629457 46, 104.3960709258914 46, 0 46 M189 46 C115.34704683199524 46, 41.694093663990486 46, 0 46 M0 46 C0 35.74880533367396, 0 25.497610667347907, 0 0 M0 46 C0 28.277714131772516, 0 10.555428263545032, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1429.4141836672989 1356.1425208796638) rotate(0 21.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1507.6641836672989 1350.1425208796638) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.1680636657135124 4.75330880816367, 2.597584155488473 3.1088335992514513, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.3595743526068416 4.53300096431639, 2.9806055292751314 2.6682179115568903, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.4478070758704646 9.378144766593092, 4.762468770873318 6.7154310792571765, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.3163147367899652 9.52940939926289, 4.499484092712319 7.017960344596775, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C6.023305356175783 11.36201685168006, 12.174922155545405 4.285391232355222, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.75875087257915 11.666351971598296, 11.64581318835214 4.894061472191693, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.375578048976325 17.053785821978902, 12.484865336217428 10.025854736099792, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.41004029965436 17.01414153755716, 12.553789837573499 9.946566167256305, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C6.827577750615223 22.630831991963447, 13.650321563556673 14.782163058992994, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C7.413960704011991 21.95627556784286, 14.82308747035021 13.433050210751812, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C10.72703285783447 24.24203437806507, 21.710688602056617 11.606783814120345, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C6.754050063429236 28.812428267181495, 13.76472301324615 20.747571592353193, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.642324438868245 25.835018985008674, 27.178492472223585 11.413807331822756, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C11.04377932938536 29.974671590917456, 19.981402253257816 19.693112543640318, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C21.14128179646881 24.455836107771706, 35.1893917795621 8.295334202055109, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C19.96728173679834 25.806368686492192, 32.84139166022117 10.996399359496078, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C21.78015876421793 29.81790457479522, 30.824071278207228 19.414073341031195, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C20.85053294298741 30.887316750075698, 28.96481963574619 21.552897691592147, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C30.729227301916936 25.62019120179402, 43.73519294574263 10.658539219735083, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C25.523898748441372 31.60823671891775, 33.324535838791505 22.63463025398254, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C30.290159765407076 32.22229297125459, 37.8700424648603 23.5026353833625, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C31.891231849548355 30.38047022797496, 41.072186633142856 19.81898989680325, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C37.29675856475941 30.259135417230254, 46.24016562671184 19.970922480242898, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C42.26060010749494 24.548888928016098, 56.167848712182916 8.550429501814584, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C46.267118746162495 26.036928809974075, 59.19387058165542 11.166401890436823, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C43.78269662308831 28.894929530559565, 54.225026335507046 16.882403331607804, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C50.99459514721693 26.69560165914144, 63.00574894691116 12.878349793700604, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C53.051938699783015 24.32889863346957, 67.12043605204335 8.144943742356872, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C56.12724425861781 26.888176622858133, 68.28403176185031 12.903392345840285, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C51.517456439565606 32.19113089388803, 59.064456123745906 23.509300887900075, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C56.73158547110044 32.28997393165974, 63.84963974996244 24.101589168372556, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C58.92890103844186 29.762251522295255, 68.24427088464529 19.04614434964359, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C63.51834177647235 30.579716237275164, 72.43613695284365 20.320966404309697, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C67.96149522775357 25.468452878486247, 81.32244385540612 10.098439686731854, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.69597646440566 30.72054121496833, 77.14833189185715 20.997218564625086, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C69.59998204984933 29.68060174952259, 78.95634306274451 18.91733963373361, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C73.04455754813084 31.815083266835224, 80.85847865144494 22.826195293065158, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C73.3015613409806 31.519434223004897, 81.37248623714444 22.2348972054045, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C77.42636322445753 32.87140479703058, 83.97901556724518 25.33344055838493, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C75.26372668564532 35.359233547581944, 79.65374248962075 30.30909805948766, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.06707914381792 37.080989384997075, 82.27343199810335 33.392502359024206, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.34946265055413 36.75614432012746, 82.83819901157577 32.74281222928498, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.41321672213974 38.17834236828289, 85.32263271789387 35.98181053052489, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C82.85958293045782 38.815225191403755, 84.21536513453002 37.25557617676662, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C31.05126833669846 0, 62.10253667339692 0, 85.70000000000013 0 M0 0 C25.29206354983155 0, 50.5841270996631 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 7.99688525274396, 85.70000000000013 15.99377050548792, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 14.824664580076934, 85.70000000000013 29.64932916015387, 85.70000000000013 39 M85.70000000000013 39 C54.724368588999 39, 23.74873717799786 39, 0 39 M85.70000000000013 39 C67.95390589348982 39, 50.20781178697951 39, 0 39 M0 39 C0 26.822433661669493, 0 14.644867323338985, 0 0 M0 39 C0 24.301500666886568, 0 9.603001333773136, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1521.7641836672988 1357.942520879664) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(916.5709458336519 1853.4558419016075) rotate(0 412 251.56944444444457)"><path d="M0 0 C237.1811243116856 0, 474.3622486233712 0, 824 0 M0 0 C172.54678277373316 0, 345.0935655474663 0, 824 0 M824 0 C824 112.20485208479487, 824 224.40970416958973, 824 503.13888888888914 M824 0 C824 119.43370529266288, 824 238.86741058532576, 824 503.13888888888914 M824 503.13888888888914 C570.5043474137783 503.13888888888914, 317.0086948275566 503.13888888888914, 0 503.13888888888914 M824 503.13888888888914 C513.3626734793186 503.13888888888914, 202.72534695863715 503.13888888888914, 0 503.13888888888914 M0 503.13888888888914 C0 365.26225807884106, 0 227.38562726879297, 0 0 M0 503.13888888888914 C0 376.0602100148592, 0 248.98153114082922, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1009.5709458336516 1923.4558419016075) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1046.9876125003182 2113.8566355524) rotate(0 81 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock is 15</text></g><g transform="translate(995.3934566561632 2229.455278815496) rotate(0 288.4999999999999 28.5)"><path d="M0 0 C115.43863970711827 0, 230.87727941423654 0, 576.9999999999999 0 M0 0 C117.08345114514229 0, 234.16690229028458 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 15.664222944527864, 576.9999999999999 31.32844588905573, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 11.606445883959532, 576.9999999999999 23.212891767919064, 576.9999999999999 57 M576.9999999999999 57 C453.28450038954605 57, 329.5690007790922 57, 0 57 M576.9999999999999 57 C367.806977636367 57, 158.61395527273407 57, 0 57 M0 57 C0 45.09641448184848, 0 33.192828963696954, 0 0 M0 57 C0 39.794306612759826, 0 22.58861322551965, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1490.393456656163 2229.455278815496) rotate(0 41 29)"><path d="M0 0 C19.06852476447821 0, 38.13704952895642 0, 82 0 M0 0 C19.28069086223841 0, 38.56138172447682 0, 82 0 M82 0 C82 23.10410188883543, 82 46.20820377767086, 82 58 M82 0 C82 13.37995346337557, 82 26.75990692675114, 82 58 M82 58 C60.44384906440973 58, 38.887698128819466 58, 0 58 M82 58 C49.94147968143225 58, 17.882959362864497 58, 0 58 M0 58 C0 42.55719810277223, 0 27.11439620554447, 0 0 M0 58 C0 45.967039696872234, 0 33.93407939374447, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1529.393456656163 2241.455278815496) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1529.393456656163 2241.455278815496) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.172222794085663 22.536912084315766, -2.5301031516349584 27.04859872199198, 0 34 M-5.814342436536368 18.025225446639556 C-3.6142086520908348 24.070043339182902, -1.4140748676453017 30.11486123172625, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1529.393456656163 2241.455278815496) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.172222794085663 22.536912084315766, 2.5301031516349584 27.04859872199198, 0 34 M5.814342436536368 18.025225446639556 C3.6142086520908348 24.070043339182902, 1.4140748676453017 30.11486123172625, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1026.3793720786027 2245.455278815496) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(922.2888234866489 1782.589056174199) rotate(0 244 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: initial state</text></g><g transform="translate(1046.4061697165266 2180.939090707132) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1401.120455430812 2172.1533764214173) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.0909203447289906 4.842052047452373, 2.4432975135194295 3.2863200778288584, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.9609887858427564 4.991521207896073, 2.183434395746961 3.585258398716258, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.055934280738017 7.528206035320831, 7.978723180608424 3.015553616712655, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.954120567050224 8.795697722190123, 5.775095753232837 5.5505369904512385, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C3.931789477079368 13.76803064219365, 7.9918903973525754 9.097418813382406, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.3020586491481456 14.49245309169886, 6.73242874149013 10.546263712392824, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C8.176788733731076 14.981729955488115, 16.08728670572693 5.881743003118217, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C4.890129338938814 18.76259908855326, 9.513967916142406 13.443481269248503, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C5.323469502644222 24.361110601469832, 10.64210506761467 18.242720278005763, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C7.166012140537533 22.241507761879706, 14.327190343401293 14.003514598825511, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.004653777300126 26.22340485757015, 18.26593044098793 15.569524773130503, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C11.149446486180546 23.756103085236145, 22.55551585874877 10.634921228462495, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.671130608757073 28.10261809167754, 25.204281898972763 13.68487680449218, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C13.073179670552559 27.640113552835167, 26.008380022563735 12.759867726807432, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C13.602967845916378 33.127674320200626, 26.017295139371807 18.846624404907992, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C17.00985498266705 29.208498991115004, 32.83106941287315 11.008273746736755, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C15.36440546127615 37.19838448302329, 23.89709593323823 27.382646935482388, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C19.931300316280954 31.94477292272559, 33.030885643247835 16.87542381488699, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C23.96493341124092 33.40162119078579, 36.11113642530516 19.429012975713682, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C25.335211234181067 31.82529687415986, 38.85169207118546 16.276364342461818, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C26.923227680139938 36.095505271404654, 36.38465052625008 25.211383341880463, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C32.18211722848127 30.045844877928026, 46.902429622932736 13.112062554927206, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C32.21982389418312 36.09948066778622, 41.99082754647384 24.859226759349877, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C35.79387852241059 31.98800113779131, 49.13893680292877 16.63626769936006, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C41.15523817334838 31.917474722426277, 54.21858166795123 16.889817073559055, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C42.866012590319265 29.94945388126103, 57.640130501893 12.953775391228561, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C48.41568418493783 29.66229935518325, 63.75245828326753 12.019358963779283, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C41.81100230326506 37.26011673160461, 50.54309451992199 27.214993716622008, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C48.26506034763322 35.93258460581613, 57.80813617180518 24.954531669974106, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C50.20061821505383 33.70597998478739, 61.67925190664641 20.501322427916612, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C51.89049263716788 37.859014184239776, 60.0719853430119 28.447283451527678, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C54.73073308969366 34.591691298743015, 65.75246624806346 21.912637680534164, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C58.30371628344327 36.57845665994509, 67.25535819870957 26.280770607867368, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C58.308125954591326 36.57338391357013, 67.26417754100568 26.270625115117458, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C60.96690458165455 39.61182132602541, 67.59471938726952 31.98739256473429, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.307395360843515 41.52086830513838, 64.27570094564744 35.80548652296024, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C62.722751713026106 43.68896260500919, 65.46333921315951 40.53627732763091, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.43769243441663 41.71614897886472, 68.89322065594057 36.59065007534198, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.65326893737495 44.11406370568252, 70.3373582539946 41.02637215368385, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.24603681104708 44.58253067821555, 69.52289400133883 41.96330609874992, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.69600811909511 46.7108050264414, 71.4358212095723 45.859747419907904, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.1135379226463 46.23049193136291, 72.27088081667466 44.899121229750946, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C28.68456582427025 0, 57.3691316485405 0, 72 0 M0 0 C21.353178030252458 0, 42.706356060504916 0, 72 0 M72 0 C72 15.29855439811945, 72 30.5971087962389, 72 46 M72 0 C72 10.088826654851436, 72 20.177653309702873, 72 46 M72 46 C46.18587675690651 46, 20.37175351381302 46, 0 46 M72 46 C55.007525628805155 46, 38.01505125761032 46, 0 46 M0 46 C0 34.933114741742614, 0 23.86622948348522, 0 0 M0 46 C0 33.28402233272791, 0 20.568044665455815, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1043.0038774082552 1977.5509344581742) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(1229.4931932183251 1978.6798043610443) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1043.0038774082552 2028.5509344581742) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(1229.4931932183251 2035.6798043610443) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1195.4985353132902 1967.6153694096092) rotate(0 52.5 20.5)"><path d="M0 0 C24.69663779810071 0, 49.39327559620142 0, 105 0 M0 0 C32.88236404582858 0, 65.76472809165716 0, 105 0 M105 0 C105 12.499904776364566, 105 24.999809552729133, 105 41 M105 0 C105 11.410576330870391, 105 22.821152661740783, 105 41 M105 41 C67.12486464902759 41, 29.249729298055158 41, 0 41 M105 41 C76.42338036373258 41, 47.846760727465146 41, 0 41 M0 41 C0 28.070744704455137, 0 15.141489408910275, 0 0 M0 41 C0 29.45000053718686, 0 17.90000107437372, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1196.4985353132902 2024.6153694096092) rotate(0 51 22.5)"><path d="M0 0 C23.705919201672078 0, 47.411838403344156 0, 102 0 M0 0 C20.977860300242902 0, 41.955720600485805 0, 102 0 M102 0 C102 11.528187688440084, 102 23.05637537688017, 102 45 M102 0 C102 15.508610609918835, 102 31.01722121983767, 102 45 M102 45 C71.24600254744291 45, 40.492005094885826 45, 0 45 M102 45 C66.05086522847414 45, 30.101730456948275 45, 0 45 M0 45 C0 29.324226681143045, 0 13.64845336228609, 0 0 M0 45 C0 31.80285060033202, 0 18.60570120066404, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1418.120455430812 2182.6533764214173) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1501.303345058886 2172.07664661918) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C0.965600441736221 4.986216104651257, 2.19265770753389 3.574648192226626, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.6133162138232222 4.241104343583608, 3.4880892517078927 2.084424670091326, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.648335235138404 7.997095100192062, 7.163525089409197 3.9533317464551168, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.3500231501029547 8.34026389830907, 6.566900919338298 4.639669342689134, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.57169766949688 11.881532066893634, 11.271706782187598 5.324421662782372, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.36987753206746 13.264067984154451, 8.86806650732876 8.089493497304003, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.856252205894873 16.50083345769221, 13.446213650054524 8.919950007526408, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.311331628755643 18.27806128129883, 10.356372495776064 12.474405654739646, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C8.394970302828646 20.82775311818362, 16.78510666798352 11.176005311433336, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C6.507872769949093 22.998610501352967, 13.010911602224413 15.517720077772035, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.84100486007546 25.261292994600325, 19.938632606538597 13.645301047190852, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.544475196613628 27.903148165692713, 15.345573279614934 18.929011389375628, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C11.607492680632955 29.326193560913538, 23.077006042724527 16.132027742964173, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C12.681829312882755 28.090310640453154, 25.225679307224127 13.660261902043398, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C12.948784612455563 33.88022604450763, 24.708928672450178 20.351727853522007, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C16.383884466024085 29.928595697312886, 31.579128379587225 12.44846715913252, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C16.27626263619843 36.1494127970949, 25.720810283082795 25.284703563625595, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C22.162332380717757 29.378264120300425, 37.49294977212145 11.742406210036656, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C22.63473288331568 34.931841853379694, 33.45073536945469 22.489454300901485, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C27.60017800282167 29.219750660110254, 43.38162560846666 11.065271914362604, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C28.03773077370014 34.813416122822915, 38.61365671337049 22.647205044716983, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C28.73180183369619 34.01497870303706, 40.001798833362585 21.050330205145276, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C37.72516161241202 29.76631408565349, 53.00150298293164 12.19289359508442, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C37.506818107690144 30.01748955540745, 52.56481597348788 12.69524453459234, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C38.80601667841654 34.61994491176037, 49.52013867808756 22.29475745222724, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C39.37094751487938 33.97006632522858, 50.65000035101323 20.995000279163666, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C41.875261774474744 37.18619466606027, 50.671613462341355 27.067149585533315, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C45.35421394435585 33.18411799959606, 57.629517802103564 19.062996252604908, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C52.548988125867446 31.004489430918987, 66.37599172827363 15.09834132017981, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C50.037153528279696 33.89402459614863, 61.35232253309814 20.8774116506391, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C52.899531906204274 36.69824728749493, 62.09006388108468 26.125749658037993, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C52.95433495201077 36.63520359497965, 62.19966997269768 25.99966227300743, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C56.56838460507855 38.57472739878568, 63.78469484198013 30.273312085548547, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C56.00765249429886 39.219775903940985, 62.66323062042075 31.563409095859154, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C58.72711598403206 42.188403367584165, 63.11514219202454 37.14055664785182, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C58.148177076502364 42.85439639651738, 61.95726437696514 38.47254270571823, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C64.14588211683927 42.05183834910684, 68.30960002078587 37.262028815826206, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.17158984037532 42.022264996129415, 68.36101546785792 37.202882109871354, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.11690508387139 44.73107973752833, 69.26463054698746 42.26040421737549, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.50200064914837 44.28807796547278, 70.03482167754143 41.374400673264375, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.7788649183992 46.61548918219853, 71.60153480818046 45.669115731422174, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.01649475781093 46.342127322326256, 72.07679448700394 45.12239201167763, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C24.9990046441555 0, 49.998009288311 0, 72 0 M0 0 C18.89581623673439 0, 37.79163247346878 0, 72 0 M72 0 C72 12.982670523226261, 72 25.965341046452522, 72 46 M72 0 C72 17.12471861690283, 72 34.24943723380566, 72 46 M72 46 C51.709999948740005 46, 31.41999989748001 46, 0 46 M72 46 C43.89292697310448 46, 15.785853946208952 46, 0 46 M0 46 C0 36.00459248870611, 0 26.00918497741222, 0 0 M0 46 C0 33.257136107981204, 0 20.514272215962407, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1509.803345058886 2182.57664661918) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1182.2293654670493 2176.6061805114414) rotate(0 71.39795918367349 18.459183673469397)"><path d="M1.672937735915184 -1.6225496381521225 L144.7023351262419 0.6433686167001724 L142.84241510137008 37.36221537571777 L0.9881971925497055 38.18504891377319" stroke="none" stroke-width="0" fill="#fff"></path><path d="M-0.5037814229726791 1.9669316858053207 C33.604280259034475 0.22042079466490105, 61.80305544201819 -0.6517978141913001, 142.04475621088432 0.6515453904867172 M-0.9637814238667488 -0.6931114718317986 C34.712639796779484 -0.3664882219217871, 67.35481266537494 0.17317866917623537, 142.61823804630922 -0.8067768141627312 M141.08415058954643 -0.7424022108316422 C142.32678374701774 10.92371443514616, 143.7166186278466 23.0008265645224, 144.241448200783 38.08895713668693 M143.4214473511188 0.9095756486058235 C143.07612582708353 14.524902024606648, 142.96508976484293 28.620887885470754, 142.61397393002198 36.27843323182691 M140.9726150403826 36.24347144943107 C101.76526391678019 37.67087043235949, 65.58319660665431 39.26389230201892, 1.8075202852487564 35.73005658965934 M142.8144610668628 35.993015675064214 C107.07914770222747 36.24827819009365, 71.14698832929136 35.37344007630886, -0.8270853236317635 36.682710318084844 M0.10155333578586578 34.99943906646598 C-0.3544819420226377 23.263524874451793, 0.09794449154059473 9.050910853336042, -0.45962439477443695 1.4708398431539536 M0.24138277024030685 36.7060696167909 C0.18824374114524345 28.4890349346916, 0.08326541816245536 18.01681989370558, 0.1552075669169426 0.02446264773607254" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1216.8773246507226 2183.0653641849112) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1247.3773246507226 2182.5653641849112) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1336.3773246507226 2175.5653641849112) rotate(0 17.75 17.75)"><path d="M0.6433686167001724 0.04649673402309418 L35.94384802877903 0.9881971925497055 L36.76668156683445 35.485912665724754 L-0.00971280038356781 34.65341268479824" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0.26330743730068207 -1.8866940587759018 C9.146876802667975 -1.0634566342458127, 20.163121338933706 -1.5647502934560178, 34.20532165467739 0.581592932343483 M0.04875726252794266 -0.43818051367998123 C7.817000098899007 -0.46741115961223845, 13.650375076383353 -0.21332728777080778, 35.575800843536854 0.9825183674693108 M35.06613479554653 0.8927158266305923 C35.65389089290053 9.057142027840019, 36.538813397325576 16.762965323776008, 36.33467184007168 33.94439209997654 M36.10715574771166 0.015097789466381073 C36.60829468693584 12.688683455064893, 35.18296524014324 25.481585221737625, 34.94225139170885 36.41692917793989 M34.94316564500332 36.54884995520115 C29.122550920024516 36.970293610058725, 19.484416053444146 34.214208691082895, -0.16343463957309723 34.34651316702366 M35.653476901352406 35.98350518196821 C24.062812204286455 34.79696834180504, 13.832689804583786 36.223613859154284, -0.6298890635371208 36.12501399964094 M-0.3658539205789566 35.36539973318577 C-0.9488696200028062 24.21011843420565, 1.8114496129378677 12.749554542452096, -0.06487669050693512 0.3372725397348404 M0.6342539265751839 35.57128971070051 C-0.6990090813860297 24.148298732563852, 0.09982834193855522 13.322651199251414, -0.8112748190760612 0.9532083794474602" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1337.3773246507226 2176.5653641849112) rotate(0 17.5 4.5)"><path d="M0.04649673402309418 0.44384802877902985 L35.988197192549706 1.2666815668344498 L34.985912665724754 8.990287199616432 L-0.8465873152017593 7.383707895874977" stroke="none" stroke-width="0" fill="#000"></path><path d="M-1.8866940587759018 -0.6089111715555191 C13.190968293696642 0.6054665889590979, 26.631805084645748 -1.3407571468502284, 35.58159293234348 -1.9275628477334976 M-0.43818051367998123 0.5884241536259651 C7.896429527550936 -0.8238418560475111, 16.22462324798107 0.05873580165207387, 35.98251836746931 -0.85588388890028 M35.40172212198377 0.12855027839541433 C35.62811806760728 2.892684410512447, 35.670513063296674 5.107440338283777, 34.29997644498944 9.56297608539462 M35.00679400525987 0.3534278992563486 C34.929477049857375 3.2306477319449187, 34.89450029328465 6.786249293759465, 35.41261813007295 8.589756751433015 M36.04884995520115 10.036187514662743 C27.488588105887175 7.714243230596185, 15.413571007549763 8.739119348302484, -1.1534868329763412 9.037085399031639 M35.48350518196821 8.278638787567616 C26.911645527929068 8.818059886619448, 21.077021896839142 8.42382785193622, 0.6250139996409416 9.050776667892933 M-0.06057012006640439 8.419734274595976 C-0.18041477613151077 7.825395655632019, -0.4640528004616499 5.297840941697359, 0.15177264288067815 0.21724449321627615 M0.03208036981523038 8.65153135098517 C0.05397928208112715 6.502076148614288, 0.36054686754941934 3.6170622456818817, 0.4289437707513571 0.14475793875753878" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1344.8773246507226 2168.0653641849112) rotate(0 3 7)"><path d="M0.44384802877902985 0.9881971925497055 L7.26668156683445 -0.014087334275245667 L5.990287199616432 13.15341268479824 L-1.6162921041250229 13.963841781020164" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.1826733514666557 -0.2253486469388008 C2.5232852697372437 -0.3191359229385853, 4.267239801585675 0.2310787014663219, 5.421731145679951 -0.41586688309907915 M0.17652724608778952 -0.05330409631133079 C2.047652880102396 0.049211896061897256, 4.602112101763487 0.3210463410615921, 5.743234833329916 -0.11136033162474632 M6.1999670997262 1.0118708834052086 C6.789042288511991 4.929835316538812, 6.553899723738432 6.615107794106008, 6.875740577280521 15.273405908048153 M6.549776732176542 -0.1273611061275005 C5.637047924101353 4.19481799826026, 5.694577778875828 9.395025209337474, 5.361843835562468 13.763786435872316 M6.310856254398823 14.542256085574627 C4.41346735060215 13.746896108537912, 3.490890763700008 13.685419448763133, 0.011125619709491774 13.444788996875285 M5.783591636270285 13.75187440291047 C3.919634724408388 14.208878369033336, 1.7916969470679756 14.216514510810375, 0.01523300036787989 13.712160757929087 M-0.9026355728507041 13.678262923657893 C1.0551976646482943 9.447966909408569, -0.019803908914327664 6.657928158342838, 0.3379358783364297 -0.297216822206974 M-0.5420623429119587 14.10864529684186 C-0.07167169630527495 10.138752960413694, -0.6566879230737686 5.77369537129998, 0.22517901584506028 0.016273856908082918" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1359.8773246507226 2168.0653641849112) rotate(0 3 7)"><path d="M0.9881971925497055 1.2666815668344498 L5.985912665724754 -0.00971280038356781 L5.153412684798241 12.383707895874977 L-0.03615821897983551 12.606611624360085" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.2253486469388008 0.19546361714601512 C1.5032554864883423 -0.3701361684501171, 3.932462342083454 -0.5600015191733837, 5.5841331169009205 0.4896859243512154 M-0.05330409631133079 -0.24203304424881933 C1.8521754227578642 -0.012565196156501757, 3.9532642252743244 -0.2920706158876419, 5.888639668375253 -0.07456785812973976 M7.011870883405209 0.8194128528237341 C7.140308762341737 4.480564644932747, 4.973564577847719 8.668329291045666, 7.273405908048153 14.176587392389774 M5.8726388938725 -0.44795388057827945 C5.8203442963957785 4.659594020992518, 6.643964115083218 9.461879929155113, 5.7637864358723165 13.335045992583037 M6.542256085574627 13.643506772816181 C4.082480916380883 13.982055233567953, 2.388054563105106 14.042211245149375, -0.5552110031247138 14.209668166935444 M5.751874402910471 13.929302891343832 C4.287781917303801 13.76775160819292, 2.4033319182693957 13.971951327621937, -0.2878392420709133 13.91194599494338 M-0.3217370763421059 15.029587890207768 C-0.7535297621786594 10.010684061050414, 0.2455127964913846 6.450131367146968, -0.297216822206974 0.24677524715662003 M0.10864529684185986 14.017123853415251 C0.5755476480722427 8.3441521294415, 0.6572653061151504 4.049268021434545, 0.016273856908082918 0.1553468100726605" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1353.3773246507226 2184.5653641849112) rotate(0 0.66275480017066 12.672529354691505)"><path d="M0.6515453904867172 -1.2524558156728745 C-1.2725264270231127 6.03360120985657, -1.802578280903399 9.51591570597142, 2.132286414504051 26.597514525055885 M-0.8067768141627312 0.07640355080366135 C0.3598242185637355 7.77222840782255, -0.44376953955739734 13.207330573685468, 0.25144047290086746 26.283067397773266" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1343.8773246507226 2183.5653641849112) rotate(0 -0.32747064530849457 13.575839783996344)"><path d="M-1.2524558156728745 -1.294678345322609 C0.6112074511498213 5.61833264861256, -1.2218312041461468 16.725706270672383, 0.5975145250558853 27.123638972640038 M0.07640355080366135 0.07580084353685379 C1.1121890852600336 9.195737625323238, -0.06210917994379994 19.85064961727709, 0.28306739777326584 28.446357913315296" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1363.3773246507226 2185.0653641849112) rotate(0 0.3258397839963436 13.629220619797707)"><path d="M-1.294678345322609 0.581592932343483 C-1.4508279721811415 5.315438725464046, 2.5026841283217074 13.491444444693625, 0.6236389726400375 26.67684830725193 M0.07580084353685379 0.9825183674693108 C-0.6382640246674418 6.8245680226758125, 0.39240828897804025 14.175751707218588, 1.9463579133152962 25.64283364266157" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1372.3773246507226 2193.0653641849112) rotate(0 -16.43732399493456 -0.6023989655077457)"><path d="M0.581592932343483 -1.9275628477334976 C-12.693850107491016 1.4121910957992077, -22.517739802598953 -1.096456346064806, -32.82315169274807 -0.35536064207553864 M0.9825183674693108 -0.85588388890028 C-7.8281599372625355 -0.22278724029660224, -15.130850265920163 0.47213020011782647, -33.85716635733843 0.7227649167180061" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1371.8773246507226 2202.0653641849112) rotate(0 -18.60562226548791 0.0995359756052494)"><path d="M-1.9275628477334976 -1.386222943663597 C-6.919766509011389 0.43957846209406853, -17.973993482664227 1.7562894535064697, -36.35536064207554 -0.6135536283254623 M-0.85588388890028 -0.3712011054158211 C-11.322854301556944 0.4444143651425838, -21.702989644035696 1.1118209651112556, -35.277235083281994 1.585294894874096" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(919.1800970778143 2588.468754522708) rotate(0 412 242.19444444444457)"><path d="M0 0 C280.227407926321 0, 560.454815852642 0, 824 0 M0 0 C238.9694912016392 0, 477.9389824032784 0, 824 0 M824 0 C824 136.2877507086844, 824 272.5755014173688, 824 484.38888888888914 M824 0 C824 153.1982566141835, 824 306.396513228367, 824 484.38888888888914 M824 484.38888888888914 C619.1304369986058 484.38888888888914, 414.26087399721143 484.38888888888914, 0 484.38888888888914 M824 484.38888888888914 C639.4241226136685 484.38888888888914, 454.84824522733686 484.38888888888914, 0 484.38888888888914 M0 484.38888888888914 C0 332.7260584450846, 0 181.06322800128004, 0 0 M0 484.38888888888914 C0 361.3100231517936, 0 238.23115741469806, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1012.180097077814 2658.468754522708) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1049.5967637444805 2842.6195481735012) rotate(0 227.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock will change from 15 to 17 after update</text></g><g transform="translate(1009.2526079003255 2961.9681914365974) rotate(0 288.5 28.5)"><path d="M0 0 C210.21055428311226 0, 420.4211085662245 0, 576.9999999999999 0 M0 0 C145.5924633927643 0, 291.1849267855286 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 13.76067228242755, 576.9999999999999 27.5213445648551, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 14.453476584702731, 576.9999999999999 28.906953169405462, 576.9999999999999 57 M576.9999999999999 57 C439.1269875355064 57, 301.2539750710129 57, 0 57 M576.9999999999999 57 C399.0728687815367 57, 221.14573756307357 57, 0 57 M0 57 C0 35.215562392026186, 0 13.431124784052372, 0 0 M0 57 C0 44.99653309509158, 0 32.99306619018316, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1504.252607900325 2961.9681914365974) rotate(0 41 29)"><path d="M0 0 C25.931014920771123 0, 51.862029841542245 0, 82 0 M0 0 C21.203527615964415 0, 42.40705523192883 0, 82 0 M82 0 C82 20.769845850765705, 82 41.53969170153141, 82 58 M82 0 C82 15.434329311549666, 82 30.868658623099332, 82 58 M82 58 C60.65659751743078 58, 39.313195034861565 58, 0 58 M82 58 C54.86310951858759 58, 27.726219037175177 58, 0 58 M0 58 C0 37.05103646963835, 0 16.102072939276695, 0 0 M0 58 C0 43.39726181775331, 0 28.794523635506625, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1543.252607900325 2973.9681914365974) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1543.252607900325 2973.9681914365974) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.479791978043323 21.69187269647201, -3.1452415195502788 25.358519946304465, 0 34 M-5.814342436536368 18.025225446639556 C-4.092619901678256 22.755619233728396, -2.370897366820143 27.486013020817232, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1543.252607900325 2973.9681914365974) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.479791978043323 21.69187269647201, 3.1452415195502788 25.358519946304465, 0 34 M5.814342436536368 18.025225446639556 C4.092619901678256 22.755619233728396, 2.370897366820143 27.486013020817232, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1040.238523322765 2977.9681914365974) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(924.8979747308113 2517.6019687952994) rotate(0 275 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: updating stock</text></g><g transform="translate(1060.2653209606888 2913.4520033282333) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1414.979606674974 2904.6662890425177) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.0694736183042561 4.8667236839697, 2.4004040606699606 3.335663350863511, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.441522668299273 4.438730210918843, 3.1445021606599943 2.4796764047617965, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.449943352066549 9.375687261948018, 4.766741323265487 6.71051606996703, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.270109001809981 8.432194609795282, 6.407072622752351 4.823530765661556, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.9077817690326295 12.645279943839396, 9.943874981259098 6.851917416673896, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.511206114358018 11.951119640829049, 11.150723671909875 5.4635968106532005, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.673260726100951 15.56097266761675, 15.08023069046668 7.040228427375489, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C8.553656316296054 14.54819339479961, 16.841021870856885 5.014669881741206, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C9.240123310904831 19.855515798424975, 18.47541268413589 9.231530671916044, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C8.74180051347356 20.428770601187903, 17.478767089273347 10.378040277441904, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C7.938773350425098 27.449560026522423, 16.134169587237874 18.021835111035053, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.911517967119886 27.48091375840344, 16.07965882062745 18.084542574797084, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C14.102280369671952 26.456268620719165, 28.06658142080252 10.392177862575423, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C11.953988300397254 28.927595946696226, 23.769997282253126 15.334832514529545, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C13.63346182153386 33.0925950140397, 26.07828309060677 18.776465792586155, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C13.834373281759817 32.861472817547124, 26.480106011058684 18.314221399600996, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C19.934335677402537 31.941281139186813, 33.03695636549101 16.86844024780943, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C19.423769074466428 32.52862082898667, 32.01582315961879 18.043119627409144, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C26.401377920165906 30.59881240177138, 40.98402544315514 13.823395397684855, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C23.885941807439863 33.49249063623425, 35.95315321770305 19.61075186661059, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C33.05256944474344 29.04450414834451, 48.64333405545709 11.109381095760178, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C33.514412316278964 28.51321469982981, 49.567019798528136 10.046802198730774, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C37.292288074414145 30.264278128088236, 52.135755906935884 13.18882167995391, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C33.45126223718552 34.68287290255567, 44.453704232478636 22.02601122888878, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C38.520159473394585 34.94878600939413, 48.94842426804365 22.952439647494757, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C43.84148948937288 28.827296074615823, 59.59108430000025 10.70945977793815, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C47.34793195115798 30.89060779166325, 61.61695381570783 14.47597583673928, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C48.8976173679153 29.107898647094505, 64.71632464922247 10.91055754760179, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C49.905834415763785 34.045089954451235, 61.08968430806631 21.17954236724431, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C46.02654066942349 38.507706922571145, 53.33109681538572 30.10477630348413, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C51.65369853686655 38.13141463624275, 59.59839714240923 28.99208435553363, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C54.28661543049228 35.10259022297723, 64.8642309296607 22.93443552900258, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C55.18257527926562 40.168918665633115, 61.013076190354276 33.46169461924342, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C56.40510873276693 38.76255480395442, 63.45814309735688 30.64896689588603, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C60.20503176150179 40.48825574864951, 66.070973746964 33.7402614099825, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C60.938011574559695 39.64505892857694, 67.5369333730798 32.05386776983735, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C65.06426690316256 40.99535750524812, 70.14636959343241 35.149067128108776, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C62.61570580044832 43.81210484096082, 65.24924738800394 40.78256179953418, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C68.15218823756568 43.54012270499029, 71.33519685437605 39.87849015229939, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.22962937584751 44.60140527331265, 69.49007913093969 42.00105528894413, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C71.22166828660232 46.106102176806594, 72.48714154458672 44.6503417206383, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.19178496651935 46.140479004132914, 72.42737490442077 44.71909537529095, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C15.607046788930894 0, 31.21409357786179 0, 72 0 M0 0 C27.95016422867775 0, 55.9003284573555 0, 72 0 M72 0 C72 15.406367994844913, 72 30.812735989689827, 72 46 M72 0 C72 16.16315798610449, 72 32.32631597220898, 72 46 M72 46 C57.02690897583962 46, 42.05381795167923 46, 0 46 M72 46 C54.47378405928612 46, 36.947568118572235 46, 0 46 M0 46 C0 36.32309472411871, 0 26.64618944823742, 0 0 M0 46 C0 33.51145796924829, 0 21.022915938496585, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1045.6130286524176 2712.5638470792755) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(1232.1023444624875 2713.6927169821456) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">3</text></g><g transform="translate(1045.6130286524176 2763.5638470792755) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(1232.1023444624875 2770.6927169821456) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">1</text></g><g transform="translate(1198.1076865574526 2702.6282820307106) rotate(0 52.5 20.5)"><path d="M0 0 C26.49429576471448 0, 52.98859152942896 0, 105 0 M0 0 C25.34860683605075 0, 50.6972136721015 0, 105 0 M105 0 C105 10.39636035040021, 105 20.79272070080042, 105 41 M105 0 C105 9.796869169920683, 105 19.593738339841366, 105 41 M105 41 C72.62157924100757 41, 40.24315848201513 41, 0 41 M105 41 C64.87077282741666 41, 24.741545654833317 41, 0 41 M0 41 C0 32.36592731401324, 0 23.731854628026486, 0 0 M0 41 C0 29.648827327042817, 0 18.297654654085633, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1199.1076865574526 2759.6282820307106) rotate(0 51 22.5)"><path d="M0 0 C26.37511971741915 0, 52.7502394348383 0, 102 0 M0 0 C36.526280634105206 0, 73.05256126821041 0, 102 0 M102 0 C102 11.974910672754051, 102 23.949821345508102, 102 45 M102 0 C102 11.71284282580018, 102 23.42568565160036, 102 45 M102 45 C68.24435574263333 45, 34.48871148526668 45, 0 45 M102 45 C65.15871930867434 45, 28.31743861734867 45, 0 45 M0 45 C0 33.67028934136033, 0 22.340578682720658, 0 0 M0 45 C0 35.718918677419424, 0 26.437837354838848, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1431.979606674974 2915.1662890425177) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1515.162496303048 2904.5895592402812) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.3753640436643175 4.514837002564089, 3.0121849113900834 2.6318899880522895, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.8493351942651193 5.119963972199745, 1.9601272125916869 3.842143927323601, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.034463156220709 8.70327414585406, 5.935780931573807 5.365689837779114, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.961258922947706 8.78748598308616, 5.789372465027801 5.534113512243313, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.4253787671312494 12.049852709554312, 10.979068977456338 5.661062948103725, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.178774409341544 12.333538571838613, 10.485860261876928 6.22843467267233, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.788121109505969 15.428840911306326, 15.309951457276716 6.775964914754638, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.330556112387134 18.255946042684027, 10.394821463039046 12.430175177510039, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C6.3299650457229895 23.203269926703264, 12.655096153772206 15.92703892847263, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C8.944233705451841 20.19589785256318, 17.88363347322991 9.912294780192461, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C11.134114539684509 23.77374047210639, 22.524851965756696 10.670196002202985, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.814464994959845 27.59256043140325, 15.885552876307369 18.30783592079671, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.337943974365544 28.48590546958986, 24.537908630189705 14.451451560316809, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C13.741365245342406 26.87145397743612, 27.34475117214343 11.22254857600933, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C16.215279177930732 30.122553894025874, 31.24191780340052 12.836383552558495, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C9.581212113384062 37.75417505646579, 17.973783674307175 28.099625877438324, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C17.757773431649383 34.445129583051255, 28.6838318739847 21.876137135538308, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C18.475860205318625 33.6190652449789, 30.120005421323178 20.2240084593936, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C27.21453877819911 29.663377840701255, 42.61034715922155 11.952526275544606, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C24.78789339673942 32.45491402306125, 37.75705639630217 17.535598640264602, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C32.95671218411056 29.154775312579325, 48.45161953419132 11.329923424229804, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C28.098486788354222 34.7435243230162, 38.73516874267865 22.50742144510356, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C32.99473548210508 35.20804685865133, 43.54065072231776 23.076359141080104, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C35.35900949885364 32.488260723770274, 48.26919875581487 17.636786871317987, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C39.758227506288875 33.52455165836228, 51.424560333832225 20.103970945431055, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C43.60212757494806 29.102650458862083, 59.11236047115059 11.26016854643067, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C48.00875136482454 30.130422015302933, 62.93859264304095 12.955604284018648, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C42.69862404602975 36.23902472116566, 52.318338005451366 25.17280969574411, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C48.85826304400882 35.25018296482732, 58.9945415645564 23.589728387996466, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C47.190995580249954 37.16815478152302, 55.66000663703865 27.425672021387886, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C54.787307700832244 34.52660965343837, 65.86561547034063 21.782474389924868, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.14701488272454 38.71428750442301, 58.585029834125216 30.15783009189416, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C58.51149848727816 36.33943057707068, 67.67092260637935 25.80271844211855, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C57.82154168796805 37.133135081344356, 66.29100900775913 27.390127450665904, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C61.9156957815482 38.52036190461843, 69.49230178705682 29.80447372192035, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C58.40411740517944 42.559970728273505, 62.4691450343193 37.88369136923049, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C63.34098199809981 42.97777001667317, 66.69979978330691 39.11389215095886, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.5650515727087 41.569639049802646, 69.14793893252468 36.29763021721783, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.23811460726664 44.59164413116013, 69.50704959377795 41.98153300463908, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.56727982892488 44.21298285940858, 70.16538003709445 41.22421046113598, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.82153976332255 46.566397388815645, 71.68688449802717 45.570932144656396, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.10737221247501 46.23758476955204, 72.2585493963321 44.9133069061292, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C17.131241887807846 0, 34.26248377561569 0, 72 0 M0 0 C16.469169813394547 0, 32.938339626789094 0, 72 0 M72 0 C72 17.891359259188174, 72 35.78271851837635, 72 46 M72 0 C72 16.85603842586279, 72 33.71207685172558, 72 46 M72 46 C50.652469736337665 46, 29.304939472675322 46, 0 46 M72 46 C44.20580386519432 46, 16.41160773038864 46, 0 46 M0 46 C0 34.4678519949317, 0 22.935703989863395, 0 0 M0 46 C0 30.86363368183374, 0 15.727267363667483, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1523.662496303048 2915.0895592402812) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1196.0885167112117 2909.119093132542) rotate(0 71.39795918367349 18.459183673469397)"><path d="M-1.1418243795633316 -1.0046259015798569 L144.4990232060759 -1.426329717040062 L144.43414712651656 35.85880122166503 L1.6835669130086899 36.37682518940795" stroke="none" stroke-width="0" fill="#fff"></path><path d="M-0.3727961629629135 0.7564176172018051 C36.729751035495084 -0.37391743593526106, 72.55191798492964 0.4629704005210378, 141.37068775995658 -1.3066493421792984 M0.16271203011274338 0.27240557223558426 C33.05778231309082 -1.5164845041489725, 67.19790111901811 -0.5159411482072, 143.68534429325746 -0.5211303755640984 M141.7684805761187 0.5503796190023422 C142.3601679568647 11.758133697312084, 142.19128701738106 24.685477009020264, 144.12463072641776 35.19265205245841 M142.03591687931703 -0.03182869404554367 C142.0532299560963 14.845505480909212, 142.76549711651194 29.45545226572725, 142.76085312618898 36.28411140870679 M143.05794600351737 37.254392927702554 C102.65255512438566 38.71467735192449, 63.151354308730504 38.18086816689642, 1.720610037446022 36.485484903868326 M142.3368285442798 36.1935180706941 C98.39024040980306 35.308574170508216, 51.90110314202552 35.750081271567176, -0.49301130324602127 36.76974835824598 M-1.9394584745168686 37.318343943174966 C-0.6244689786692664 25.33277080455906, -1.769820865418534 14.929091586841594, 0.5810288339853287 -1.157154694199562 M0.27907007187604904 37.90980687570203 C-0.48869153535335613 27.076435881016785, 0.3116646143201058 17.86483765244482, -0.028601713478565216 -0.6333113238215446" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1230.736475894885 2915.5782768060126) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1261.236475894885 2915.0782768060126) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1350.236475894885 2908.0782768060126) rotate(0 17.75 17.75)"><path d="M-1.426329717040062 1.6382287591695786 L34.440433874726295 1.6835669130086899 L34.958457842469215 34.71851383149624 L0.8811601549386978 33.981839045882225" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-1.1716939359903336 1.1620158106088638 C9.653480711206795 -1.6089075982198118, 18.66992427930236 -1.4540733277425169, 36.198420867323875 1.4736865907907486 M-0.46430235356092453 -0.33890873938798904 C11.763868709281088 1.1416969546303153, 21.93412503823638 0.767912174127996, 36.01372999697924 0.26068418473005295 M34.278960570693016 -0.794292077422142 C34.32482313815504 10.155010279640555, 33.87197708789259 19.649615981429815, 33.65919195115566 33.854674234986305 M35.58365911990404 0.30937688797712326 C35.42046035733074 7.916929476335645, 34.85444487538189 15.735595040768386, 34.934196658432484 35.606899194419384 M37.1436623185873 36.723780527710915 C25.494754079356788 37.27222864385694, 12.836919448524714 35.88320915456861, -1.7926498800516129 34.497640028595924 M34.60587138682604 35.017713479697704 C26.027147359773515 35.291196866966786, 17.310918755084277 35.79700498197228, -0.285099558532238 35.459210090339184 M-0.46284259855747223 33.624925032258034 C0.18523628171533346 28.873567626252772, -1.2281505400314927 20.07083387747407, 1.4420164674520493 -0.423099622130394 M-0.6386323496699333 36.077849082648754 C-0.9988924947008491 23.83069953136146, -0.9852723565325141 11.647690299898386, -0.5023129507899284 0.8515524193644524" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1351.236475894885 2909.0782768060126) rotate(0 17.5 4.5)"><path d="M1.6382287591695786 -1.0595661252737045 L36.68356691300869 -0.5415421575307846 L34.21851383149624 9.881160154938698 L-1.518160954117775 9.852583780884743" stroke="none" stroke-width="0" fill="#000"></path><path d="M1.1620158106088638 -0.33480609953403473 C10.398500222712755 -1.10160290338099, 22.37706532329321 0.9034673061221838, 36.47368659079075 0.32542406022548676 M-0.33890873938798904 0.5362410023808479 C12.342845428735018 -0.35862455181777475, 23.422480151057243 0.6762358207255602, 35.26068418473005 -0.5137188956141472 M34.64256856516003 -0.5727911598980426 C35.56977706037462 2.1710989460349084, 35.87051556192338 5.0669780351221565, 34.25960340574384 8.315998660773039 M35.13921959958971 -0.030708205327391602 C34.947960516959434 2.3859390031546357, 34.88796239808202 5.08809684701264, 35.04810463748872 9.058956218138338 M36.223780527710915 10.109853133559227 C25.632565271109343 8.854758462682366, 13.15550101548433 7.4949910257011645, -1.0023599714040756 8.081820353865623 M34.517713479697704 8.547378487884998 C26.54396116361022 8.538435996696354, 19.086741745471954 8.401955426856876, -0.04078990966081619 8.030270762741566 M-0.8437837354838849 9.115304457396268 C0.29430051393806944 5.599290919303893, -0.42357212953269485 3.888928473740816, -0.1903948299586773 0.25116306468844407 M0.2600320871919393 8.708880842104554 C0.007154864966869359 5.12271432839334, -0.23688484936952597 1.5893680203706027, 0.38319858871400353 -0.320924186334014" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1358.736475894885 2900.5782768060126) rotate(0 3 7)"><path d="M-1.0595661252737045 1.6835669130086899 L5.458457842469215 -0.7814861685037613 L6.881160154938698 12.481839045882225 L0.8525837808847427 13.071687087416649" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.10044182986021044 -0.42756918221712115 C1.7184451818466187 -0.41597937539219854, 4.430406229197979 0.40724550291895867, 6.097627218067646 0.16344334334135058 M0.16087230071425435 0.26682777777314187 C1.2172873176634313 0.1750268679857254, 2.9013718597590925 0.09853977978229525, 5.845884331315756 0.08255694285035131 M5.108991529047489 0.9300986513495446 C4.706526533812285 5.160126301646233, 4.6259616057574755 6.372696371376515, 4.93599791675806 13.955439828336239 M5.952231680601836 -0.024545668810606047 C5.5677331200242035 3.5180775426328186, 5.615649937689304 7.3467366732656965, 6.091709672659635 14.117608953267336 M6.332955940067768 14.516183011233807 C3.982892552018165 14.136190580278635, 1.6877196058630939 13.736005424410104, -0.27545389384031294 13.565090434253216 M5.8642135463655 13.852096609026194 C3.6750624664127827 13.974901852309705, 1.3537664584815499 13.958825835883617, -0.2909187711775303 14.059996489435434 M0.17936248928308496 14.40672018378973 C-0.7776093040406704 9.600835561752318, 1.0418105091154577 6.325442577898501, 0.3906981006264687 1.3880153402686117 M-0.4528520233929157 13.979978800565004 C-0.3539093977212906 9.711871360987423, -0.26221053659915916 6.819106481224297, -0.4992154009640216 0.5733800657093524" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1373.7364758948847 2900.5782768060126) rotate(0 3 7)"><path d="M1.6835669130086899 -0.5415421575307846 L5.218513831496239 0.8811601549386978 L4.481839045882225 14.852583780884743 L-0.9283129125833511 15.40739668905735" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.42756918221712115 -0.3919948026537895 C1.0673729181289673 0.21957044318318364, 3.3390896156430245 0.10767140105366711, 6.163443343341351 -0.4263735696673393 M0.26682777777314187 -0.1563391126692295 C1.6761109314858915 0.1219445389509201, 3.1210424311459066 -0.186290128827095, 6.082556942850351 -0.09381167069077492 M6.930098651349544 -1.2080007061362266 C7.342313652783632 2.0567067652940754, 4.792671519070863 5.626542715728283, 5.955439828336239 13.435953618586064 M5.975454331189394 -0.4439791567623615 C5.925436688363552 3.921060428768396, 6.118142200410366 8.725955208390952, 6.117608953267336 14.10178316757083 M6.516183011233807 13.870135267078876 C3.8063784688711166 13.495500629991293, 1.1130946174263954 13.757841700166464, -0.4349095657467842 14.48035183697939 M5.852096609026193 13.955414303392171 C4.740316211432219 13.88731284171343, 3.460717362910509 13.68192393809557, 0.05999648943543434 13.890541537851096 M0.40672018378973007 13.189991714060307 C-0.578715347200632 11.133401346206664, -0.16491448774933815 6.638677738606929, 1.3880153402686117 -0.3115098938345908 M-0.020021199434995696 13.556682073324918 C-0.48053740441799164 10.342466223984957, 0.5151878219842909 6.888955178111791, 0.5733800657093524 -0.37084814384579656" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1367.236475894885 2917.0782768060126) rotate(0 -0.5165169888082914 12.3095419742167)"><path d="M-1.3066493421792984 -1.2703963369131088 C0.8331094544008374 10.96692061636597, 0.6391630425676703 19.527716119103133, -0.9212452322244644 25.57139529287815 M-0.5211303755640984 0.5156411454081535 C0.7888884256407619 7.308128895722331, -0.10550590392202136 14.37452636111528, 0.18729443103075027 25.889480285346508" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1357.736475894885 2916.0782768060126) rotate(0 -0.308343032559776 14.058291979134083)"><path d="M-1.2703963369131088 0.6984208673238754 C1.415784153267741 8.259962144233288, 0.6285385544598103 14.44456431243569, -0.42860470712184906 27.322182521224022 M0.5156411454081535 0.5137299969792366 C0.6865354941040278 9.390361562930048, 0.8778531689941884 19.492849696241322, -0.11051971465349197 27.60285396128893" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1377.2364758948847 2917.5782768060126) rotate(0 0.5400990881577172 13.416583094745874)"><path d="M0.6984208673238754 1.4736865907907486 C0.5019339735433459 6.387170092575253, -0.9016276144608855 10.74658521655947, 0.8221825212240219 26.572482004761696 M0.5137299969792366 0.26068418473005295 C-0.06319325249642127 10.185491303391755, 0.721233963035047 19.217817890308798, 1.102853961288929 24.863565377891064" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1386.2364758948847 2925.5782768060126) rotate(0 -16.581374015659094 0.6196436536490637)"><path d="M1.4736865907907486 0.32542406022548676 C-7.433884973824025 -1.3025136847794057, -16.885996943712236 -0.5590859313309193, -32.927517995238304 1.7788518518209457 M0.26068418473005295 -0.5137188956141472 C-8.152966913580896 -0.13803736999630928, -17.466833160817625 -0.22247783973813057, -34.636434622108936 0.664356179535389" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1385.7364758948847 2934.5782768060126) rotate(0 -17.505109880119562 0.33789916107161844)"><path d="M0.32542406022548676 0.5448111444711685 C-9.93103972144425 -0.42686093762516975, -17.70089856155217 1.8107059574127198, -34.221148148179054 -0.042260751128196716 M-0.5137188956141472 0.2751898095011711 C-12.96387639246881 -0.16510929360985754, -25.704624985679985 0.16053991347551344, -35.33564382046461 0.1371423527598381" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1830.430097077814 2584.8713272638206) rotate(0 412 257.81944444444457)"><path d="M0 0 C289.3018495619297 0, 578.6036991238594 0, 824 0 M0 0 C266.4378617227078 0, 532.8757234454156 0, 824 0 M824 0 C824 168.13916297101736, 824 336.2783259420347, 824 515.6388888888891 M824 0 C824 103.9498607670889, 824 207.8997215341778, 824 515.6388888888891 M824 515.6388888888891 C509.1749795854091 515.6388888888891, 194.34995917081824 515.6388888888891, 0 515.6388888888891 M824 515.6388888888891 C495.227893358469 515.6388888888891, 166.45578671693795 515.6388888888891, 0 515.6388888888891 M0 515.6388888888891 C0 387.63720168262324, 0 259.63551447635734, 0 0 M0 515.6388888888891 C0 312.98472778310804, 0 110.330566677327, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1923.430097077814 2654.8713272638206) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1957.8467637444805 2719.272120914614) rotate(0 31 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current</text></g><g transform="translate(1909.2526079003255 3004.620764177708) rotate(0 288.5 28.5)"><path d="M0 0 C192.039151353389 0, 384.078302706778 0, 576.9999999999999 0 M0 0 C136.73794285580513 0, 273.47588571161026 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 22.734716738015415, 576.9999999999999 45.46943347603083, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 19.196387576311828, 576.9999999999999 38.392775152623656, 576.9999999999999 57 M576.9999999999999 57 C404.0372541241347 57, 231.07450824826947 57, 0 57 M576.9999999999999 57 C368.86377132609476 57, 160.72754265218964 57, 0 57 M0 57 C0 41.1177114598453, 0 25.235422919690603, 0 0 M0 57 C0 44.01384368017316, 0 31.027687360346317, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2404.252607900325 3004.620764177708) rotate(0 41 29)"><path d="M0 0 C26.443931220471864 0, 52.88786244094373 0, 82 0 M0 0 C24.761952497065067 0, 49.523904994130135 0, 82 0 M82 0 C82 18.225900404155254, 82 36.45180080831051, 82 58 M82 0 C82 14.270743449032308, 82 28.541486898064615, 82 58 M82 58 C64.16376731544733 58, 46.32753463089466 58, 0 58 M82 58 C50.749854277074334 58, 19.49970855414867 58, 0 58 M0 58 C0 42.38057781010866, 0 26.761155620217323, 0 0 M0 58 C0 43.396501176059246, 0 28.793002352118492, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2443.252607900325 3016.620764177708) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2443.252607900325 3016.620764177708) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-3.672683048188459 23.909386256288435, -1.5310236598405504 29.79354706593731, 0 34 M-5.814342436536368 18.025225446639556 C-4.438116949352489 21.8063738967552, -3.0618914621686106 25.587522346870845, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2443.252607900325 3016.620764177708) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C3.672683048188459 23.909386256288435, 1.5310236598405504 29.79354706593731, 0 34 M5.814342436536368 18.025225446639556 C4.438116949352489 21.8063738967552, 3.0618914621686106 25.587522346870845, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1940.238523322765 3020.620764177708) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(2138.8360795545505 2714.400990817485) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1960.2653209606888 2956.104576069344) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(2314.979606674974 2947.3188617836304) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.6911573955638532 4.151558307328436, 3.643771615189155 1.9053325975809834, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.7997554922365635 5.176998895052827, 1.8609678085345753 3.956213773029765, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.062912716240784 7.520178263586619, 7.992680051613957 2.999498073244231, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C4.049132298871088 7.536030820367036, 7.965119216874566 3.0312031868050653, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.8136656135422236 12.75354819572466, 9.755642670278286 7.068453920444423, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.449655413071555 14.322662437473312, 7.027622269336949 10.206682403941727, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.600209395927803 17.945745424395433, 10.934128030120384 11.80977394093285, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C7.778368749644854 15.440059717986403, 15.290446737554486 6.798402528114792, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C9.557007825434983 19.49098186417192, 19.109181713196193 8.50246280340994, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C8.269400297414974 20.97220488530608, 16.533966657156174 11.46490884567826, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C12.381592583292894 22.33868114203743, 25.019808052973467 7.800077342065066, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C10.375073706732453 24.646917066125155, 21.006770299852583 12.416549190240513, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.866313075108254 27.878086348743665, 25.594646831675124 13.235813318624427, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C9.116221042623618 32.19207374708522, 18.094462766705853 21.863788115307525, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C16.041378277651415 30.322603995694497, 30.89411600284188 13.23648375589574, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C10.575569958155524 36.61029720636832, 19.9624993638501 25.811870177243396, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C21.993421283387114 29.57257411029863, 37.15512757746016 12.131026190033062, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C19.326236998072215 32.64081864836124, 31.82075900683036 18.26751526615828, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C20.41523953522264 37.485076881063115, 29.0117486732686 27.59592435626833, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C24.005080671428516 33.3554370510295, 36.19143094568036 19.33664469620109, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C27.30168755303564 35.66013699022456, 37.141570272041484 24.340646779520274, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C29.966561935749773 32.59454969113763, 42.471319037469755 18.20947218134641, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C34.096683995671754 33.94040010226008, 45.7445477494511 20.5410656282976, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C34.61132520278209 33.34837311654626, 46.77383016367178 19.35701165686996, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C37.873037623192126 35.69321454148944, 47.65418056763873 24.44129671168539, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C42.805641640247245 30.01890271493779, 57.51938860174897 13.092673058582086, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C41.52728154943033 37.58650012328902, 49.97565301225252 27.867760499990823, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C45.01406722213595 33.57541204265762, 56.949224357663766 19.84558433872802, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C50.628924475497925 33.21326999415753, 62.53586442753459 19.5159024466569, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C49.1254441827442 34.94282622382081, 59.52890384202714 22.97501490598346, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C52.9683870680543 36.61903848462857, 62.22777420478474 25.967332052305267, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C52.332436774433255 37.35061561097314, 60.95587361754265 27.430486304994417, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C54.29645130424312 41.188287691380104, 59.24082824030926 35.500432670737396, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C55.52636925090044 39.77342894207139, 61.7006641336239 32.67071517211997, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C61.848158492637964 38.59805466809013, 69.35722720923634 29.95985924886374, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C61.44910340704825 39.05711503129342, 68.55911703805691 30.877979975270318, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C65.10228084440658 40.95162746820704, 70.22239747592046 35.061607054026624, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C63.95140054181702 42.27556380879902, 67.92063687074135 37.70947973521058, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.92874239103999 43.7971677475582, 70.88830516132467 40.39258023743522, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.98336749725237 43.734328751130384, 70.99755537374942 40.2669022445796, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C71.01105223518607 46.34838822840949, 72.0659094417542 45.134913823844094, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.2324011335524 46.093755448755694, 72.50860723848686 44.625648264536494, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C23.49195336699486 0, 46.98390673398972 0, 72 0 M0 0 C20.274025350809097 0, 40.548050701618195 0, 72 0 M72 0 C72 16.314749504625798, 72 32.629499009251596, 72 46 M72 0 C72 17.762838457524776, 72 35.52567691504955, 72 46 M72 46 C46.1160380423069 46, 20.232076084613794 46, 0 46 M72 46 C43.98897323012352 46, 15.97794646024704 46, 0 46 M0 46 C0 36.70909492820501, 0 27.418189856410027, 0 0 M0 46 C0 28.832874299585818, 0 11.665748599171636, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1956.8630286524176 2765.9664198203873) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(2143.3523444624875 2767.0952897232573) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">3</text></g><g transform="translate(1956.8630286524176 2816.9664198203873) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(2143.3523444624875 2824.0952897232573) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">1</text></g><g transform="translate(1956.8630286524176 2880.9664198203873) rotate(0 53.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">after update</text></g><g transform="translate(2143.8523444624875 2882.0952897232573) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">17</text></g><g transform="translate(2109.3576865574523 2756.0308547718223) rotate(0 52.5 20.5)"><path d="M0 0 C31.698343444615606 0, 63.39668688923121 0, 105 0 M0 0 C31.209965955466036 0, 62.41993191093207 0, 105 0 M105 0 C105 11.741622019559145, 105 23.48324403911829, 105 41 M105 0 C105 9.947629534453155, 105 19.89525906890631, 105 41 M105 41 C79.51778180524707 41, 54.03556361049414 41, 0 41 M105 41 C71.71336268261075 41, 38.4267253652215 41, 0 41 M0 41 C0 31.30118932649493, 0 21.602378652989863, 0 0 M0 41 C0 28.524867499619724, 0 16.049734999239444, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2110.3576865574523 2813.0308547718223) rotate(0 51 22.5)"><path d="M0 0 C28.886803181469443 0, 57.773606362938885 0, 102 0 M0 0 C37.173303835093975 0, 74.34660767018795 0, 102 0 M102 0 C102 13.430311616510155, 102 26.86062323302031, 102 45 M102 0 C102 16.05211198702455, 102 32.1042239740491, 102 45 M102 45 C62.9927919074893 45, 23.985583814978597 45, 0 45 M102 45 C75.50919366627932 45, 49.01838733255863 45, 0 45 M0 45 C0 35.4621723331511, 0 25.924344666302204, 0 0 M0 45 C0 31.916056986898184, 0 18.832113973796368, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2331.979606674974 2957.8188617836304) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(2415.162496303048 2947.242131981392) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.4970441935106777 4.374860002394918, 3.255545211082804 2.3519359877139463, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.0193498443811764 4.924384489941499, 2.300156512823801 3.450984962807109, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.695068028794355 7.943335170789077, 7.256990676721099 3.8458118876491483, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.2546144924552527 8.450019003842351, 6.376083604042894 4.8591795537556965, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C6.041150369986185 11.341488511566151, 12.210612183166209 4.244334552127405, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.344291267072877 12.143133207841995, 10.816893977339593 5.847623944679093, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C8.414446457345543 14.708336018509971, 16.56260215295586 5.33495512916193, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.4214844934095785 17.000976498615042, 12.576678225083935 9.92023608937207, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C6.6772876169141355 22.8037210136902, 13.349741296154498 15.127941102446501, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C6.9544811298598415 22.484846353710854, 13.90412832204591 14.490191782487804, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C6.872640764250068 28.676005271567007, 14.001904414887814 20.474725601124224, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C10.997917141478736 23.93041765614801, 22.25245716934515 10.983550370286228, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C11.256541242895041 29.72991700735603, 22.3751031672487 16.939474635849148, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C11.845267458656053 29.05266496824178, 23.552555598770724 15.58497055762065, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C16.24604414541426 30.08716284738364, 31.303447738367574 12.76560145927403, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C15.538740655247599 30.900822436788527, 29.88884075803425 14.392920638083801, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C15.158556348394061 37.43518679913732, 23.485397707474053 27.85625156771044, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C20.84115003976956 30.898110545505528, 34.85058509022505 14.78209906044686, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C21.211286517846283 36.56932958158926, 30.60384263851589 25.76442975732062, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C23.229577828526097 34.24755102121452, 34.640425259875514 21.120872636571143, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C30.306104344153184 32.20395083159861, 43.150403854276576 17.42827446226838, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C29.255969708044375 33.41199254030672, 41.05013458205896 19.84435787968459, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C35.24597035382438 32.6182973849912, 48.04312046575637 17.896860193759842, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C32.40388163119093 35.88774646202785, 42.35894302048945 24.435758347833136, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C39.423679421275665 33.90940520605776, 50.7554641638058 20.873678040822032, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C36.86256421475625 36.85563122689101, 45.63323375076697 26.766130082488527, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C49.00458898942211 28.98484187324387, 64.93026789223609 10.66444399990052, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C43.409127522921416 35.42168396812881, 53.7393449592347 23.5381281896704, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C46.490976769857184 37.97343430545929, 54.25996901625312 29.036231069260428, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C48.95423324235933 35.13978188061016, 59.186481961257414 23.36892621956216, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C54.52169625510804 34.8321606691958, 65.33439257889222 22.39357642143973, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.29617725229622 38.54269582692153, 58.88335457326858 29.814646736891184, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C58.04537107987899 36.87564882028253, 66.73866779158101 26.87515492854224, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C54.34157254094606 41.13638164618229, 59.33107071371516 35.396620580341775, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C61.493812828987195 39.005682724789736, 68.6485358819348 30.775115362262955, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.64835957910307 41.12863384045977, 64.95762938216656 35.02101759360303, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C62.83992437265473 43.554170879182365, 65.69768453241676 40.26669387597726, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.76384713880717 41.34095091106735, 69.54553006472162 35.84025393974723, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.32639823571641 44.490085434116665, 69.68361685067751 41.77841561055215, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.3437430886772 44.47013246324269, 69.71830655659907 41.7385096688042, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C71.15470381114815 46.183135993775196, 72.35321259367839 44.8044093545755, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C70.82261474171672 46.56516076763254, 71.68903445481551 45.5684589022902, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C26.955635350942615 0, 53.91127070188523 0, 72 0 M0 0 C26.84984226822853 0, 53.69968453645706 0, 72 0 M72 0 C72 16.324836088716985, 72 32.64967217743397, 72 46 M72 0 C72 16.064654491841793, 72 32.129308983683586, 72 46 M72 46 C47.6831055700779 46, 23.36621114015579 46, 0 46 M72 46 C47.87400836348533 46, 23.74801672697067 46, 0 46 M0 46 C0 28.00655534118414, 0 10.013110682368279, 0 0 M0 46 C0 30.18186087757349, 0 14.36372175514698, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2423.662496303048 2957.742131981392) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(2096.0885167112115 2951.7716658736545) rotate(0 71.39795918367349 18.459183673469397)"><path d="M-1.39677394926548 0.32469500601291656 L144.1485536168425 1.0561283975839615 L143.16979814275192 36.469005918320306 L0.8744791597127914 36.901885843094476" stroke="none" stroke-width="0" fill="#fff"></path><path d="M1.97709359228611 0.2847932428121567 C45.81741551317428 -1.4418476016348118, 94.41446656003902 -1.710245409709763, 144.25420788629935 1.0933693498373032 M0.36778729408979416 -0.539526991546154 C29.279434256453307 -1.328064804577402, 57.55820005426604 -0.7314215951200045, 143.34479577793763 0.8614866212010384 M142.79116085871146 -1.6496993452310562 C142.0197555263934 12.957873272211563, 143.791121168941 28.65417574805262, 143.7805507551043 38.108356779631265 M143.4031320358722 0.810993380844593 C142.26080883488825 14.157132057313394, 143.62355817303828 30.160688031145476, 143.17326481594728 37.808787731643804 M142.3686511884539 36.304374998625406 C109.32375555161619 36.44563753010511, 71.09515935325503 34.99013645054578, 0.7016643434762955 34.95789129119743 M142.0741914059131 36.43621197175611 C86.5527700301488 35.87242209359239, 29.673369833583735 36.724482173166976, 0.9116184040904045 37.65035119485486 M0.7120059877634048 38.15940267425407 C-1.6189305448547313 27.43336544385063, 0.17987040042725105 15.783362759345628, 0.8774517923593521 -0.5245310217142105 M-0.6241615489125252 36.01623979043592 C-0.7518961163610218 25.33034535988554, -0.20926091395318497 13.12852838294845, -0.6112461760640144 0.5358352139592171" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2130.7364758948847 2958.2308495471234) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2161.2364758948847 2957.7308495471234) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2250.2364758948847 2950.7308495471234) rotate(0 17.75 17.75)"><path d="M1.0561283975839615 0.3738797754049301 L35.05063857138157 0.8744791597127914 L35.48351849615574 35.92132793366909 L1.9206861406564713 36.94069562852383" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.055244579911231995 1.2888831049203873 C13.835698738321664 1.590593797005713, 29.412051602452998 -1.8358097254857422, 36.66893516480923 1.669340506196022 M-0.1361897513270378 -0.015486307442188263 C12.416098352149131 0.3740075787529349, 24.77277640923858 0.6458221634849906, 35.12330622226 0.6146547123789787 M34.35250221192837 1.1342719942331314 C35.65061199370771 11.626187809929252, 36.39716779891401 23.263016345351936, 35.518439158797264 33.57663430273533 M34.9268779233098 0.824236087501049 C36.48808241333813 13.18295497186482, 36.6783230349049 25.09360714480281, 35.95243377238512 34.930626802146435 M35.840311869978905 34.69427575170994 C24.49321003295481 36.55228977914899, 13.756322429329153 37.71093874689191, -0.8161541372537613 34.9236406236887 M34.865563578903675 34.619517259299755 C27.62975437156856 36.36826742265374, 20.056245274096728 35.048971777893605, -0.9880843684077263 35.67945259064436 M0.08543048799037933 35.31508578360081 C-2.2144235760346054 24.463641164079306, -0.7448147923126817 12.03542957678437, 0.8267763704061508 1.3221758753061295 M0.1576504185795784 35.44335525482893 C0.021761129833757886 26.45452732257545, 0.8560904965177178 17.933160022646188, 0.16234750300645828 0.6763176247477531" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2251.2364758948847 2951.7308495471234) rotate(0 17.5 4.5)"><path d="M0.3738797754049301 -0.4493614286184311 L35.87447915971279 -0.01648150384426117 L35.42132793366909 10.920686140656471 L1.4406956285238266 8.818684473633766" stroke="none" stroke-width="0" fill="#000"></path><path d="M1.2888831049203873 -0.32364238798618317 C12.456677693873644 1.1030371513217687, 20.028662346303463 1.1786029662936925, 36.66934050619602 0.7355745881795883 M-0.015486307442188263 0.46045345813035965 C9.16053294017911 0.9091530341655016, 18.044003054499626 -0.32902736477553846, 35.61465471237898 -0.0023787543177604675 M35.51042239740491 0.199545132368803 C35.41441536031663 2.4931305393576624, 35.86228826127947 3.923568210750818, 34.1344854362309 9.546492301672696 M35.37090623937547 0.3150808934122324 C35.12133970931173 3.830583215877414, 34.71860935166478 7.0633832830935726, 34.7437820609659 8.903864884749055 M34.19427575170994 7.8848107904195786 C26.276150953024626 7.099078521504998, 18.009286530315876 8.243400439992547, -0.5763593763113022 7.556546077132225 M34.119517259299755 9.21762365847826 C27.099162217229605 9.509931100532413, 16.967410385608673 7.78986288420856, 0.1794525906443596 9.356002993881702 M-0.08321139737963679 8.10263708755374 C0.48412048883736136 5.505495142936707, 0.25264005728065975 2.8551175720989703, 0.5949791438877582 -0.5617453940212727 M-0.025490135326981522 8.865677631273865 C-0.22112558394670487 7.3530406471341845, -0.2603389433026314 5.29688597805798, 0.30434293113648897 0.23762888945639132" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2258.7364758948847 2943.2308495471234) rotate(0 3 7)"><path d="M-0.4493614286184311 0.8744791597127914 L5.983518496155739 0.42132793366909027 L7.920686140656471 15.440695628523827 L-0.18131552636623383 13.718226775527" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.09709271639585493 0.4374868556857109 C1.9043979406356812 -0.6291006942093372, 3.503454820811749 0.4621356587111949, 6.2206723764538765 -0.3237161949276924 M0.1381360374391079 0.16466322317719456 C2.354000631719828 0.24713677704334253, 4.0781011573970325 0.18532559216022487, 5.999286373704672 -0.24745490178465843 M6.31040353924036 0.6892426714301108 C6.752667757719754 5.13996829688549, 5.93258256599307 6.161712904274465, 6.850099135935306 15.13539073318243 M6.490125834196806 0.2641425140202045 C6.54305717855692 3.0837545178830625, 6.236466549932957 6.493601659685374, 5.850456487387419 13.785102678090334 M5.665443237125873 14.210499303042889 C3.019250813126564 14.533922265917063, 0.9699410185217858 13.802920459657908, -0.43303617686033247 13.710706774890422 M6.065287097543478 14.273485521227121 C4.492864418774843 13.824867863357067, 2.2501132182776926 14.001006812751294, 0.10680089816451072 14.1861552990973 M-1.395897863805294 14.614216254651547 C-0.3731518493592739 11.442736196517943, 0.572763325124979 9.302724339067936, -0.8738261684775352 -1.2629785791039465 M-0.20894590690732 13.57212767675519 C0.35301359117031095 10.150121044367552, 0.09157219350337978 7.751406190544366, 0.36964493915438656 0.13085792139172558" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2273.7364758948847 2943.2308495471234) rotate(0 3 7)"><path d="M0.8744791597127914 -0.01648150384426117 L6.42132793366909 1.9206861406564713 L7.440695628523827 13.818684473633766 L-0.2817732244729996 12.524681463837624" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0.4374868556857109 0.32801080495119095 C1.1929924249649049 0.36038982108235357, 4.067655403912068 0.23038164809346198, 5.676283805072307 0.29555464535951614 M0.16466322317719456 0.2584459863603115 C1.9644937478005886 -0.14063534080982207, 3.620968712121248 -0.028340833783149694, 5.752545098215341 -0.09556404724717141 M6.689242671430111 0.8329926028847692 C7.4653328903019425 3.4096589595079427, 4.73304305151105 7.643602232635022, 7.13539073318243 13.246081449091434 M6.264142514020205 0.6232942692935466 C5.753066121041774 3.7143327333033085, 5.716831813752651 6.645714672654868, 5.785102678090334 14.69137409850955 M6.210499303042889 13.411857183277608 C4.6560004323720925 13.72169808253646, 2.022905065119266 13.533508146852256, -0.28929322510957717 13.926730735599994 M6.273485521227121 14.219595154374838 C4.458358680456877 13.716632108986378, 3.2151594825088976 14.119858317673208, 0.18615529909729955 13.702442725747824 M0.6142162546515464 13.632828284800052 C0.26707957848906516 10.318942117691039, 0.938467107862234 4.606571148335933, -1.2629785791039465 -0.8389919266104697 M-0.42787232324481006 14.375084649771452 C-0.48094061791896814 9.69293266609311, 0.48135260164737703 5.528223767131567, 0.13085792139172558 -0.15727650001645088" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2267.2364758948847 2959.7308495471234) rotate(0 0.8333176635205746 12.12975324690342)"><path d="M1.0933693498373032 -1.9681140035390854 C1.3082746260240674 11.323464896418153, 1.0571361270174384 18.23284052114934, 1.4851821511983871 26.227620497345924 M0.8614866212010384 0.8206919953227043 C-0.10544792670756575 8.520871610604228, 0.41809278134256606 14.990328510887922, 0.181453175842762 25.926251105964184" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2257.7364758948847 2958.7308495471234) rotate(0 -0.5737110041081905 14.095221109688282)"><path d="M-1.9681140035390854 1.168935164809227 C2.015409979149699 8.67974182162434, -0.5910755698382855 16.876250041462484, 0.22762049734592438 27.969027385115623 M0.8206919953227043 -0.3766937777400017 C0.9413630602508783 9.682583495341241, -0.3251186518371105 19.05568510826677, -0.07374889403581619 28.567135997116566" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2277.236475894885 2960.2308495471234) rotate(0 0.845221109688282 13.517780814319849)"><path d="M1.168935164809227 1.669340506196022 C1.4343508942052723 8.445026008598507, 2.243330171145499 18.761019625701014, 1.4690273851156235 26.42090691626072 M-0.3766937777400017 0.6146547123789787 C0.7496319456771017 8.212477344460787, 0.9657954874262213 17.513215520046653, 2.0671359971165657 25.721716813743114" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2286.2364758948847 2968.2308495471234) rotate(0 -16.05447134003043 0.48556103962073394)"><path d="M1.669340506196022 0.7355745881795883 C-13.137781877815724 1.1839011292159558, -24.38983529210091 0.16774636313319208, -33.07909308373928 1.0977548211812973 M0.6146547123789787 -0.0023787543177604675 C-13.95390418469906 -0.44662270858883857, -26.83185791820288 0.43906011268496514, -33.778283186256886 0.49231619387865067" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2285.7364758948847 2977.2308495471234) rotate(0 -17.38605460897088 0.8219596296548843)"><path d="M0.7355745881795883 -1.079053983092308 C-6.266367966607214 0.22969608828425414, -14.539917526319625 1.6245768070220947, -34.9022451788187 2.7229732424020767 M-0.0023787543177604675 -0.8248496726155281 C-14.076804945096374 0.029596001952886586, -26.945613306984306 1.2071309092640878, -35.50768380612135 1.5949947163462639" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1824.15996631772 1770.2890595675917) rotate(0 244 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: initial state</text></g><g transform="translate(1855.6599663177199 2520.2890595675917) rotate(0 275 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: updating stock</text></g><g transform="translate(1230.1599663177199 1685.2890595675917) rotate(0 98 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">alternative 1</text></g><g transform="translate(2047.6599663177199 1692.7890595675917) rotate(0 98 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">alternative 2</text></g><g transform="translate(1410.3187679866014 1348.8928867907737) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.4245540883265806 4.458250329234831, 3.1105650007146095 2.5187166413937736, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.0416068012916408 4.8987807898708216, 2.34467042664473 3.399777562665754, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.2858567810230084 8.414078862104724, 6.438568181178406 4.78729927028044, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.2853176939057724 9.56506741811414, 4.437490006943934 7.089276382299275, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.912805019729161 12.639501354936556, 9.953921482652161 6.840360238868213, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.6729466808315223 14.06579521743392, 7.474204804856884 9.69294796386294, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.506626709289865 15.75266317612456, 14.746962656844508 7.423609444391108, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C7.989401072722355 15.19729480061559, 15.712511383709488 6.312872693373166, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.108748000762871 22.307382619143166, 14.212662063851969 14.13526431335243, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C5.260777704715555 24.433229265198854, 10.516721471757338 18.386957605463806, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C7.808664564618157 27.599233063216616, 15.873952015623992 18.321181184423438, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.298340080009983 28.1862942277412, 14.853303046407644 19.49530351347261, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C9.780493364357602 31.427915854371093, 17.454830323202298 22.599601070547592, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C13.368263852888148 27.300658032005664, 24.63037130026339 14.345085425816738, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C14.974944290744936 31.549395962618448, 22.85671676811436 22.482453911748596, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C19.465076460800553 26.384089769939752, 31.836981108225594 12.151841526391202, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C24.22926903898746 27.000525488899985, 35.72229182774629 13.779315169240725, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C23.12470329438591 28.27118302518816, 33.51316033854319 16.320630241817078, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C31.052683527570583 25.24809737868312, 44.382105397049926 9.914351573513276, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C29.795719308109998 26.694069305757793, 41.86817695812876 12.806295427662622, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C32.38282151190626 29.81496101108199, 42.055365957858655 18.687971463017305, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C33.30055899329363 28.75922480637138, 43.890840920633394 16.576499053596084, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C40.44468713249262 26.63785784472148, 52.53602276217828 12.728367335225354, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C39.953269725938405 27.203168903979936, 51.55318794906984 13.858989453742264, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C41.61400376214182 31.389725282758157, 49.88764061361407 21.871994836004983, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C47.01201809050106 25.18002013768735, 60.68366927033254 9.452584545863374, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C48.167500219764634 29.947802347897323, 57.35155909200658 19.38275117121238, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C50.12455077371315 27.696473219300575, 61.26566019990361 14.880092914018881, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C54.950211905547526 28.242197456107192, 65.92996705570975 15.6114340123384, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C54.90763642626359 28.291174942397724, 65.84481609714189 15.70938898491946, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C63.22322650475755 24.82219517552106, 76.83292181727667 9.166031656095196, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C63.59626442284176 24.393064139861472, 77.5789976534451 8.307769584776025, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C65.05761358779769 28.80898657540063, 75.51468057549434 16.779507080560627, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C64.76865042983721 29.141400663169186, 74.93675425957338 17.444335256097734, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.21785733748133 31.27055435347016, 76.19209363800852 22.097244841628747, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C70.07031189293934 29.139549157058624, 79.89700274892454 17.83523444880568, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C73.59383619649051 31.183210463001203, 81.95703594816428 21.562449685397112, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C72.01490616069215 32.99956169339599, 78.79917587656755 25.19515214618668, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C74.86289472535546 35.82033797130387, 78.85207856904103 31.231306906931508, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C75.0218753433517 35.637451691000514, 79.17003980503353 30.865534346324804, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.60436868969872 36.46290846588569, 83.34801108986494 32.156340520801436, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.53695074376839 36.54046394096367, 83.21317519800428 32.3114514709574, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.00849972587095 38.64391601465589, 84.51319872535629 36.9129578232709, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.04239395530837 38.60492516392397, 84.58098718423113 36.83497612180707, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C32.74580490000551 0, 65.49160980001102 0, 85.70000000000013 0 M0 0 C19.455593113824758 0, 38.911186227649516 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 13.62363718673587, 85.70000000000013 27.24727437347174, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 7.82143609598279, 85.70000000000013 15.64287219196558, 85.70000000000013 39 M85.70000000000013 39 C56.40713170640179 39, 27.114263412803453 39, 0 39 M85.70000000000013 39 C63.53586610801528 39, 41.37173221603042 39, 0 39 M0 39 C0 25.79555174186826, 0 12.591103483736518, 0 0 M0 39 C0 26.93629071637988, 0 14.872581432759759, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1425.6641836672989 840.473507170834) rotate(0 21.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1406.5687679866014 833.2238730819438) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.4555318372155774 4.422614505586104, 3.172520498492603 2.447444994096318, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.1870386413074132 4.7314805957126564, 2.6355341066762747 3.0651771743494245, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.762329949078644 9.016327589906023, 5.391514517289677 5.991796725883039, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.8222380466076165 8.947411207171978, 5.511330712347622 5.853963960414949, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C6.064106454627717 11.315080557041043, 12.256524352449272 4.1915186430771865, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.24002755519765 14.56381170244487, 6.608366553589139 10.68898093388484, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.425263986294751 15.846260282183612, 14.58423721085428 7.610803656509212, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.943864090256802 17.55041592104616, 11.621437418778381 11.019114934234302, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.471353066344916 21.89025320739928, 14.937872195016059 13.301005489864657, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C5.357450122959003 24.322020369401937, 10.710066308244233 18.164539813869972, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.23323120088127 25.960456610878424, 18.72308528815022 15.043628279747054, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C11.791742976225766 23.01722549501918, 23.84010883883921 9.157166048028564, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C16.01034648426053 24.261289643607526, 29.914536563008152 8.266348649020458, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C10.806458292634769 30.24767821396427, 19.506760179756633 20.23912578973395, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C16.157048766897635 30.189540319217848, 25.220925720419753 19.76274262494739, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C18.769447291424736 27.18431958953109, 30.445722769473957 13.752301165573876, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C20.590166252526892 31.18683436507417, 28.44408625482515 22.15193292158909, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.099060073144468 29.451050583984703, 31.461873896060304 18.680365359410164, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C26.69354294820576 30.2627149838195, 35.663824238320274 19.943586783786042, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C25.080546201339722 32.118255482364454, 32.4378307445882 23.65466778087595, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C34.63283553007527 27.226615968775953, 46.55539399419669 13.51128137840523, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C32.99233191221806 29.11379950269066, 43.274386758482265 17.285648446234642, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C35.61771520518492 32.19065385243897, 42.88207890756287 23.833959350660326, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C42.3260917323811 24.47354943180949, 56.29883196195524 8.399750509401372, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C47.27698140814668 24.87521470799536, 61.213595905623784 8.842973686479397, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C42.363685514622375 30.527315079234448, 51.38700411857516 20.14717442895757, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C50.201826110359974 27.607578113364703, 61.42021087319726 14.702302702147133, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C52.71637700351996 24.714918207524086, 66.44931265951723 8.9169828904659, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C56.20877578647401 26.794385329019903, 68.44709481756271 12.715809758163818, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C51.65038491822366 32.038214171619856, 59.330313081062 23.20346744336373, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C59.5617026089435 29.03429658755033, 69.50987402564857 17.59023448015374, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C63.703566274137145 24.269627480094943, 77.79360135603586 8.060896265242967, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C62.270941628361385 32.01468595882482, 69.94133665662174 23.190905847409006, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C66.07942668606204 27.633525069072714, 77.55830677202303 14.428584067904794, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.07941910245168 31.42980932539971, 75.91521716794921 22.41575478548785, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C70.04673423488033 29.16667215000598, 79.8498474328065 17.889480434700385, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C73.69913319971204 31.062080117120125, 82.1676299546073 21.320188993634957, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C72.41038734454995 32.54461263383561, 79.59013824428314 24.28525402706593, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C77.2753954624955 33.04507334090056, 83.67708004332113 25.680777646124895, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C75.51899407856129 35.06558200337773, 80.16427727545272 29.721794971079234, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C80.54315134759022 35.38296255500032, 85.22557640564797 29.996448699030687, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.81466061450901 36.22099527929023, 83.76859493948552 31.67251414761051, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.66022725489519 37.89418925515019, 85.81665378340477 35.41350430425949, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.60362085695817 37.95930746698352, 85.70344098753074 35.54374072792615, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C21.05272591106597 0, 42.10545182213194 0, 85.70000000000013 0 M0 0 C20.390299159362943 0, 40.780598318725886 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 12.150230193883182, 85.70000000000013 24.300460387766364, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 11.797666608542205, 85.70000000000013 23.59533321708441, 85.70000000000013 39 M85.70000000000013 39 C57.69139444358655 39, 29.682788887172975 39, 0 39 M85.70000000000013 39 C66.92966488234708 39, 48.15932976469404 39, 0 39 M0 39 C0 24.39182279035449, 0 9.783645580708978, 0 0 M0 39 C0 27.356765545159575, 0 15.713531090319155, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1415.6641836672989 260.31505805746497) rotate(0 21.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1396.5687679866014 253.0654239685748) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.5247266891293827 4.343014934002126, 3.3109102023202137 2.2882458509283623, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.880941370554186 5.083605225523745, 2.0233395651698203 3.769426433971601, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.035985275130465 7.551154741127234, 7.938825169393319 3.061451028325461, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.67071835281382 9.121714675984162, 5.208291324760029 6.202570898039318, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.889131394208884 11.516366338598454, 11.906574231611607 4.594090206192011, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.443982487779084 12.028451577064278, 11.016276418752007 5.618260683123658, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.002808864492601 17.48260771499452, 11.73932696724998 10.883498522131026, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C7.539391620207313 15.714971457739715, 14.812492478679404 7.348226007621417, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C9.906993859538126 19.08836898757109, 19.80915378140248 7.69723705020828, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C6.400185529507074 23.12249050061828, 12.795537121340375 15.765480076302657, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C10.422681947823541 24.592150049650606, 21.10198678203476 12.307015157291417, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.042802353155541 28.480256755567623, 14.34222759269876 20.083228569125453, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.487790658574035 26.01278976370757, 26.869424911635164 11.769348889220545, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C14.621202188054145 25.859317355034616, 27.136247970595385 11.462404071874637, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C18.525508948093822 27.464938553008803, 29.95784608281213 14.313539092529304, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C18.172062686112294 27.87153196644271, 29.250953558849073 15.12672591939712, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C20.949472545463763 30.773499757163894, 29.162698840698894 21.325263705768542, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.747549333443413 28.705049026514676, 32.75885241665819 17.188362244470106, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C28.57316026875329 28.100462600596217, 39.423058879415336 15.619082017339469, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C29.94616841392979 26.52099740752805, 42.16907516976834 12.46015163120314, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C30.60928203926329 31.855184789369865, 38.50828701257272 22.768419019593058, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C34.24309550344289 27.674960582443322, 45.77591394093193 14.407970605739969, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C36.5691095068009 31.09619990504985, 44.784867510794825 21.645051455882093, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C41.74632990408641 25.140489122792403, 55.139308305365844 9.73362989136719, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C40.74931779527038 32.384432701214415, 48.158268679871185 23.8614096729175, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C43.37070339550055 29.368873523565544, 53.401039880331524 17.83029131761976, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C50.89271684618298 26.81279923803224, 62.80199234484327 13.112744951482213, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C52.38933444687543 25.09113763250474, 65.79522754622818 9.669421740427207, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C57.25506820175456 25.59076358976619, 70.53967964812381 10.3085662796564, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C54.01549183903063 29.317469890223748, 64.06052692267596 17.761978880571508, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C61.766908843533585 26.49749700387109, 73.92028649482874 12.516635312795263, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C57.59209042061002 31.300076223486606, 65.5706496489816 22.12179375202629, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C67.59245085286398 25.892989868221868, 80.58435510562691 10.947513666203104, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C66.71712723226744 26.899934507450432, 78.83370786443385 12.961402944660232, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C67.69401400131045 31.873167177734402, 75.14440696566675 23.302470490157237, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C68.99039340992344 30.381853262294154, 77.73716578289273 20.319842659276738, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C69.92256920361453 35.40652002607902, 74.6145019624123 30.009068811552748, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C71.3558200342678 33.75775355087222, 77.48100362371885 26.71153586113916, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C77.66819664726737 32.59320726762002, 84.46268241286487 24.77704549956382, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C76.62275497634309 33.79585033744366, 82.3717990710163 27.182331639211093, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.17495020512054 36.95689792402115, 82.48917412070857 33.144319437072355, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.07364264013388 37.07343894619429, 82.28655899073526 33.37740148141864, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.683618584539 37.86728060852505, 85.8634364426924 35.35968701100921, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.62134083128937 37.938922968336136, 85.73888093619314 35.50297173063139, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C32.98184273473924 0, 65.96368546947848 0, 85.70000000000013 0 M0 0 C32.54269795306032 0, 65.08539590612064 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 9.08658194616437, 85.70000000000013 18.17316389232874, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 10.802469717711212, 85.70000000000013 21.604939435422423, 85.70000000000013 39 M85.70000000000013 39 C52.886900880411346 39, 20.073801760822562 39, 0 39 M85.70000000000013 39 C68.02522339455794 39, 50.35044678911574 39, 0 39 M0 39 C0 26.94815444871783, 0 14.896308897435663, 0 0 M0 39 C0 28.28894211128354, 0 17.57788422256708, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g></svg> \ No newline at end of file
+ <rect x="0" y="0" width="2664.430097077814" height="3110.5102161527097" fill="#ffffff"></rect><g transform="translate(10 80.94624914970746) rotate(0 412 84.5)"><path d="M0 0 C236.0917525626719 0, 472.1835051253438 0, 824 0 M0 0 C176.35285029634835 0, 352.7057005926967 0, 824 0 M824 0 C824 49.4694261523895, 824 98.938852304779, 824 169 M824 0 C824 67.28842992009595, 824 134.5768598401919, 824 169 M824 169 C617.4895157001913 169, 410.9790314003825 169, 0 169 M824 169 C591.9370339371264 169, 359.8740678742528 169, 0 169 M0 169 C0 117.27438288601115, 0 65.5487657720223, 0 0 M0 169 C0 113.33390512643382, 0 57.66781025286764, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(103 150.94624914970746) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(195 136.19977029409756) rotate(0 91.5 26.5)"><path d="M0 0 C45.52723533092067 0, 91.05447066184134 0, 183 0 M0 0 C51.992949485499416 0, 103.98589897099883 0, 183 0 M183 0 C183 11.300868693832308, 183 22.601737387664617, 183 53 M183 0 C183 11.438832641299815, 183 22.87766528259963, 183 53 M183 53 C122.95858201207594 53, 62.91716402415186 53, 0 53 M183 53 C130.75365380672739 53, 78.50730761345476 53, 0 53 M0 53 C0 34.84640925144777, 0 16.692818502895534, 0 0 M0 53 C0 40.84659901196137, 0 28.69319802392274, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(223 151.19977029409756) rotate(0 62.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">manage stock</text></g><g transform="translate(907.7142857142858 69.91405600838334) rotate(0 412 208.375)"><path d="M0 0 C254.33701222166422 0, 508.67402444332845 0, 824 0 M0 0 C264.9951364375651 0, 529.9902728751302 0, 824 0 M824 0 C824 153.41329097186681, 824 306.82658194373363, 824 416.75 M824 0 C824 126.50023594351951, 824 253.00047188703903, 824 416.75 M824 416.75 C651.8076281763613 416.75, 479.6152563527226 416.75, 0 416.75 M824 416.75 C609.0305379532277 416.75, 394.06107590645547 416.75, 0 416.75 M0 416.75 C0 306.4687552821124, 0 196.1875105642248, 0 0 M0 416.75 C0 326.79617198703346, 0 236.8423439740669, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1000.7142857142858 139.91405600838334) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(999.7142857142858 194.41405600838334) rotate(0 57 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock</text></g><g transform="translate(1159.7142857142858 183.41405600838334) rotate(0 49.370370370370324 25)"><path d="M0 0 C20.526187548641513 0, 41.052375097283026 0, 98.74074074074065 0 M0 0 C33.9206452924413 0, 67.8412905848826 0, 98.74074074074065 0 M98.74074074074065 0 C98.74074074074065 15.900936177931726, 98.74074074074065 31.801872355863452, 98.74074074074065 50 M98.74074074074065 0 C98.74074074074065 17.552169584669176, 98.74074074074065 35.10433916933835, 98.74074074074065 50 M98.74074074074065 50 C77.60304118323788 50, 56.465341625735114 50, 0 50 M98.74074074074065 50 C65.00140776341016 50, 31.262074786079666 50, 0 50 M0 50 C0 34.59016109351069, 0 19.18032218702137, 0 0 M0 50 C0 38.90304414089769, 0 27.806088281795382, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1195.3068783068784 196.91405600838334) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(908.6428571428569 628.8069131512407) rotate(0 412 199)"><path d="M0 0 C171.06364584192636 0, 342.1272916838527 0, 824 0 M0 0 C255.8546619512141 0, 511.7093239024282 0, 824 0 M824 0 C824 81.9858877783641, 824 163.9717755567282, 824 397.9999999999999 M824 0 C824 144.49592706207184, 824 288.9918541241437, 824 397.9999999999999 M824 397.9999999999999 C531.5596520878374 397.9999999999999, 239.11930417567487 397.9999999999999, 0 397.9999999999999 M824 397.9999999999999 C640.076900806278 397.9999999999999, 456.153801612556 397.9999999999999, 0 397.9999999999999 M0 397.9999999999999 C0 254.37236361447714, 0 110.74472722895439, 0 0 M0 397.9999999999999 C0 268.26489560361944, 0 138.529791207239, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1001.6428571428569 698.8069131512407) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1020.6428571428569 753.3069131512407) rotate(0 57 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock</text></g><g transform="translate(1027.6428571428569 833.9735798179072) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1207.6428571428569 838.9735798179072) rotate(0 50 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">15/02/2021</text></g><g transform="translate(1160.6428571428569 827.4735798179072) rotate(0 94.5 23)"><path d="M0 0 C49.514051240589474 0, 99.02810248117895 0, 189 0 M0 0 C62.65167563119903 0, 125.30335126239807 0, 189 0 M189 0 C189 11.8154281610623, 189 23.6308563221246, 189 46 M189 0 C189 13.85625107046217, 189 27.71250214092434, 189 46 M189 46 C139.20686839828267 46, 89.41373679656535 46, 0 46 M189 46 C135.00017379960045 46, 81.0003475992009 46, 0 46 M0 46 C0 35.174531353078784, 0 24.349062706157564, 0 0 M0 46 C0 35.074815454520284, 0 24.14963090904057, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1504.0367965367964 832.2565512018541) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.5074106075470783 4.36293480719127, 3.276278039155605 2.328085597306652, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.2573088406609494 4.650643978407226, 2.776074505383347 2.903503939738564, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.3959154886861533 8.287470801869468, 6.658685596504696 4.5340831498099305, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.647507052907955 7.998047814865392, 7.161868724948299 3.955237175801777, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.4980046620184115 11.966306174529965, 11.124320767230662 5.493969878055035, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.42039580787412 13.205953355679192, 8.969103058942078 7.973264240353487, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.6630406266275335 16.72309795439064, 13.059790491519845 9.36447900092326, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.7918771398395315 16.574888499895042, 13.31746351794384 9.068060091932072, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.53269558237936 21.819686714933805, 15.060557227084947 13.159872504933709, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.336812067357375 19.74428810766848, 18.668790197040977 9.00907529040306, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C6.6674860457231295 28.912008778352718, 13.591594977833937 20.94673261469564, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C11.063083258220995 23.855452614226436, 22.38278940282967 10.833620286443082, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.323628940968602 26.201636217315997, 26.5411014764243 12.1470417964374, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C11.888797744895843 29.002589102194243, 21.67143908427878 17.748947566193888, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C20.458527636515843 25.241254923280316, 33.82388345965617 9.866171833072332, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C19.6486159863538 26.172951698266964, 32.20406015933209 11.729565383045628, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C25.559914321986906 25.469793194119767, 38.38358239374518 10.717850579680285, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.845389402126045 28.59249690254184, 32.95453255402346 16.963257996524433, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C27.17710913200512 29.706435723176284, 36.630956605918996 18.83102826249961, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C28.21692052825332 28.510269543463995, 38.71057939841539 16.43869590307503, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C31.542013545691002 30.782199931955766, 40.37375002542815 20.622449304764856, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C34.79887591425044 27.035608356498003, 46.88747476254703 13.12926615384933, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C35.54102822993252 32.27887212601467, 42.72870495705808 24.010395897811723, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C40.35904046007414 26.73638307085531, 52.36472941734131 12.92541778749301, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C43.7970188346571 28.878453710849296, 54.25367075864462 16.849451692187266, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C41.24319660752628 31.81629011859939, 49.14602630438298 22.725124507687454, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C47.70826425099851 30.47609289782541, 56.43308715447433 20.43933227106855, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C50.55418139142306 27.20223972991225, 62.12492143532342 13.891625935242235, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C54.63078229458096 28.609659188894028, 65.2911078337766 16.346357477912075, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C53.15853815763985 30.30328233174744, 62.3466195598944 19.7336037636189, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C60.24594347822508 28.24716750859936, 70.87835576421173 16.0159763222518, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C59.758081658722524 28.80838833284445, 69.90263212520662 17.138417970741987, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C63.07351194298503 31.091434424308353, 71.54647728586903 21.344402778376075, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C61.491216262386075 32.91165738615166, 68.38188592467111 24.984848702062685, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C66.15965410531828 33.638246327390696, 72.07568717368243 26.832628789469823, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C69.49393434923996 29.802595673962045, 78.74424766152576 19.161327482612517, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C70.53531559049588 34.70163594097189, 75.839994736175 28.599300641338488, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C73.30286312200644 31.517936695239655, 81.37508979919612 22.23190214987402, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C75.35165630103094 35.25808209598322, 79.82960172039202 30.10679515629021, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C76.73927055609427 33.66181449554887, 82.60483023051864 26.9142599554215, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.66673358224277 36.39116586378327, 83.47274087495305 32.0128553165966, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C78.5739796905923 37.64823541760579, 81.2872330916521 34.52699442424164, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.39465042796638 38.19970044653909, 85.28550012954716 36.02452668703729, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.53537583556748 38.037814383541495, 85.56695094474935 35.7007545610421, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C27.382204342065425 0, 54.76440868413085 0, 85.70000000000013 0 M0 0 C32.83631051179958 0, 65.67262102359916 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 10.997829736489804, 85.70000000000013 21.995659472979607, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 11.259689829032869, 85.70000000000013 22.519379658065738, 85.70000000000013 39 M85.70000000000013 39 C64.52176900905567 39, 43.343538018111204 39, 0 39 M85.70000000000013 39 C67.73251752677383 39, 49.76503505354754 39, 0 39 M0 39 C0 27.597405128460377, 0 16.194810256920753, 0 0 M0 39 C0 27.376256355363875, 0 15.75251271072775, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1518.1367965367963 840.0565512018541) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(999.37012987013 903.4373024460815) rotate(0 288.49999999999994 28.5)"><path d="M0 0 C221.61191750867292 0, 443.22383501734583 0, 576.9999999999999 0 M0 0 C148.48949075983836 0, 296.9789815196767 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 18.23236775575206, 576.9999999999999 36.46473551150412, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 20.396188223082575, 576.9999999999999 40.79237644616515, 576.9999999999999 57 M576.9999999999999 57 C347.06813987987107 57, 117.13627975974231 57, 0 57 M576.9999999999999 57 C383.78149965358887 57, 190.5629993071779 57, 0 57 M0 57 C0 45.01051211776212, 0 33.02102423552424, 0 0 M0 57 C0 37.25637744097039, 0 17.51275488194078, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1494.3701298701299 903.4373024460815) rotate(0 41 29)"><path d="M0 0 C24.34247198160738 0, 48.68494396321476 0, 82 0 M0 0 C16.84486520532519 0, 33.68973041065038 0, 82 0 M82 0 C82 23.11392058711499, 82 46.22784117422998, 82 58 M82 0 C82 13.750576350279154, 82 27.501152700558308, 82 58 M82 58 C62.860811912454665 58, 43.72162382490933 58, 0 58 M82 58 C56.11744595523923 58, 30.234891910478467 58, 0 58 M0 58 C0 44.30942313093692, 0 30.61884626187384, 0 0 M0 58 C0 36.32924136202782, 0 14.658482724055645, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1533.3701298701299 915.4373024460815) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1533.3701298701299 915.4373024460815) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-3.6026222832168626 24.101876626037612, -1.3909021298973574 30.17852780543567, 0 34 M-5.814342436536368 18.025225446639556 C-3.699884998619726 23.834649511713405, -1.585427560703084 29.644073576787253, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1533.3701298701299 915.4373024460815) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C3.6026222832168626 24.101876626037612, 1.3909021298973574 30.17852780543567, 0 34 M5.814342436536368 18.025225446639556 C3.699884998619726 23.834649511713405, 1.585427560703084 29.644073576787253, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1030.3560452925694 919.4373024460815) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(979.594140530665 313.37777863655765) rotate(0 288.49999999999994 28.5)"><path d="M0 0 C183.09519919259472 0, 366.19039838518944 0, 576.9999999999999 0 M0 0 C191.24218815611673 0, 382.48437631223345 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 21.60299933133647, 576.9999999999999 43.20599866267294, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 18.814047324378045, 576.9999999999999 37.62809464875609, 576.9999999999999 57 M576.9999999999999 57 C375.89746348159383 57, 174.79492696318772 57, 0 57 M576.9999999999999 57 C408.40211005760347 57, 239.80422011520704 57, 0 57 M0 57 C0 39.55012987079098, 0 22.100259741581972, 0 0 M0 57 C0 34.29172115949913, 0 11.583442318998273, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1474.5941405306648 313.37777863655765) rotate(0 41 29)"><path d="M0 0 C23.301106604374947 0, 46.602213208749895 0, 82 0 M0 0 C20.47363549526781 0, 40.94727099053562 0, 82 0 M82 0 C82 17.889156678505245, 82 35.77831335701049, 82 58 M82 0 C82 14.935910755209626, 82 29.871821510419252, 82 58 M82 58 C53.102421215735376 58, 24.20484243147075 58, 0 58 M82 58 C60.69060039464384 58, 39.38120078928768 58, 0 58 M0 58 C0 35.32809809129685, 0 12.656196182593703, 0 0 M0 58 C0 39.14703913349658, 0 20.29407826699316, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1513.5941405306648 325.37777863655765) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1513.5941405306648 325.37777863655765) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.189382644804416 22.489765781944783, -2.5644228530724638 26.954306117250006, 0 34 M-5.814342436536368 18.025225446639556 C-4.477115144868706 21.699227235174916, -3.1398878532010444 25.37322902371028, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1513.5941405306648 325.37777863655765) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.189382644804416 22.489765781944783, 2.5644228530724638 26.954306117250006, 0 34 M5.814342436536368 18.025225446639556 C4.477115144868706 21.699227235174916, 3.1398878532010444 25.37322902371028, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1010.5800559531045 329.37777863655765) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(911.0870972286061 1169.8355253211091) rotate(0 412 210.2500000000001)"><path d="M0 0 C218.95360033735636 0, 437.9072006747127 0, 824 0 M0 0 C209.57695072665814 0, 419.1539014533163 0, 824 0 M824 0 C824 105.35239861519078, 824 210.70479723038156, 824 420.5 M824 0 C824 150.35835648789072, 824 300.71671297578143, 824 420.5 M824 420.5 C503.3142883516848 420.5, 182.62857670336962 420.5, 0 420.5 M824 420.5 C525.6960808418692 420.5, 227.3921616837382 420.5, 0 420.5 M0 420.5 C0 301.00011278777384, 0 181.50022557554766, 0 0 M0 420.5 C0 277.7357025532517, 0 134.97140510650348, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1004.0870972286061 1239.8355253211091) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1003.0870972286061 1294.3355253211091) rotate(0 57 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock</text></g><g transform="translate(1015.4669520449852 1414.5492479492834) rotate(0 288.5 28.499999999999886)"><path d="M0 0 C191.88294327305627 0, 383.76588654611254 0, 576.9999999999999 0 M0 0 C153.981226036977 0, 307.962452073954 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 18.94759962530807, 576.9999999999999 37.89519925061614, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 16.581719528418034, 576.9999999999999 33.16343905683607, 576.9999999999999 57 M576.9999999999999 57 C420.6865388090721 57, 264.37307761814435 57, 0 57 M576.9999999999999 57 C439.98495214777057 57, 302.96990429554126 57, 0 57 M0 57 C0 38.82633978752419, 0 20.65267957504838, 0 0 M0 57 C0 39.23593317987397, 0 21.471866359747942, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1510.4669520449852 1414.5492479492834) rotate(0 41 28.999999999999886)"><path d="M0 0 C20.85595868881792 0, 41.71191737763584 0, 82 0 M0 0 C20.54434408191592 0, 41.08868816383184 0, 82 0 M82 0 C82 20.739083653502167, 82 41.478167307004334, 82 58 M82 0 C82 22.57253795582801, 82 45.14507591165602, 82 58 M82 58 C52.31441581193357 58, 22.62883162386715 58, 0 58 M82 58 C58.69681153055281 58, 35.39362306110561 58, 0 58 M0 58 C0 38.30837276596576, 0 18.616745531931514, 0 0 M0 58 C0 42.259131410531694, 0 26.518262821063395, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1549.4669520449852 1426.5492479492834) rotate(0 0 16.999999999999886)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1549.4669520449852 1426.5492479492834) rotate(0 0 16.999999999999886)"><path d="M-5.814342436536368 18.025225446639556 C-4.113869358350213 22.697236831346512, -2.4133962801640587 27.369248216053464, 0 34 M-5.814342436536368 18.025225446639556 C-4.337132291635999 22.083826963542613, -2.85992214673563 26.142428480445666, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1549.4669520449852 1426.5492479492834) rotate(0 0 16.999999999999886)"><path d="M5.814342436536368 18.025225446639556 C4.113869358350213 22.697236831346512, 2.4133962801640587 27.369248216053464, 0 34 M5.814342436536368 18.025225446639556 C4.337132291635999 22.083826963542613, 2.85992214673563 26.142428480445666, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1046.4528674674248 1430.5492479492832) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(1412.0870972286057 122.20172403965671) rotate(0 83.875 26.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.7173956115841835 4.121374692556808, 3.6962480472298154 1.8449653680377276, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.53886432158337 4.326751448274156, 3.339185467228188 2.2557188794724237, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.454961933751928 8.219545636907089, 6.776778486636245 4.398232819885173, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.266605675249538 9.586593133211604, 4.400065969631465 7.1323278124942, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.998957222391963 11.390026175559758, 12.126225887977766 4.341409880114616, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.228720528642109 12.276082134131954, 10.585752500478057 6.1135217972590095, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.703031796699608 15.52672496855091, 15.139772831663993 6.971733029243808, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.370490594102075 18.21000667656041, 10.474690426468928 12.33829644526281, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.3456726381002575 22.034832001457943, 14.686511338526742 13.59016307798198, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.026548904733454 20.101205047875514, 18.048263871793136 9.722909170817125, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.850964347063032 25.24983591541769, 19.958551580513742 13.622386888825588, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C9.221483318128827 25.973971004048572, 18.699589522645333 15.07065706608735, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C13.353319137344942 27.317849960621473, 26.5686589561485 12.11534054238004, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C12.917833983670329 27.818818323222533, 25.697688648799275 13.11727726758216, 36.877284942009794 0.25662288638767805 M-0.12347750552006431 48.9181433959388 C10.175775454041249 37.07020817328198, 20.475028413602562 25.222272950625165, 42.520359378862906 -0.13797931854138312 M-0.12347750552006431 48.9181433959388 C9.746254045955597 37.5643160313688, 19.61598559743126 26.210488666798796, 42.520359378862906 -0.13797931854138312 M0.9271837283995019 53.806508252569145 C18.728587858763337 33.32833533682499, 36.52999198912717 12.850162421080846, 47.50737478672552 0.22212805675232872 M0.9271837283995019 53.806508252569145 C16.271137215299515 36.155308919370725, 31.615090702199527 18.504109586172298, 47.50737478672552 0.22212805675232872 M5.914199136262113 54.16661562786285 C23.49673440300238 33.94022253815562, 41.07926966974265 13.713829448448386, 53.15044922357863 -0.1724741481767289 M5.914199136262113 54.16661562786285 C22.864289477153022 34.66776720015996, 39.81437981804393 15.168918772457062, 53.15044922357863 -0.1724741481767289 M11.557273573115229 53.772013422933796 C22.848484740685947 40.7829608164994, 34.13969590825667 27.79390821006499, 58.13746463144125 0.1876332271169865 M11.557273573115229 53.772013422933796 C27.778619477682486 35.111489571715715, 43.99996538224974 16.450965720497635, 58.13746463144125 0.1876332271169865 M16.544288980977836 54.13212079822751 C27.664794442473266 41.33944264299447, 38.78529990396869 28.546764487761436, 63.78053906829436 -0.20696897781207468 M16.544288980977836 54.13212079822751 C31.45706359913156 36.976936013496115, 46.36983821728528 19.82175122876471, 63.78053906829436 -0.20696897781207468 M22.18736341783096 53.73751859329845 C35.13162478449539 38.84684926227623, 48.075886151159835 23.956179931254013, 68.76755447615697 0.1531383974816336 M22.18736341783096 53.73751859329845 C33.69096689012845 40.504136589569505, 45.19457036242595 27.27075458584056, 68.76755447615697 0.1531383974816336 M27.174378825693566 54.09762596859217 C43.810054153418044 34.96047063879192, 60.44572948114252 15.82331530899166, 74.41062891301009 -0.2414638074474169 M27.174378825693566 54.09762596859217 C44.820114437100386 33.798529199055054, 62.46585004850721 13.499432429517938, 74.41062891301009 -0.2414638074474169 M32.817453262546685 53.703023763663104 C49.97269154179106 33.968179628871866, 67.12792982103542 14.23333549408062, 79.3976443208727 0.11864356784629138 M32.817453262546685 53.703023763663104 C49.16607414542128 34.89608679837052, 65.51469502829588 16.089149833077933, 79.3976443208727 0.11864356784629138 M37.80446867040929 54.06313113895682 C53.664216718620374 35.81857803780972, 69.52396476683145 17.574024936662624, 85.04071875772581 -0.27595863708276624 M37.80446867040929 54.06313113895682 C53.71031346445203 35.76554979772929, 69.61615825849475 17.467968456501765, 85.04071875772581 -0.27595863708276624 M43.447543107262405 53.668528934027755 C54.41543216278025 41.05141587065494, 65.38332121829811 28.43430280728213, 90.02773416558841 0.08414873821094204 M43.447543107262405 53.668528934027755 C60.61312426753425 33.92178667566286, 77.7787054278061 14.175044417297954, 90.02773416558841 0.08414873821094204 M48.43455851512502 54.02863630932147 C66.93170216944668 32.750106625561266, 85.42884582376834 11.471576941801061, 95.67080860244155 -0.3104534667181156 M48.43455851512502 54.02863630932147 C59.88172162803239 40.86018151192713, 71.32888474093976 27.69172671453279, 95.67080860244155 -0.3104534667181156 M54.07763295197813 53.63403410439241 C70.07482934877166 35.23136476541137, 86.07202574556518 16.828695426430336, 100.65782401030415 0.049653908575599814 M54.07763295197813 53.63403410439241 C67.6147605641235 38.0613501748612, 81.15188817626887 22.488666245329977, 100.65782401030415 0.049653908575599814 M59.064648359840746 53.99414147968613 C69.32977228979794 42.185467214455, 79.59489621975513 30.37679294922387, 106.30089844715727 -0.3449482963534578 M59.064648359840746 53.99414147968613 C71.3619643394091 39.84769768317661, 83.65928031897745 25.701253886667097, 106.30089844715727 -0.3449482963534578 M64.70772279669386 53.59953927475706 C83.26681476468622 32.24974620806945, 101.82590673267859 10.899953141381843, 111.28791385501988 0.015159078940250481 M64.70772279669386 53.59953927475706 C78.26906752069534 37.99899674483243, 91.83041224469684 22.398454214907797, 111.28791385501988 0.015159078940250481 M69.69473820455647 53.959646650050786 C83.79703187510646 37.73681354209728, 97.89932554565644 21.513980434143782, 116.2749292628825 0.375266454233973 M69.69473820455647 53.959646650050786 C88.0232089730638 32.87515292528618, 106.35167974157113 11.790659200521574, 116.2749292628825 0.375266454233973 M75.3378126414096 53.56504444512172 C86.93044099155067 40.22925103446481, 98.52306934169175 26.8934576238079, 121.9180036997356 -0.019335750695091747 M75.3378126414096 53.56504444512172 C85.89947527144335 41.415241427804126, 96.46113790147712 29.265438410486535, 121.9180036997356 -0.019335750695091747 M80.3248280492722 53.92515182041544 C91.03408820637159 41.605557270977584, 101.74334836347099 29.285962721539736, 126.9050191075982 0.34077162459862365 M80.3248280492722 53.92515182041544 C97.90171544146804 33.70525586715205, 115.4786028336639 13.485359913888658, 126.9050191075982 0.34077162459862365 M85.96790248612533 53.53054961548638 C103.91797705566042 32.88135092343194, 121.8680516251955 12.23215223137749, 132.54809354445132 -0.05383058033042687 M85.96790248612533 53.53054961548638 C101.66089334007648 35.477828722292706, 117.35388419402764 17.425107829099034, 132.54809354445132 -0.05383058033042687 M90.95491789398793 53.89065699078009 C108.32841377881171 33.90473620189459, 125.70190966363549 13.9188154130091, 137.53510895231395 0.3062767949632672 M90.95491789398793 53.89065699078009 C108.67003675500621 33.511743922899626, 126.38515561602449 13.132830855019158, 137.53510895231395 0.3062767949632672 M96.59799233084104 53.49605478585103 C107.80858362942544 40.59974472969279, 119.01917492800983 27.703434673534552, 143.17818338916706 -0.0883254099657762 M96.59799233084104 53.49605478585103 C112.61753868732575 35.0676747593386, 128.63708504381046 16.639294732826166, 143.17818338916706 -0.0883254099657762 M101.58500773870365 53.85616216114475 C120.14001076986757 32.511072878203706, 138.69501380103148 11.165983595262652, 148.16519879702966 0.2717819653279321 M101.58500773870365 53.85616216114475 C115.72587718093787 37.588952704161514, 129.8667466231721 21.321743247178283, 148.16519879702966 0.2717819653279321 M107.22808217555678 53.46155995621568 C120.72671425385484 37.93316007264153, 134.2253463321529 22.404760189067368, 153.8082732338828 -0.12282023960112554 M107.22808217555678 53.46155995621568 C123.66510766343758 34.55292512627111, 140.10213315131838 15.644290296326545, 153.8082732338828 -0.12282023960112554 M112.21509758341939 53.8216673315094 C129.19705003766725 34.28616573521338, 146.1790024919151 14.750664138917358, 158.7952886417454 0.23728713569258275 M112.21509758341939 53.8216673315094 C125.31139171728925 38.75610430823168, 138.4076858511591 23.690541284953955, 158.7952886417454 0.23728713569258275 M117.20211299128198 54.181774706803104 C133.7292754332167 35.169449172591754, 150.25643787515145 16.157123638380398, 164.4383630785985 -0.15731506923647487 M117.20211299128198 54.181774706803104 C133.81536100577716 35.0704190496007, 150.42860902027232 15.959063392398306, 164.4383630785985 -0.15731506923647487 M122.8451874281351 53.78717250187405 C133.05985181490902 42.036545300963766, 143.27451620168293 30.285918100053483, 168.7693194574706 0.9575018862800135 M122.8451874281351 53.78717250187405 C140.28991833384504 33.71930519547341, 157.73464923955493 13.651437889072767, 168.7693194574706 0.9575018862800135 M127.83220283599772 54.14727987716777 C139.23328157673114 41.03183908558893, 150.6343603174646 27.916398294010087, 168.50786263340916 7.355285903355906 M127.83220283599772 54.14727987716777 C140.48760654926735 39.58890326479492, 153.143010262537 25.030526652422076, 168.50786263340916 7.355285903355906 M133.47527727285083 53.752677672238704 C144.62918479252608 40.92157484453947, 155.7830923122013 28.09047201684024, 168.90246483833823 12.998360340209018 M133.47527727285083 53.752677672238704 C144.07958096052596 41.55382172935999, 154.6838846482011 29.354965786481277, 168.90246483833823 12.998360340209018 M138.46229268071346 54.11278504753242 C146.9895790886307 44.30326416453928, 155.51686549654798 34.493743281546145, 168.6410080142768 19.39614435728491 M138.46229268071346 54.11278504753242 C147.05382327377777 44.229359683598474, 155.64535386684204 34.34593431966452, 168.6410080142768 19.39614435728491 M144.10536711756657 53.718182842603355 C151.0098749112423 45.77545520934758, 157.91438270491798 37.83272757609179, 169.03561021920584 25.03921879413801 M144.10536711756657 53.718182842603355 C150.88646680478465 45.91741999621139, 157.66756649200275 38.11665714981944, 169.03561021920584 25.03921879413801 M149.09238252542917 54.07829021789707 C153.03681122133102 49.54074406159562, 156.98123991723287 45.003197905294165, 168.7741533951444 31.437002811213915 M149.09238252542917 54.07829021789707 C155.1688205945165 47.088147834783996, 161.2452586636038 40.098005451670915, 168.7741533951444 31.437002811213915 M154.73545696228229 53.68368801296802 C159.36574426257135 48.35715178635883, 163.9960315628604 43.03061555974963, 168.51269657108293 37.83478682828981 M154.73545696228229 53.68368801296802 C159.73862405330902 47.928202655403005, 164.74179114433576 42.172717297838, 168.51269657108293 37.83478682828981 M159.72247237014489 54.04379538826172 C161.7409058583154 51.721853271393485, 163.7593393464859 49.39991115452525, 168.907298776012 43.47786126514291 M159.72247237014489 54.04379538826172 C162.50026141307723 50.848314631347684, 165.27805045600957 47.65283387443364, 168.907298776012 43.47786126514291 M165.36554680699803 53.64919318333267 C166.0601001263845 52.850200987579996, 166.75465344577094 52.05120879182733, 168.64584195195056 49.875645282218805 M165.36554680699803 53.64919318333267 C166.5819819107356 52.24984467055833, 167.79841701447322 50.850496157783994, 168.64584195195056 49.875645282218805" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C46.183000835520254 0, 92.36600167104051 0, 167.7500000000001 0 M0 0 C36.24056315112396 0, 72.48112630224792 0, 167.7500000000001 0 M167.7500000000001 0 C167.7500000000001 18.842396629508585, 167.7500000000001 37.68479325901717, 167.7500000000001 53 M167.7500000000001 0 C167.7500000000001 16.167132444027814, 167.7500000000001 32.33426488805563, 167.7500000000001 53 M167.7500000000001 53 C102.15453257735359 53, 36.55906515470707 53, 0 53 M167.7500000000001 53 C129.04311365347831 53, 90.33622730695649 53, 0 53 M0 53 C0 38.442475789133454, 0 23.884951578266914, 0 0 M0 53 C0 40.237482499238105, 0 27.474964998476207, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1442.0870972286054 137.2017240396567) rotate(0 58 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">without stock</text></g><g transform="translate(1412.0870972286061 1209.701724039657) rotate(0 79.5 26.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.6763342954413682 4.168610333406417, 3.614125414944185 1.9394366497369457, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.1857802863737288 4.732928167473438, 2.633017396808906 3.068072317870987, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.349949477829196 9.4907170557863, 4.566753574790781 6.940575657643593, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.9945686047466356 8.749167577490088, 5.85599182862566 5.457476701051167, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.2124176579807555 12.294836441487785, 10.55314675915535 6.151030411970671, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.955555809340896 12.590322197183463, 10.039423061875631 6.742001923362029, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.076761123300264 17.397535372819554, 11.887231484865305 10.713353837781092, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.779310011319332 16.589345327514167, 13.292329260903442 9.096973747170317, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C10.242782297882494 18.70208857658965, 20.480730658091215 6.924676228245396, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C7.852918654132539 21.451312209925682, 15.701003370591305 12.423123494917462, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C11.518475193802992 23.331584118629685, 23.293573273993662 9.785883295249576, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C6.37849692552965 29.24445273225389, 13.013616737446979 21.61162052249799, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C14.51001094359821 25.987228249816308, 28.882042568655038 9.45409712076971, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C13.87108758374623 26.722225497625526, 27.604195848951075 10.924091616388147, 36.877284942009794 0.25662288638767805 M-0.12347750552006431 48.9181433959388 C9.59161271356494 37.742210534601575, 19.306702932649944 26.56627767326435, 42.520359378862906 -0.13797931854138312 M-0.12347750552006431 48.9181433959388 C10.07668795276822 37.18419530429695, 20.276853411056504 25.450247212655093, 42.520359378862906 -0.13797931854138312 M0.9271837283995019 53.806508252569145 C19.295938194460234 32.67567343481305, 37.66469266052097 11.54483861705696, 47.50737478672552 0.22212805675232872 M0.9271837283995019 53.806508252569145 C15.31044142937061 37.26046300045369, 29.693699130341717 20.714417748338235, 47.50737478672552 0.22212805675232872 M5.914199136262113 54.16661562786285 C17.356716674322968 41.00350495300532, 28.79923421238382 27.840394278147787, 53.15044922357863 -0.1724741481767289 M5.914199136262113 54.16661562786285 C16.72681856170508 41.72811984152908, 27.53943798714805 29.289624055195297, 53.15044922357863 -0.1724741481767289 M11.557273573115229 53.772013422933796 C27.133354011702025 35.853782578050385, 42.709434450288825 17.935551733166967, 58.13746463144125 0.1876332271169865 M11.557273573115229 53.772013422933796 C28.228403248938907 34.59407253118161, 44.89953292476258 15.416131639429423, 58.13746463144125 0.1876332271169865 M16.544288980977836 54.13212079822751 C31.813370743359187 36.567051531509506, 47.082452505740534 19.001982264791508, 63.78053906829436 -0.20696897781207468 M16.544288980977836 54.13212079822751 C27.78688901826427 41.198988900311456, 39.0294890555507 28.2658570023954, 63.78053906829436 -0.20696897781207468 M22.18736341783096 53.73751859329845 C37.50897302708171 36.11202295104255, 52.83058263633245 18.48652730878665, 68.76755447615697 0.1531383974816336 M22.18736341783096 53.73751859329845 C39.292479008292844 34.06033401616755, 56.39759459875472 14.383149439036657, 68.76755447615697 0.1531383974816336 M27.174378825693566 54.09762596859217 C37.44142796809184 42.286736999791636, 47.70847711049011 30.4758480309911, 74.41062891301009 -0.2414638074474169 M27.174378825693566 54.09762596859217 C39.98323217008107 39.36272574848162, 52.79208551446856 24.627825528371073, 74.41062891301009 -0.2414638074474169 M32.817453262546685 53.703023763663104 C50.442127839427194 33.42815494286845, 68.0668024163077 13.153286122073801, 79.3976443208727 0.11864356784629138 M32.817453262546685 53.703023763663104 C45.6266603288556 38.96771663302921, 58.435867395164514 24.232409502395328, 79.3976443208727 0.11864356784629138 M37.80446867040929 54.06313113895682 C47.43497861338879 42.9844967551254, 57.065488556368294 31.905862371293974, 85.04071875772581 -0.27595863708276624 M37.80446867040929 54.06313113895682 C55.52889401123652 33.673512190721695, 73.25331935206376 13.283893242486577, 85.04071875772581 -0.27595863708276624 M43.447543107262405 53.668528934027755 C55.83551580208621 39.417796516385934, 68.22348849691002 25.167064098744106, 90.02773416558841 0.08414873821094204 M43.447543107262405 53.668528934027755 C53.30706930107239 42.32644149050074, 63.166595494882365 30.98435404697373, 90.02773416558841 0.08414873821094204 M48.43455851512502 54.02863630932147 C67.0908023826447 32.567082766716126, 85.74704625016437 11.10552922411079, 95.67080860244155 -0.3104534667181156 M48.43455851512502 54.02863630932147 C64.45882481017543 35.59482661459903, 80.48309110522584 17.161016919876587, 95.67080860244155 -0.3104534667181156 M54.07763295197813 53.63403410439241 C66.08133276375455 39.82535707116004, 78.08503257553096 26.016680037927674, 100.65782401030415 0.049653908575599814 M54.07763295197813 53.63403410439241 C72.70393937857786 32.206919648014434, 91.33024580517758 10.779805191636449, 100.65782401030415 0.049653908575599814 M59.064648359840746 53.99414147968613 C70.1902433741227 41.19560846372059, 81.31583838840466 28.397075447755043, 106.30089844715727 -0.3449482963534578 M59.064648359840746 53.99414147968613 C77.69525417082781 32.562081147338446, 96.32585998181489 11.130020814990765, 106.30089844715727 -0.3449482963534578 M64.70772279669386 53.59953927475706 C74.13910111598257 42.74997961969814, 83.5704794352713 31.90041996463922, 111.28791385501988 0.015159078940250481 M64.70772279669386 53.59953927475706 C76.55944342570267 39.96569429193582, 88.41116405471146 26.33184930911458, 111.28791385501988 0.015159078940250481 M69.69473820455647 53.959646650050786 C79.16741721515874 43.06257598450837, 88.64009622576101 32.16550531896595, 116.2749292628825 0.375266454233973 M69.69473820455647 53.959646650050786 C84.3289502908371 37.1249114014217, 98.96316237711773 20.290176152792604, 116.2749292628825 0.375266454233973 M75.3378126414096 53.56504444512172 C86.14180353400351 41.13647465037817, 96.94579442659742 28.70790485563462, 121.9180036997356 -0.019335750695091747 M75.3378126414096 53.56504444512172 C88.49005672829291 38.43511836351181, 101.64230081517624 23.305192281901906, 121.9180036997356 -0.019335750695091747 M80.3248280492722 53.92515182041544 C94.71918853078526 37.36633428033221, 109.11354901229831 20.807516740248992, 126.9050191075982 0.34077162459862365 M80.3248280492722 53.92515182041544 C94.9563456434856 37.09351623033399, 109.587863237699 20.261880640252535, 126.9050191075982 0.34077162459862365 M85.96790248612533 53.53054961548638 C98.10578744757035 39.56751022535702, 110.24367240901537 25.604470835227655, 132.54809354445132 -0.05383058033042687 M85.96790248612533 53.53054961548638 C103.80023219455592 33.01680089175919, 121.63256190298651 12.503052168031992, 132.54809354445132 -0.05383058033042687 M90.95491789398793 53.89065699078009 C109.39582213972629 32.67682334589489, 127.83672638546466 11.462989701009697, 137.53510895231395 0.3062767949632672 M90.95491789398793 53.89065699078009 C107.35244279276296 35.027462390609415, 123.74996769153798 16.164267790438743, 137.53510895231395 0.3062767949632672 M96.59799233084104 53.49605478585103 C109.90482235845376 38.18829792182548, 123.21165238606649 22.88054105779993, 143.17818338916706 -0.0883254099657762 M96.59799233084104 53.49605478585103 C112.77249588422322 34.88941689555622, 128.9469994376054 16.282779005261403, 143.17818338916706 -0.0883254099657762 M101.58500773870365 53.85616216114475 C112.7724984269507 40.98642631730611, 123.95998911519776 28.11669047346747, 148.16519879702966 0.2717819653279321 M101.58500773870365 53.85616216114475 C112.20006631530816 41.644934133818495, 122.81512489191269 29.433706106492235, 148.16519879702966 0.2717819653279321 M107.22808217555678 53.46155995621568 C120.61208073460944 38.06503085158998, 133.9960792936621 22.66850174696429, 153.8082732338828 -0.12282023960112554 M107.22808217555678 53.46155995621568 C123.95754253771364 34.2165172857343, 140.6870028998705 14.971474615252909, 153.8082732338828 -0.12282023960112554 M112.21509758341939 53.8216673315094 C123.90092493152434 40.3786607380102, 135.58675227962928 26.935654144510988, 158.7952886417454 0.23728713569258275 M112.21509758341939 53.8216673315094 C125.43723812719647 38.611334574112234, 138.65937867097352 23.401001816715073, 158.7952886417454 0.23728713569258275 M117.20211299128198 54.181774706803104 C130.99364885222906 38.316427565314, 144.7851847131761 22.4510804238249, 159.18989084667447 5.880361572545695 M117.20211299128198 54.181774706803104 C129.560359585114 39.96523825661208, 141.91860617894605 25.74870180642106, 159.18989084667447 5.880361572545695 M122.8451874281351 53.78717250187405 C136.18922449133777 38.43661383957947, 149.53326155454045 23.086055177284887, 158.928434022613 12.278145589621587 M122.8451874281351 53.78717250187405 C133.82487607659277 41.15648555956519, 144.80456472505045 28.525798617256328, 158.928434022613 12.278145589621587 M127.83220283599772 54.14727987716777 C135.85614577605108 44.916789417586315, 143.88008871610447 35.686298958004855, 159.32303622754208 17.921220026474714 M127.83220283599772 54.14727987716777 C134.7825839478684 46.15178102792611, 141.73296505973906 38.15628217868446, 159.32303622754208 17.921220026474714 M133.47527727285083 53.752677672238704 C142.9837100332666 42.81447702247116, 152.49214279368235 31.876276372703618, 159.0615794034806 24.319004043550592 M133.47527727285083 53.752677672238704 C138.9946597866402 47.40335440100733, 144.51404230042957 41.05403112977595, 159.0615794034806 24.319004043550592 M138.46229268071346 54.11278504753242 C146.49336568920856 44.87409238247427, 154.52443869770366 35.63539971741612, 158.80012257941917 30.716788060626484 M138.46229268071346 54.11278504753242 C144.5317253147659 47.13070149556227, 150.60115794881838 40.14861794359212, 158.80012257941917 30.716788060626484 M144.10536711756657 53.718182842603355 C149.59412307147716 47.4040913982784, 155.08287902538774 41.08999995395345, 159.19472478434824 36.3598624974796 M144.10536711756657 53.718182842603355 C149.94297087583203 47.00278790522013, 155.7805746340975 40.28739296783691, 159.19472478434824 36.3598624974796 M149.09238252542917 54.07829021789707 C151.6516127612986 51.13423260774811, 154.21084299716802 48.19017499759916, 158.9332679602868 42.75764651455549 M149.09238252542917 54.07829021789707 C151.84593396586163 50.91069163316567, 154.5994854062941 47.74309304843426, 158.9332679602868 42.75764651455549 M154.73545696228229 53.68368801296802 C156.05098012122784 52.17035173194947, 157.36650328017342 50.657015450930906, 159.32787016521584 48.400720951408616 M154.73545696228229 53.68368801296802 C156.24479352007694 51.947394921017356, 157.75413007787157 50.211101829066685, 159.32787016521584 48.400720951408616" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C42.85518687916917 0, 85.71037375833833 0, 158.9999999999999 0 M0 0 C57.662035526800864 0, 115.32407105360173 0, 158.9999999999999 0 M158.9999999999999 0 C158.9999999999999 16.40436417544261, 158.9999999999999 32.80872835088522, 158.9999999999999 53 M158.9999999999999 0 C158.9999999999999 13.331009528692814, 158.9999999999999 26.662019057385628, 158.9999999999999 53 M158.9999999999999 53 C107.5467047874815 53, 56.093409574963104 53, 0 53 M158.9999999999999 53 C99.94139154357828 53, 40.88278308715668 53, 0 53 M0 53 C0 37.42691702758893, 0 21.853834055177863, 0 0 M0 53 C0 38.142077192012216, 0 23.284154384024436, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1442.0870972286061 1224.701724039657) rotate(0 58 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">without stock</text></g><g transform="translate(63.87281151432046 18.772654820766093) rotate(0 239 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># create product without stock</text></g><g transform="translate(1402.7656686571772 674.1650017457355) rotate(0 79.5 26.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C0.9590294310660872 4.993775187729691, 2.1795156861936227 3.5897663583834936, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.6496073208572388 4.199356200588598, 3.560671465775926 2.000928384101308, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.2922858811843985 9.55705143561321, 4.451426381501186 7.073244417297413, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.0914745139615354 8.637690081056238, 6.04980364705546 5.234521708183471, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C3.3443144650777965 14.443843336032044, 6.816940373349432 10.449044201059191, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.01479641440859 12.522173676688933, 10.157904272011018 6.605704882372967, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.313581141842409 17.125105105291176, 12.360871521949596 10.168493302724338, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C4.782279493601126 18.886666143353406, 9.29826822546703 13.6916153788488, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C10.401758796254919 18.519207035371387, 20.798683654836065 6.5589131458088765, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.456501407525241 19.606601272058242, 18.90816887737671 8.733701619182579, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.005901199992277 26.221969861914648, 18.26842528637223 15.566654781819501, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.555575484081738 27.890378745678326, 15.367773854551155 18.903472549346855, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.642047587190632 28.136074280874105, 25.14611585583988 13.751789182885304, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C10.608185768210685 30.47576466208169, 21.078392217879987 18.431169945300475, 36.877284942009794 0.25662288638767805 M-0.12347750552006431 48.9181433959388 C14.406384704375723 32.203448948400236, 28.93624691427151 15.488754500861681, 42.520359378862906 -0.13797931854138312 M-0.12347750552006431 48.9181433959388 C9.233749970811559 38.15388452798652, 18.59097744714318 27.389625660034245, 42.520359378862906 -0.13797931854138312 M0.9271837283995019 53.806508252569145 C18.00889372145183 34.15624873525034, 35.090603714504155 14.505989217931528, 47.50737478672552 0.22212805675232872 M0.9271837283995019 53.806508252569145 C17.862422907835555 34.324744131814455, 34.797662087271604 14.842980011059765, 47.50737478672552 0.22212805675232872 M5.914199136262113 54.16661562786285 C23.081586803698528 34.41779522072842, 40.24897447113494 14.668974813593984, 53.15044922357863 -0.1724741481767289 M5.914199136262113 54.16661562786285 C24.35590728671535 32.951857196391245, 42.79761543716859 11.737098764919644, 53.15044922357863 -0.1724741481767289 M11.557273573115229 53.772013422933796 C21.88915933163768 41.886538459312895, 32.22104509016013 30.001063495691994, 58.13746463144125 0.1876332271169865 M11.557273573115229 53.772013422933796 C20.900653777763026 43.023684018852805, 30.244033982410823 32.27535461477181, 58.13746463144125 0.1876332271169865 M16.544288980977836 54.13212079822751 C33.39244654091159 34.750532621397895, 50.24060410084536 15.368944444568285, 63.78053906829436 -0.20696897781207468 M16.544288980977836 54.13212079822751 C30.442759793989424 38.14375906625568, 44.34123060700101 22.15539733428385, 63.78053906829436 -0.20696897781207468 M22.18736341783096 53.73751859329845 C40.118140206275996 33.110519458939486, 58.048916994721026 12.483520324580525, 68.76755447615697 0.1531383974816336 M22.18736341783096 53.73751859329845 C33.500898195362026 40.722785611230535, 44.81443297289309 27.708052629162626, 68.76755447615697 0.1531383974816336 M27.174378825693566 54.09762596859217 C41.51903694549283 37.59598445518877, 55.86369506529209 21.094342941785364, 74.41062891301009 -0.2414638074474169 M27.174378825693566 54.09762596859217 C39.51854700250924 39.89728488456045, 51.86271517932491 25.696943800528732, 74.41062891301009 -0.2414638074474169 M32.817453262546685 53.703023763663104 C49.97093502899642 33.970200265697684, 67.12441679544617 14.237376767732272, 79.3976443208727 0.11864356784629138 M32.817453262546685 53.703023763663104 C45.83650989757114 38.726312318909905, 58.85556653259559 23.749600874156705, 79.3976443208727 0.11864356784629138 M37.80446867040929 54.06313113895682 C50.58084181837462 39.36559511067067, 63.357214966339946 24.66805908238452, 85.04071875772581 -0.27595863708276624 M37.80446867040929 54.06313113895682 C52.652311974553655 36.982641286501135, 67.50015527869802 19.90215143404545, 85.04071875772581 -0.27595863708276624 M43.447543107262405 53.668528934027755 C53.54115053169298 42.0571618380714, 63.63475795612354 30.445794742115048, 90.02773416558841 0.08414873821094204 M43.447543107262405 53.668528934027755 C59.36198264326729 35.36106047317875, 75.27642217927217 17.053592012329744, 90.02773416558841 0.08414873821094204 M48.43455851512502 54.02863630932147 C62.885471912113616 37.40476208193895, 77.33638530910221 20.780887854556426, 95.67080860244155 -0.3104534667181156 M48.43455851512502 54.02863630932147 C61.09681455113109 39.46237700135656, 73.75907058713716 24.896117693391645, 95.67080860244155 -0.3104534667181156 M54.07763295197813 53.63403410439241 C63.76684417702619 42.48787162020596, 73.45605540207424 31.341709136019503, 100.65782401030415 0.049653908575599814 M54.07763295197813 53.63403410439241 C63.522854684102754 42.76854942455894, 72.96807641622738 31.903064744725476, 100.65782401030415 0.049653908575599814 M59.064648359840746 53.99414147968613 C75.20503992215906 35.42674494621858, 91.34543148447739 16.859348412751032, 106.30089844715727 -0.3449482963534578 M59.064648359840746 53.99414147968613 C69.90665639563773 41.52183796446898, 80.74866443143469 29.049534449251826, 106.30089844715727 -0.3449482963534578 M64.70772279669386 53.59953927475706 C81.14042862926009 34.695873639816455, 97.57313446182631 15.792208004875853, 111.28791385501988 0.015159078940250481 M64.70772279669386 53.59953927475706 C77.6778807169985 38.679079366571244, 90.64803863730316 23.758619458385425, 111.28791385501988 0.015159078940250481 M69.69473820455647 53.959646650050786 C83.30804872765351 38.299324306590634, 96.92135925075056 22.639001963130486, 116.2749292628825 0.375266454233973 M69.69473820455647 53.959646650050786 C83.57195224873057 37.9957380333892, 97.44916629290466 22.031829416727625, 116.2749292628825 0.375266454233973 M75.3378126414096 53.56504444512172 C89.05065264842965 37.79022652776953, 102.76349265544968 22.015408610417342, 121.9180036997356 -0.019335750695091747 M75.3378126414096 53.56504444512172 C90.54478350832046 36.07142559029717, 105.75175437523133 18.577806735472613, 121.9180036997356 -0.019335750695091747 M80.3248280492722 53.92515182041544 C96.85611522661567 34.908081320901715, 113.38740240395916 15.891010821388, 126.9050191075982 0.34077162459862365 M80.3248280492722 53.92515182041544 C92.6511138377993 39.745382070916534, 104.97739962632642 25.56561232141764, 126.9050191075982 0.34077162459862365 M85.96790248612533 53.53054961548638 C95.70477304283412 42.32956134184813, 105.44164359954293 31.12857306820987, 132.54809354445132 -0.05383058033042687 M85.96790248612533 53.53054961548638 C102.12242584637285 34.946896308143835, 118.27694920662037 16.363243000801297, 132.54809354445132 -0.05383058033042687 M90.95491789398793 53.89065699078009 C105.38220157030383 37.29396564753089, 119.80948524661974 20.69727430428169, 137.53510895231395 0.3062767949632672 M90.95491789398793 53.89065699078009 C105.5442524583211 37.107547425593715, 120.13358702265427 20.324437860407343, 137.53510895231395 0.3062767949632672 M96.59799233084104 53.49605478585103 C110.83845913987416 37.11427166465995, 125.07892594890727 20.732488543468875, 143.17818338916706 -0.0883254099657762 M96.59799233084104 53.49605478585103 C109.71675322390044 38.40464671258903, 122.83551411695983 23.31323863932702, 143.17818338916706 -0.0883254099657762 M101.58500773870365 53.85616216114475 C111.81690229869126 42.085713913318486, 122.04879685867887 30.315265665492223, 148.16519879702966 0.2717819653279321 M101.58500773870365 53.85616216114475 C116.46409779057373 36.739727037277, 131.3431878424438 19.623291913409247, 148.16519879702966 0.2717819653279321 M107.22808217555678 53.46155995621568 C122.42569725688868 35.97870370154591, 137.6233123382206 18.495847446876148, 153.8082732338828 -0.12282023960112554 M107.22808217555678 53.46155995621568 C121.91485115558635 36.56636491743614, 136.60162013561592 19.6711698786566, 153.8082732338828 -0.12282023960112554 M112.21509758341939 53.8216673315094 C127.60625312960151 36.116168240557066, 142.99740867578362 18.41066914960473, 158.7952886417454 0.23728713569258275 M112.21509758341939 53.8216673315094 C128.57550998385707 35.00116577693906, 144.93592238429474 16.180664222368712, 158.7952886417454 0.23728713569258275 M117.20211299128198 54.181774706803104 C127.5022841883169 42.33278317276633, 137.80245538535183 30.483791638729556, 159.18989084667447 5.880361572545695 M117.20211299128198 54.181774706803104 C132.9196815579222 36.10078038941022, 148.6372501245624 18.019786072017332, 159.18989084667447 5.880361572545695 M122.8451874281351 53.78717250187405 C136.21592740610657 38.40589565004875, 149.586667384078 23.02461879822345, 158.928434022613 12.278145589621587 M122.8451874281351 53.78717250187405 C136.1507215140126 38.48090644827832, 149.45625559989006 23.17464039468259, 158.928434022613 12.278145589621587 M127.83220283599772 54.14727987716777 C139.49335374182792 40.73266028326407, 151.1545046476581 27.31804068936036, 159.32303622754208 17.921220026474714 M127.83220283599772 54.14727987716777 C134.99547975648431 45.90687241566457, 142.15875667697094 37.66646495416138, 159.32303622754208 17.921220026474714 M133.47527727285083 53.752677672238704 C139.90777337582978 46.35293737579948, 146.3402694788087 38.95319707936025, 159.0615794034806 24.319004043550592 M133.47527727285083 53.752677672238704 C141.77271026596918 44.2075728959221, 150.0701432590875 34.6624681196055, 159.0615794034806 24.319004043550592 M138.46229268071346 54.11278504753242 C143.373776160389 48.46276961992574, 148.2852596400646 42.81275419231905, 158.80012257941917 30.716788060626484 M138.46229268071346 54.11278504753242 C146.30494626051942 45.090844140544895, 154.14759984032534 36.06890323355738, 158.80012257941917 30.716788060626484 M144.10536711756657 53.718182842603355 C149.64530062640395 47.34521835593179, 155.18523413524136 40.97225386926022, 159.19472478434824 36.3598624974796 M144.10536711756657 53.718182842603355 C148.06447503732974 49.16375017092931, 152.0235829570929 44.60931749925527, 159.19472478434824 36.3598624974796 M149.09238252542917 54.07829021789707 C152.35916932678035 50.32028188849612, 155.62595612813152 46.56227355909516, 158.9332679602868 42.75764651455549 M149.09238252542917 54.07829021789707 C151.07904965559007 51.79289111569554, 153.065716785751 49.507492013494, 158.9332679602868 42.75764651455549 M154.73545696228229 53.68368801296802 C156.08904198267132 52.12656656902486, 157.44262700306038 50.5694451250817, 159.32787016521584 48.400720951408616 M154.73545696228229 53.68368801296802 C155.7443932571591 52.523039574443125, 156.7533295520359 51.36239113591823, 159.32787016521584 48.400720951408616" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C57.12026438964527 0, 114.24052877929054 0, 158.9999999999999 0 M0 0 C46.70137843983245 0, 93.4027568796649 0, 158.9999999999999 0 M158.9999999999999 0 C158.9999999999999 19.07754825735465, 158.9999999999999 38.1550965147093, 158.9999999999999 53 M158.9999999999999 0 C158.9999999999999 20.238926912005994, 158.9999999999999 40.47785382401199, 158.9999999999999 53 M158.9999999999999 53 C100.32112446175881 53, 41.64224892351773 53, 0 53 M158.9999999999999 53 C120.11725420458234 53, 81.23450840916479 53, 0 53 M0 53 C0 37.942913892026986, 0 22.885827784053976, 0 0 M0 53 C0 32.091631084028634, 0 11.183262168057261, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1432.7656686571772 689.1650017457355) rotate(0 58 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">without stock</text></g><g transform="translate(955.0750205101397 10) rotate(0 363 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># creating product with stock: unknown restock</text></g><g transform="translate(949.3607347958546 1107.5000000000002) rotate(0 388 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># creating product with stock: with restock never </text></g><g transform="translate(944.3607347958537 557.9401274238317) rotate(0 328 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># creating product next restock: with date</text></g><g transform="translate(1164.0591474942667 1281.4486080177874) rotate(0 49.370370370370324 25)"><path d="M0 0 C23.45212747769911 0, 46.90425495539822 0, 98.74074074074065 0 M0 0 C20.73102883723459 0, 41.46205767446918 0, 98.74074074074065 0 M98.74074074074065 0 C98.74074074074065 15.249146842397751, 98.74074074074065 30.498293684795502, 98.74074074074065 50 M98.74074074074065 0 C98.74074074074065 16.16197635885328, 98.74074074074065 32.32395271770656, 98.74074074074065 50 M98.74074074074065 50 C60.310699718686934 50, 21.88065869663322 50, 0 50 M98.74074074074065 50 C76.67712927301304 50, 54.613517805285426 50, 0 50 M0 50 C0 36.00903776939958, 0 22.018075538799163, 0 0 M0 50 C0 36.17254475597292, 0 22.345089511945844, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1199.6517400868593 1294.9486080177874) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1164.5591474942662 741.9486080177871) rotate(0 49.370370370370324 25)"><path d="M0 0 C36.75340074836791 0, 73.50680149673582 0, 98.74074074074065 0 M0 0 C27.905231700830686 0, 55.81046340166137 0, 98.74074074074065 0 M98.74074074074065 0 C98.74074074074065 10.219821208156645, 98.74074074074065 20.43964241631329, 98.74074074074065 50 M98.74074074074065 0 C98.74074074074065 19.624865702353418, 98.74074074074065 39.249731404706836, 98.74074074074065 50 M98.74074074074065 50 C63.61054871782516 50, 28.480356694909673 50, 0 50 M98.74074074074065 50 C59.59350222807924 50, 20.446263715417828 50, 0 50 M0 50 C0 37.69948286470026, 0 25.398965729400512, 0 0 M0 50 C0 35.27201617602259, 0 20.54403235204518, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1200.1517400868588 755.4486080177871) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1814.0809814929971 1842.1071341952047) rotate(0 412 266.56944444444457)"><path d="M0 0 C274.16651551425457 0, 548.3330310285091 0, 824 0 M0 0 C297.1355347424746 0, 594.2710694849492 0, 824 0 M824 0 C824 116.17392364551009, 824 232.34784729102017, 824 533.1388888888891 M824 0 C824 179.31330792878038, 824 358.62661585756075, 824 533.1388888888891 M824 533.1388888888891 C567.5864898949861 533.1388888888891, 311.17297978997226 533.1388888888891, 0 533.1388888888891 M824 533.1388888888891 C609.0388737887145 533.1388888888891, 394.0777475774288 533.1388888888891, 0 533.1388888888891 M0 533.1388888888891 C0 356.3190921438136, 0 179.49929539873796, 0 0 M0 533.1388888888891 C0 424.3866610642214, 0 315.6344332395537, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1907.0809814929967 1912.1071341952047) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1941.4976481596632 1976.507927845998) rotate(0 31 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current</text></g><g transform="translate(1892.9034923155082 2261.8565711090932) rotate(0 288.5 28.5)"><path d="M0 0 C210.84164893589912 0, 421.68329787179823 0, 576.9999999999999 0 M0 0 C226.9310409609228 0, 453.8620819218456 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 18.367664065584542, 576.9999999999999 36.735328131169084, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 15.9439185295254, 576.9999999999999 31.8878370590508, 576.9999999999999 57 M576.9999999999999 57 C425.9349190238862 57, 274.8698380477726 57, 0 57 M576.9999999999999 57 C374.58122126422813 57, 172.16244252845638 57, 0 57 M0 57 C0 42.26120472885668, 0 27.522409457713366, 0 0 M0 57 C0 40.81852394007146, 0 24.637047880142923, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2387.903492315508 2261.8565711090932) rotate(0 41 29)"><path d="M0 0 C25.642520541697746 0, 51.28504108339549 0, 82 0 M0 0 C26.927581327408557 0, 53.855162654817114 0, 82 0 M82 0 C82 13.545655617862941, 82 27.091311235725883, 82 58 M82 0 C82 15.152590277045967, 82 30.305180554091933, 82 58 M82 58 C58.220881395787 58, 34.441762791574 58, 0 58 M82 58 C60.07624772265554 58, 38.15249544531107 58, 0 58 M0 58 C0 41.12247482761741, 0 24.244949655234812, 0 0 M0 58 C0 44.12997278943658, 0 30.259945578873158, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2426.903492315508 2273.8565711090932) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2426.903492315508 2273.8565711090932) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.643740285856778 21.241428422796748, -3.4731381351771877 24.45763139895394, 0 34 M-5.814342436536368 18.025225446639556 C-4.192122791573714 22.482237290570144, -2.5699031466110593 26.939249134500734, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2426.903492315508 2273.8565711090932) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.643740285856778 21.241428422796748, 3.4731381351771877 24.45763139895394, 0 34 M5.814342436536368 18.025225446639556 C4.192122791573714 22.482237290570144, 2.5699031466110593 26.939249134500734, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1923.8894077379477 2277.8565711090932) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(2122.486963969733 1971.636797748869) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1943.9162053758716 2213.340383000729) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(2298.630491090157 2204.5546687150145) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.2611187238108525 4.646261209196374, 2.7836942716831534 2.8947384013168596, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.684746007607977 4.158933765479314, 3.6309488392774023 1.920083513882739, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.739643965731985 7.892056421213751, 7.346142550596359 3.743254388498496, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.495653567887535 9.3231036737803, 4.858161754907459 6.605348893631596, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.036540063134039 13.647528877358473, 8.201391569461917 8.85641528371205, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.724114151797874 12.856565368135819, 9.576539746789587 7.274488265266742, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.662661821618544 17.873902126926485, 11.059032881501865 11.666087345994956, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C8.512157091179688 14.59593279229763, 16.758023420624156 5.110148676737246, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C5.41527874113689 24.255496154016846, 10.825723544600008 18.031491383099794, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.2384643851211 19.857424174236503, 18.472094832568427 9.235347423539103, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C6.6630249272410005 28.91714070811543, 13.582672740869679 20.956996474221064, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C6.85865783887235 28.692090787162066, 13.973938564132379 20.506896632314337, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.757833347695543 28.0028779999832, 25.377687376849703 13.485396621103487, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C9.781914135509359 31.42628144412422, 19.425848952477335 20.332203509385533, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C15.385556051510763 31.07704116540005, 29.582471550560577 14.745358095306848, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C10.468500563770826 36.73346645504877, 19.748360575080703 26.058208674604284, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C23.047288933632743 28.36023806006382, 39.26286287795142 9.706354089563447, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C15.250952012991574 37.328897745620154, 23.67018903666908 27.643673460676105, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C22.597903379623546 34.974209350880756, 33.37707636207041 22.574189295903615, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C25.14197529521831 32.047589393482305, 38.46522019325994 16.72094938110671, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C33.226246254203865 28.844711833674296, 48.99068767437794 10.709796466419746, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C32.04277050544074 30.20614494576362, 46.62373617685169 13.432662690598391, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C34.020553773428105 34.02797790476389, 45.592287304963804 20.71622123330521, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C32.413442208827774 35.87674827555963, 42.37806417576315 24.413761974896698, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C37.03616338094111 36.65592823059203, 45.9804320831367 26.36672408989057, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C40.145889213497476 33.07859787770013, 52.199883748249434 19.212063384106777, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C41.38525638927088 37.749881380566954, 49.691602691933625 28.19452301454669, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C45.61547031299049 32.88357692693349, 58.152030539372845 18.461914107279764, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C49.12053993799855 34.948467912037486, 59.51909535253584 22.986298282416808, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C45.893685857880584 38.66053890051741, 53.065387192299916 30.41044025937665, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C53.860181013368766 35.593146904187826, 64.01136209541367 23.91554889142378, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.905471129667134 37.841783399680835, 60.10194232801041 28.412821882409805, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C56.96246633493155 38.12138822689979, 64.57285830168613 29.366633741776763, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C55.967382671880934 39.26610103541496, 62.5826909755849 31.656059358807116, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C59.720999826929734 41.045070794267275, 65.10290987781988 34.85389150121803, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.65157792303411 41.12493155927794, 64.96406607002864 35.01361303123936, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C63.66028229464649 42.610457043109555, 67.33840037640029 38.37926620383165, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.12857415865852 42.07174887739148, 68.27498410442436 37.30184987239548, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.63395468548822 44.13628221086212, 70.29872975022114 41.070809164043055, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.26606697808306 44.55948860686598, 69.56295433541081 41.91722195605079, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.80251865776677 46.58827866771744, 71.6488422869156 45.614694702459985, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.0006149851761 46.36039491107923, 72.04503494173426 45.15892718918357, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C16.193762657046317 0, 32.387525314092635 0, 72 0 M0 0 C17.504456117749214 0, 35.00891223549843 0, 72 0 M72 0 C72 18.31221368983388, 72 36.62442737966776, 72 46 M72 0 C72 9.369684558361769, 72 18.739369116723537, 72 46 M72 46 C56.27316487133503 46, 40.54632974267005 46, 0 46 M72 46 C54.11647951304913 46, 36.23295902609825 46, 0 46 M0 46 C0 29.97982518002391, 0 13.959650360047817, 0 0 M0 46 C0 28.27324437573552, 0 10.546488751471038, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(25.09190430596891 1844.8671778767057) rotate(0 412 84.5)"><path d="M0 0 C167.83794610202312 0, 335.67589220404625 0, 824 0 M0 0 C257.2014637738466 0, 514.4029275476933 0, 824 0 M824 0 C824 50.912549302354456, 824 101.82509860470891, 824 169 M824 0 C824 65.41591384075582, 824 130.83182768151164, 824 169 M824 169 C620.7830524712801 169, 417.5661049425602 169, 0 169 M824 169 C523.9873734682799 169, 223.97474693655965 169, 0 169 M0 169 C0 129.2093227174133, 0 89.41864543482662, 0 0 M0 169 C0 116.37540726996957, 0 63.75081453993916, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(118.09190430596891 1914.8671778767057) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(210.0919043059689 1900.1206990210958) rotate(0 57.125 26.5)"><path d="M0 0 C35.751818736735736 0, 71.50363747347147 0, 114.25 0 M0 0 C23.568781670089813 0, 47.137563340179625 0, 114.25 0 M114.25 0 C114.25 13.111394657567145, 114.25 26.22278931513429, 114.25 53 M114.25 0 C114.25 20.921167401596904, 114.25 41.84233480319381, 114.25 53 M114.25 53 C85.3933772421442 53, 56.536754484288394 53, 0 53 M114.25 53 C70.957347367052 53, 27.664694734103975 53, 0 53 M0 53 C0 40.39483490996063, 0 27.789669819921254, 0 0 M0 53 C0 38.70905513353645, 0 24.41811026707291, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(238.0919043059689 1915.1206990210958) rotate(0 29 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">infinite</text></g><g transform="translate(41.46471582028926 1777.6935835477643) rotate(0 328 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: product without stock</text></g><g transform="translate(1940.5139130676002 2023.2022267517714) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(2127.00322887767 2024.3310966546414) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1940.5139130676002 2074.2022267517714) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(2127.00322887767 2081.3310966546414) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1940.5139130676002 2138.2022267517714) rotate(0 53.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">after update</text></g><g transform="translate(2127.50322887767 2139.3310966546414) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(2093.0085709726354 2013.2666617032064) rotate(0 52.5 20.5)"><path d="M0 0 C29.524161050096158 0, 59.048322100192316 0, 105 0 M0 0 C36.504488250240684 0, 73.00897650048137 0, 105 0 M105 0 C105 10.325107226148248, 105 20.650214452296495, 105 41 M105 0 C105 9.495934232696891, 105 18.991868465393782, 105 41 M105 41 C74.71515481732786 41, 44.43030963465571 41, 0 41 M105 41 C82.80356870032847 41, 60.60713740065694 41, 0 41 M0 41 C0 29.32805828116834, 0 17.65611656233668, 0 0 M0 41 C0 30.19957923032343, 0 19.399158460646863, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2094.0085709726354 2070.2666617032064) rotate(0 51 22.5)"><path d="M0 0 C29.419563891738658 0, 58.839127783477316 0, 102 0 M0 0 C21.562247548252344 0, 43.12449509650469 0, 102 0 M102 0 C102 12.810667740181088, 102 25.621335480362177, 102 45 M102 0 C102 11.854120356962087, 102 23.708240713924173, 102 45 M102 45 C80.61069051995874 45, 59.22138103991747 45, 0 45 M102 45 C80.83884798511863 45, 59.67769597023725 45, 0 45 M0 45 C0 28.328645361587405, 0 11.65729072317481, 0 0 M0 45 C0 34.01418258436024, 0 23.028365168720484, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2315.630491090157 2215.0546687150145) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(2398.813380718231 2204.477938912777) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.2929012427776867 4.6096996034750255, 2.8472593096168217 2.8216151898741626, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.8517481006590948 5.117188240914534, 1.964953025379638 3.836592464753179, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.09839555532937 7.479359926500602, 8.06364572979113 2.9178613990721978, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.417184224681352 8.263003919919068, 6.701223068495093 4.4851493859091285, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.626918667928739 12.968375982103431, 9.382148779051317 7.498109493201968, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.0565887613896 13.62446548827937, 8.241488965973039 8.81028850555384, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.715422844976365 17.813207512523, 11.164554928217507 11.544698117187984, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.3614574419609475 18.220398129401186, 10.456624122186673 12.35907935094436, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.255578161251454 22.13847384128991, 14.506322384829135 13.797446757645918, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C9.11980589185277 19.993925156140836, 18.23477784603177 9.50834938734777, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.516160549036586 25.634983627284917, 19.28894398446085 14.392682312560037, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C8.576053189518198 26.716453433070832, 17.408729265424075 16.555621924131874, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C7.888108409659516 33.60485572055613, 15.638237500777649 24.689352062249355, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C10.083295149170755 31.079582247471905, 20.028610979800128 19.638805116080903, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C12.453225116675018 34.45030203235194, 23.717809680889086 21.491879829210635, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C17.347384032036103 28.820216236201507, 33.506127511611254 10.23170823690976, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C21.55381318289163 30.078285380667076, 36.27591137646919 13.142448730769956, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C17.43780191320385 34.813214709081535, 28.043888837093625 22.61230738759887, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C21.766510546829146 35.93061739971741, 31.71429069648162 24.487005393576922, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C25.628588071527656 31.48780542906593, 39.43844574587864 15.601381452273955, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C31.670289547372953 30.63463527221622, 45.87877426071611 14.289643343503592, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C27.76486623666902 35.1273108656745, 38.06792763930825 23.274994530420155, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C34.76571105880082 33.170772505260544, 47.08260187570924 19.001810434298527, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C35.18121105421159 32.69279443733952, 47.91360186653077 18.045854298456483, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C38.55263945950966 34.91142205950037, 49.01338424027381 22.87771174770723, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C44.16297574114715 28.457468447218805, 60.23405680354878 9.969804523144113, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C43.00925016649015 35.8816902457304, 52.939590246372155 24.458140744873575, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C44.183068593544675 34.53136661143301, 55.28722710048122 21.757493476278793, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C50.32497873871401 33.56291956726326, 61.92797295396676 20.215201592868357, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C50.67816756563262 33.15662229899264, 62.63435060780398 19.402607056327117, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C52.82528990392236 36.783652941408874, 61.94157987652086 26.296560965865872, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.6217935531243 38.168117121572735, 59.53458717492474 29.065489326193596, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C56.227503305490856 38.9668664764438, 63.10293224280475 31.057590240864783, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C58.04774158782605 36.87292186283115, 66.74340880747513 26.869701013639492, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C58.367247243970056 42.602384996897925, 62.39540471190053 37.96851990647933, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.4060130110888 41.40742167590182, 64.47293624613802 35.57859326448713, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C62.6812477262444 43.736707480176584, 65.3803312395961 40.63176707796569, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C63.23149924957844 43.10371551170787, 66.48083428626418 39.36578314102827, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.55133689041521 44.23132311218837, 70.13349416007512 41.26089096669556, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.69743126289549 44.06326076161429, 70.42568290503566 40.92476626554739, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.67077826831753 46.739828649694815, 71.38536150801714 45.91779466641475, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.17890829205336 46.15529192362867, 72.40162155548879 44.74872121428245, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C21.932444740086794 0, 43.86488948017359 0, 72 0 M0 0 C16.67752176448703 0, 33.35504352897406 0, 72 0 M72 0 C72 15.75052264314145, 72 31.5010452862829, 72 46 M72 0 C72 17.814064511843025, 72 35.62812902368605, 72 46 M72 46 C55.68650033399463 46, 39.37300066798925 46, 0 46 M72 46 C54.943963611871 46, 37.887927223742004 46, 0 46 M0 46 C0 35.37932818550617, 0 24.75865637101233, 0 0 M0 46 C0 32.96904833596199, 0 19.93809667192399, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2407.313380718231 2214.977938912777) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(2079.7394011263946 2209.0074728050386) rotate(0 71.39795918367349 18.459183673469397)"><path d="M-0.5332256704568863 0.6636613756418228 L142.39418197377609 -0.21745406091213226 L142.0709440778105 37.68443713169921 L-1.0454198271036148 37.45789322834838" stroke="none" stroke-width="0" fill="#fff"></path><path d="M1.2337845712900162 0.015040740370750427 C28.56659816368199 -0.6406339541427333, 60.50770338205053 0.5121108159073156, 142.37637738092826 0.3370445817708969 M0.969361238181591 0.0363282635807991 C53.000192554431926 -1.0081881181698065, 105.90986145613148 -1.295944322774909, 143.62886483921693 -0.8408563658595085 M144.35668972834037 -0.005633488297462463 C143.05124262072758 7.868321860368756, 144.06162193515019 15.354444567433411, 143.8131206403582 38.28928787093986 M142.8561464096515 -0.7321699187159538 C142.48485237584282 9.912379578227283, 143.74567300305534 21.5151276225338, 141.94549305691407 36.03820743989576 M143.7820804487078 35.948196714934 C103.69038879167367 34.684089546974484, 62.7665590731161 37.91535604554016, 0.4336971193552017 35.91201574188102 M143.5466149593799 37.79356136750806 C111.85944364060252 36.627684100235264, 80.07601225102437 37.33753226574735, -0.7850254252552986 36.95606484841932 M1.762398138642311 37.63891774993766 C1.6323946739687605 28.70091673362313, -1.345415708874257 16.862387763207035, 0.34800995886325836 -1.2112757116556168 M0.9106399342417717 36.41863313150037 C-0.325401134167095 27.893693413798267, -0.25188214269675807 20.403612399709445, -0.8587933257222176 -0.8126259371638298" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2114.387360310068 2215.4666564785084) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2144.887360310068 2214.9666564785084) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2233.887360310068 2207.9666564785084) rotate(0 17.75 17.75)"><path d="M-0.21745406091213226 -0.7249742895364761 L36.266069784760475 -1.0454198271036148 L36.039525881409645 34.953821524977684 L-0.5811678022146225 33.94901929795742" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-1.6772920042276382 -0.5623360723257065 C12.31542037986219 -0.07014615889638665, 26.016979809850454 1.8284014904871584, 36.63062797486782 0.5429748445749283 M-0.6205151602625847 -0.8873010352253914 C13.182961030676962 0.9743744572624564, 27.34160842522979 -0.8362084189429879, 36.01312632113695 -0.8793523982167244 M35.90208564698696 1.0762657076120377 C35.920972196497026 14.437571438774468, 36.869920579828324 26.54535238966346, 36.92520032823086 35.345044031739235 M34.882127709686756 0.6866742894053459 C35.69470356430858 10.01338423974812, 34.359919771812855 21.528746228665113, 35.70761031657457 35.05759137123823 M33.8974636644125 35.468544855713844 C24.72829246856272 36.09227961298078, 9.692773006111384 34.0602547525987, -0.8783871084451675 34.875888243317604 M35.65491981059313 35.63417714089155 C23.049795074388385 34.88912582490593, 8.907751698046923 35.3702452192083, 0.15197987109422684 35.72035758942366 M1.736088141798973 34.21069277822971 C-0.2767997700348496 22.069753262773155, -1.3391734081879259 6.453609279543162, -1.8685132414102554 0.9973238855600357 M-0.6993556544184685 34.90320556610823 C0.31328725669533014 20.75843490771949, -0.6352202906832098 7.467843631654976, 0.3318306878209114 -0.20086819678544998" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2234.887360310068 2208.9666564785084) rotate(0 17.5 4.5)"><path d="M-0.7249742895364761 0.7660697847604752 L33.954580172896385 0.5395258814096451 L34.453821524977684 8.418832197785378 L-1.5509807020425797 9.610531702637672" stroke="none" stroke-width="0" fill="#000"></path><path d="M-0.5623360723257065 -0.5245472341775894 C10.10678030923009 0.6305706825107336, 22.531649254262447 1.4241540756076574, 35.54297484457493 1.938722476363182 M-0.8873010352253914 -0.9082713648676872 C11.460095394402742 -0.6236760597676039, 20.276661440730095 0.7303066272288561, 34.120647601783276 0.7803856804966927 M35.48431956842542 0.18988746330142015 C35.46070591054857 3.314379166066647, 34.870041213855146 6.03627000823617, 34.93026981428265 9.054205238074065 M35.30900343023241 0.20458074845373636 C34.56563543990254 1.6449751149863008, 34.85883403733373 4.175518788769842, 34.800916117057206 9.221886468306185 M34.968544855713844 10.628730162978172 C26.631495725363493 7.713204536214471, 15.797269470989704 7.84116903282702, -0.6241117566823959 10.501393184065819 M35.13417714089155 9.864767976105213 C20.651795502752066 9.091919435188174, 7.569735825061798 9.206201804801822, 0.22035758942365646 9.881199069321156 M-0.580188249796629 8.932994066923857 C0.0806218392401934 5.4306782484054565, -0.7464274399727583 2.1106736786663536, 0.4487957485020161 0.8195759408175946 M-0.26855749525129796 9.06114672459662 C-0.326128788292408 6.384079122170806, -0.05964756757020948 4.6488453973084685, -0.0903906885534525 -0.04892716370522976" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2242.387360310068 2200.4666564785084) rotate(0 3 7)"><path d="M0.7660697847604752 -1.0454198271036148 L6.539525881409645 -0.546178475022316 L5.4188321977853775 12.44901929795742 L0.6105317026376724 12.975818023085594" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.1573641702532768 -0.1258622959256172 C1.3105869054794312 0.4605720810592175, 2.7581354543566703 0.1797625355422497, 6.581616742908954 0.021796958148479484 M-0.27248140946030613 0.24988394156098365 C1.308709875494242 -0.24005285918712616, 3.2758714668452744 -0.2105676573514938, 6.234115704149008 -0.0008450232446193806 M6.295380498468876 0.712041591107845 C6.987790362089871 4.039350315928459, 7.0257862250506875 8.383771772682667, 6.084319259226322 12.974962113797664 M6.318236719816923 -0.5952977173030376 C5.297385944426059 3.2569841168820863, 6.058825100958347 6.482704214006663, 6.345156728476286 13.660440278798342 M6.488619048893452 14.13010913580656 C3.6300677686929705 13.51955802336335, 1.6004303678870209 13.811822818666696, 0.4504179552197457 14.525116412341594 M6.259430392831564 13.76449237242341 C3.9169691093266006 13.916878819167614, 1.856913679093122 14.07415790528059, 0.2643597207963467 14.108082560449839 M-0.10423145145177837 14.243606971204281 C-0.9694191680848598 7.575133657455444, -1.4294854389131069 3.9342222943902008, 1.2748959079384803 -0.6996279016137122 M0.09511712715029719 13.398844671994448 C-0.4709283643960952 10.551809810847045, 0.3301912730932236 7.336269853264093, -0.07610892131924629 -0.2537410013377666" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2257.387360310068 2200.4666564785084) rotate(0 3 7)"><path d="M-1.0454198271036148 0.5395258814096451 L5.453821524977684 -0.5811678022146225 L4.44901929795742 14.610531702637672 L-1.0241819769144058 15.71179236471653" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.1258622959256172 0.10111337453126912 C1.7405143976211548 0.35492480948567384, 2.7565172508358957 0.5973531599342823, 6.02179695814848 -0.23903321474790573 M0.24988394156098365 -0.25225690975785253 C1.1343999825417996 0.20843417823314664, 2.591576088219881 0.288611986041069, 5.9991549767553805 0.009882958978414513 M6.712041591107845 0.9596443668007848 C5.8685684402287 5.4791001826524734, 5.966869421750307 9.047239165008069, 4.974962113797664 13.19619512706995 M5.404702282696962 -0.6161119349300861 C5.9569233903288845 3.4803060151636602, 5.786448360383511 7.015114315599203, 5.660440278798342 14.312697874754667 M6.130109135806561 13.698094518482685 C4.201262578368187 13.785345772355795, 3.1742882743477816 14.49927986010909, 0.5251164123415947 14.294340141117573 M5.76449237242341 14.011309250444175 C4.015876304358243 14.097480039894581, 2.2802035041153426 14.316245799362658, 0.10808256044983866 14.25327547416091 M0.24360697120428076 13.152107001841069 C-0.9526626242697239 7.276779031753539, 0.7883782254159449 4.444372509419917, -0.6996279016137122 -0.9384387955069542 M-0.6011553280055523 13.431161843985318 C-0.10307776391506192 11.011379397660493, -0.05705651700496668 7.482086911052464, -0.2537410013377666 0.26812442466616626" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2250.887360310068 2216.9666564785084) rotate(0 0.20799543491568784 12.861703209578991)"><path d="M0.3370445817708969 1.4790066629648209 C1.1970670645311474 7.748928401209415, 2.141103164218366 14.483514787964525, -0.2967773824930191 25.25896967947483 M-0.8408563658595085 -0.9776364043354988 C0.3780693576857448 4.715135126076639, 0.7509636652842164 11.190323036797345, 0.5329431965947151 26.70104282349348" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2241.387360310068 2215.9666564785084) rotate(0 0.250685129314661 14.525629587471485)"><path d="M1.4790066629648209 1.1306279748678207 C0.6211254256218672 9.60351373706013, 0.28769342735409736 14.533647750355303, -0.7410303205251694 26.225397929549217 M-0.9776364043354988 0.5131263211369514 C-0.5415409543365239 6.641430140696466, 0.43958113148808486 11.750827835164964, 0.7010428234934807 28.53813285380602" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2260.887360310068 2217.4666564785084) rotate(0 0.881765391677618 12.415816836059093)"><path d="M1.1306279748678207 0.5429748445749283 C2.400230086557567 7.176649219505489, 0.08326465371996172 12.978047614134848, -0.2746020704507828 23.683457270264626 M0.5131263211369514 -0.8793523982167244 C1.14090927798301 9.785861305184664, 0.9684087648615243 19.763734376095236, 2.038132853806019 25.71098607033491" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2269.887360310068 2225.4666564785084) rotate(0 -17.636783942580223 0.7824612371146031)"><path d="M0.5429748445749283 1.938722476363182 C-10.700319261848927 -0.6045706267654896, -22.78698189854622 -1.5823953147232532, -35.816542729735374 1.665892943739891 M-0.8793523982167244 0.7803856804966927 C-7.343905100226403 0.20740614578127864, -14.481133888661862 0.7125958029925823, -33.78901392966509 0.5086011365056038" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2269.387360310068 2234.4666564785084) rotate(0 -16.776338193565607 0.44000930051925025)"><path d="M1.938722476363182 0.0726565271615982 C-14.25333623595536 -0.7693445153534413, -28.6933729172498 -0.9709065914154053, -34.33410705626011 -0.681712731719017 M0.7803856804966927 -0.0028167441487312317 C-7.24243448458612 0.08671231970191007, -14.01775377176702 0.2988919261097909, -35.491398863494396 1.6854602620005608" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1017.5202442733594 258.5144794614512) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1197.5202442733594 263.5144794614512) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">unknown</text></g><g transform="translate(1150.5202442733594 252.01447946145117) rotate(0 94.5 23)"><path d="M0 0 C70.95094501152634 0, 141.9018900230527 0, 189 0 M0 0 C63.35328986272216 0, 126.70657972544431 0, 189 0 M189 0 C189 13.924661253392697, 189 27.849322506785395, 189 46 M189 0 C189 12.685054327547553, 189 25.370108655095105, 189 46 M189 46 C127.70845397338272 46, 66.41690794676542 46, 0 46 M189 46 C122.6631328381598 46, 56.32626567631959 46, 0 46 M0 46 C0 27.616077138483522, 0 9.232154276967044, 0 0 M0 46 C0 29.85423505157232, 0 13.708470103144641, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1493.9141836672989 256.7974508453981) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.832670802688257 3.9887657545743176, 3.9267984294379623 1.5797474920727463, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.9784212350667891 4.971467469048262, 2.2182992941950266 3.545150921020635, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.37460858018232 8.311981596267827, 6.6160717794970285 4.5831047386066475, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.1916574118686847 8.522442840360005, 6.250169442869758 5.004027226791005, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C3.473213240430706 14.295562257136579, 7.074737924055251 10.152482043268261, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.64777413008577 12.94438451731999, 9.423859703365379 7.450126563635084, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.737950658843504 17.787292227166486, 11.209610555951786 11.492867546474958, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.81762107869453 17.695641893179832, 11.368951395653838 11.30956687850165, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C8.842980156026423 20.312376736941175, 17.681126374379073 10.145252548948452, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C10.475407666861928 18.43448370139758, 20.945981396050083 6.389466477861255, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.226002889761762 25.968771831627873, 18.708628665911203 15.060258721245951, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C9.014899617064208 26.211618367200103, 18.286422120516093 15.545951792390408, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.395472293624229 26.118989894152126, 26.684788181735552 11.981749150109657, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C15.87821753425575 24.413286613372307, 29.650278662998595 8.570342588550023, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C20.763277283758065 24.89068055698112, 34.43338275414061 9.165023100473938, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C20.995815386415636 24.62317607020873, 34.89845895945576 8.630014126929158, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C26.521045039779274 24.364138781161774, 40.305843829329916 8.506541753764303, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.469745334088707 29.02462597077197, 32.20324441794878 17.827516132984698, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C31.71044703869748 24.49142701605997, 45.69763241930372 8.401010848266978, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C28.99194369025733 27.618707383030046, 40.26062572242342 14.65557158220713, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C32.31425868693094 29.893833518843415, 41.918240307908036 18.84571647854015, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C35.053310785473414 26.742914518947746, 47.396344504992975 12.543878478748816, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C37.94887564361349 29.508960531907263, 47.54439978442001 18.47057270959692, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C39.671166823849696 27.52769117012815, 50.988982144892425 14.508033986038686, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C42.910973032649395 29.897732808829765, 52.4815791546292 18.888009888148204, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C40.845781098123865 32.273464365155576, 48.35119528557814 23.639473000799825, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C47.85792328245242 30.303929876185542, 56.73240521738214 20.09500622778881, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C51.850272077486025 25.711257951772012, 64.71710280744935 10.909662378961755, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C57.216362961433376 25.635288875425577, 70.46226916748145 10.397616850975169, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C57.89790854275577 24.85126037059122, 71.82536033012623 8.829559841306455, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C58.74595402494579 29.972707986816566, 67.87837685765317 19.46705727868621, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C61.246167829479475 27.096541014783178, 72.87880446672051 13.714723334619432, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C62.96835188884398 31.212407228293863, 71.33615717758694 21.586348386347094, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C65.56105143686412 28.229847578835304, 76.52155627362723 15.62122908742997, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.82577589031709 30.571224056124393, 77.40793074368003 20.698584246937216, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C66.07067846212402 33.74060109633355, 71.89773588729389 27.037338327355528, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C71.06352488858407 34.09400065205085, 76.89641333235139 27.384030063496404, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C73.56723629186938 31.213810152912455, 81.903836138922 21.623649065219617, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C75.80055067590764 34.74168818894585, 80.7273904701454 29.074007342215474, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C76.02338835294613 34.485342765342246, 81.17306582422239 28.56131649500827, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C78.70849720079879 37.49349072364621, 81.55626811206508 34.217505036322486, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C80.34716357299014 35.60842069910181, 84.83360085644779 30.44736498723369, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C84.11595924225449 37.369929574732005, 86.72811775812337 34.36498494342312, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.01921927251676 38.6315845868548, 84.53463781864794 36.88829496766872, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C27.48174813024704 0, 54.96349626049408 0, 85.70000000000013 0 M0 0 C23.505595291927495 0, 47.01119058385499 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 11.344363928586246, 85.70000000000013 22.68872785717249, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 10.094785391539336, 85.70000000000013 20.189570783078672, 85.70000000000013 39 M85.70000000000013 39 C51.926018922403536 39, 18.15203784480694 39, 0 39 M85.70000000000013 39 C56.20642761819073 39, 26.71285523638133 39, 0 39 M0 39 C0 27.112638091295956, 0 15.225276182591912, 0 0 M0 39 C0 29.743110812455413, 0 20.48622162491083, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1508.0141836672988 264.59745084539827) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1031.2702442733594 1351.859549495717) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1211.2702442733594 1356.859549495717) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1164.2702442733594 1345.359549495717) rotate(0 94.5 23)"><path d="M0 0 C43.93486205711961 0, 87.86972411423922 0, 189 0 M0 0 C68.47068202123046 0, 136.94136404246092 0, 189 0 M189 0 C189 11.259486769139768, 189 22.518973538279536, 189 46 M189 0 C189 17.517849661409855, 189 35.03569932281971, 189 46 M189 46 C146.6980354629457 46, 104.3960709258914 46, 0 46 M189 46 C115.34704683199524 46, 41.694093663990486 46, 0 46 M0 46 C0 35.74880533367396, 0 25.497610667347907, 0 0 M0 46 C0 28.277714131772516, 0 10.555428263545032, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1429.4141836672989 1356.1425208796638) rotate(0 21.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1507.6641836672989 1350.1425208796638) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.1680636657135124 4.75330880816367, 2.597584155488473 3.1088335992514513, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.3595743526068416 4.53300096431639, 2.9806055292751314 2.6682179115568903, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.4478070758704646 9.378144766593092, 4.762468770873318 6.7154310792571765, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.3163147367899652 9.52940939926289, 4.499484092712319 7.017960344596775, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C6.023305356175783 11.36201685168006, 12.174922155545405 4.285391232355222, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.75875087257915 11.666351971598296, 11.64581318835214 4.894061472191693, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.375578048976325 17.053785821978902, 12.484865336217428 10.025854736099792, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.41004029965436 17.01414153755716, 12.553789837573499 9.946566167256305, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C6.827577750615223 22.630831991963447, 13.650321563556673 14.782163058992994, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C7.413960704011991 21.95627556784286, 14.82308747035021 13.433050210751812, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C10.72703285783447 24.24203437806507, 21.710688602056617 11.606783814120345, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C6.754050063429236 28.812428267181495, 13.76472301324615 20.747571592353193, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.642324438868245 25.835018985008674, 27.178492472223585 11.413807331822756, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C11.04377932938536 29.974671590917456, 19.981402253257816 19.693112543640318, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C21.14128179646881 24.455836107771706, 35.1893917795621 8.295334202055109, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C19.96728173679834 25.806368686492192, 32.84139166022117 10.996399359496078, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C21.78015876421793 29.81790457479522, 30.824071278207228 19.414073341031195, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C20.85053294298741 30.887316750075698, 28.96481963574619 21.552897691592147, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C30.729227301916936 25.62019120179402, 43.73519294574263 10.658539219735083, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C25.523898748441372 31.60823671891775, 33.324535838791505 22.63463025398254, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C30.290159765407076 32.22229297125459, 37.8700424648603 23.5026353833625, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C31.891231849548355 30.38047022797496, 41.072186633142856 19.81898989680325, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C37.29675856475941 30.259135417230254, 46.24016562671184 19.970922480242898, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C42.26060010749494 24.548888928016098, 56.167848712182916 8.550429501814584, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C46.267118746162495 26.036928809974075, 59.19387058165542 11.166401890436823, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C43.78269662308831 28.894929530559565, 54.225026335507046 16.882403331607804, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C50.99459514721693 26.69560165914144, 63.00574894691116 12.878349793700604, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C53.051938699783015 24.32889863346957, 67.12043605204335 8.144943742356872, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C56.12724425861781 26.888176622858133, 68.28403176185031 12.903392345840285, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C51.517456439565606 32.19113089388803, 59.064456123745906 23.509300887900075, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C56.73158547110044 32.28997393165974, 63.84963974996244 24.101589168372556, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C58.92890103844186 29.762251522295255, 68.24427088464529 19.04614434964359, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C63.51834177647235 30.579716237275164, 72.43613695284365 20.320966404309697, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C67.96149522775357 25.468452878486247, 81.32244385540612 10.098439686731854, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.69597646440566 30.72054121496833, 77.14833189185715 20.997218564625086, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C69.59998204984933 29.68060174952259, 78.95634306274451 18.91733963373361, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C73.04455754813084 31.815083266835224, 80.85847865144494 22.826195293065158, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C73.3015613409806 31.519434223004897, 81.37248623714444 22.2348972054045, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C77.42636322445753 32.87140479703058, 83.97901556724518 25.33344055838493, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C75.26372668564532 35.359233547581944, 79.65374248962075 30.30909805948766, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.06707914381792 37.080989384997075, 82.27343199810335 33.392502359024206, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.34946265055413 36.75614432012746, 82.83819901157577 32.74281222928498, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.41321672213974 38.17834236828289, 85.32263271789387 35.98181053052489, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C82.85958293045782 38.815225191403755, 84.21536513453002 37.25557617676662, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C31.05126833669846 0, 62.10253667339692 0, 85.70000000000013 0 M0 0 C25.29206354983155 0, 50.5841270996631 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 7.99688525274396, 85.70000000000013 15.99377050548792, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 14.824664580076934, 85.70000000000013 29.64932916015387, 85.70000000000013 39 M85.70000000000013 39 C54.724368588999 39, 23.74873717799786 39, 0 39 M85.70000000000013 39 C67.95390589348982 39, 50.20781178697951 39, 0 39 M0 39 C0 26.822433661669493, 0 14.644867323338985, 0 0 M0 39 C0 24.301500666886568, 0 9.603001333773136, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1521.7641836672988 1357.942520879664) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(916.5709458336519 1853.4558419016075) rotate(0 412 251.56944444444457)"><path d="M0 0 C237.1811243116856 0, 474.3622486233712 0, 824 0 M0 0 C172.54678277373316 0, 345.0935655474663 0, 824 0 M824 0 C824 112.20485208479487, 824 224.40970416958973, 824 503.13888888888914 M824 0 C824 119.43370529266288, 824 238.86741058532576, 824 503.13888888888914 M824 503.13888888888914 C570.5043474137783 503.13888888888914, 317.0086948275566 503.13888888888914, 0 503.13888888888914 M824 503.13888888888914 C513.3626734793186 503.13888888888914, 202.72534695863715 503.13888888888914, 0 503.13888888888914 M0 503.13888888888914 C0 365.26225807884106, 0 227.38562726879297, 0 0 M0 503.13888888888914 C0 376.0602100148592, 0 248.98153114082922, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1009.5709458336516 1923.4558419016075) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1046.9876125003182 2113.8566355524) rotate(0 81 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock is 15</text></g><g transform="translate(995.3934566561632 2229.455278815496) rotate(0 288.4999999999999 28.5)"><path d="M0 0 C115.43863970711827 0, 230.87727941423654 0, 576.9999999999999 0 M0 0 C117.08345114514229 0, 234.16690229028458 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 15.664222944527864, 576.9999999999999 31.32844588905573, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 11.606445883959532, 576.9999999999999 23.212891767919064, 576.9999999999999 57 M576.9999999999999 57 C453.28450038954605 57, 329.5690007790922 57, 0 57 M576.9999999999999 57 C367.806977636367 57, 158.61395527273407 57, 0 57 M0 57 C0 45.09641448184848, 0 33.192828963696954, 0 0 M0 57 C0 39.794306612759826, 0 22.58861322551965, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1490.393456656163 2229.455278815496) rotate(0 41 29)"><path d="M0 0 C19.06852476447821 0, 38.13704952895642 0, 82 0 M0 0 C19.28069086223841 0, 38.56138172447682 0, 82 0 M82 0 C82 23.10410188883543, 82 46.20820377767086, 82 58 M82 0 C82 13.37995346337557, 82 26.75990692675114, 82 58 M82 58 C60.44384906440973 58, 38.887698128819466 58, 0 58 M82 58 C49.94147968143225 58, 17.882959362864497 58, 0 58 M0 58 C0 42.55719810277223, 0 27.11439620554447, 0 0 M0 58 C0 45.967039696872234, 0 33.93407939374447, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1529.393456656163 2241.455278815496) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1529.393456656163 2241.455278815496) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.172222794085663 22.536912084315766, -2.5301031516349584 27.04859872199198, 0 34 M-5.814342436536368 18.025225446639556 C-3.6142086520908348 24.070043339182902, -1.4140748676453017 30.11486123172625, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1529.393456656163 2241.455278815496) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.172222794085663 22.536912084315766, 2.5301031516349584 27.04859872199198, 0 34 M5.814342436536368 18.025225446639556 C3.6142086520908348 24.070043339182902, 1.4140748676453017 30.11486123172625, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1026.3793720786027 2245.455278815496) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(922.2888234866489 1782.589056174199) rotate(0 244 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: initial state</text></g><g transform="translate(1046.4061697165266 2180.939090707132) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1401.120455430812 2172.1533764214173) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.0909203447289906 4.842052047452373, 2.4432975135194295 3.2863200778288584, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.9609887858427564 4.991521207896073, 2.183434395746961 3.585258398716258, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.055934280738017 7.528206035320831, 7.978723180608424 3.015553616712655, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.954120567050224 8.795697722190123, 5.775095753232837 5.5505369904512385, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C3.931789477079368 13.76803064219365, 7.9918903973525754 9.097418813382406, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.3020586491481456 14.49245309169886, 6.73242874149013 10.546263712392824, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C8.176788733731076 14.981729955488115, 16.08728670572693 5.881743003118217, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C4.890129338938814 18.76259908855326, 9.513967916142406 13.443481269248503, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C5.323469502644222 24.361110601469832, 10.64210506761467 18.242720278005763, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C7.166012140537533 22.241507761879706, 14.327190343401293 14.003514598825511, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.004653777300126 26.22340485757015, 18.26593044098793 15.569524773130503, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C11.149446486180546 23.756103085236145, 22.55551585874877 10.634921228462495, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.671130608757073 28.10261809167754, 25.204281898972763 13.68487680449218, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C13.073179670552559 27.640113552835167, 26.008380022563735 12.759867726807432, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C13.602967845916378 33.127674320200626, 26.017295139371807 18.846624404907992, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C17.00985498266705 29.208498991115004, 32.83106941287315 11.008273746736755, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C15.36440546127615 37.19838448302329, 23.89709593323823 27.382646935482388, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C19.931300316280954 31.94477292272559, 33.030885643247835 16.87542381488699, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C23.96493341124092 33.40162119078579, 36.11113642530516 19.429012975713682, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C25.335211234181067 31.82529687415986, 38.85169207118546 16.276364342461818, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C26.923227680139938 36.095505271404654, 36.38465052625008 25.211383341880463, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C32.18211722848127 30.045844877928026, 46.902429622932736 13.112062554927206, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C32.21982389418312 36.09948066778622, 41.99082754647384 24.859226759349877, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C35.79387852241059 31.98800113779131, 49.13893680292877 16.63626769936006, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C41.15523817334838 31.917474722426277, 54.21858166795123 16.889817073559055, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C42.866012590319265 29.94945388126103, 57.640130501893 12.953775391228561, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C48.41568418493783 29.66229935518325, 63.75245828326753 12.019358963779283, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C41.81100230326506 37.26011673160461, 50.54309451992199 27.214993716622008, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C48.26506034763322 35.93258460581613, 57.80813617180518 24.954531669974106, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C50.20061821505383 33.70597998478739, 61.67925190664641 20.501322427916612, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C51.89049263716788 37.859014184239776, 60.0719853430119 28.447283451527678, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C54.73073308969366 34.591691298743015, 65.75246624806346 21.912637680534164, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C58.30371628344327 36.57845665994509, 67.25535819870957 26.280770607867368, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C58.308125954591326 36.57338391357013, 67.26417754100568 26.270625115117458, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C60.96690458165455 39.61182132602541, 67.59471938726952 31.98739256473429, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.307395360843515 41.52086830513838, 64.27570094564744 35.80548652296024, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C62.722751713026106 43.68896260500919, 65.46333921315951 40.53627732763091, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.43769243441663 41.71614897886472, 68.89322065594057 36.59065007534198, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.65326893737495 44.11406370568252, 70.3373582539946 41.02637215368385, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.24603681104708 44.58253067821555, 69.52289400133883 41.96330609874992, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.69600811909511 46.7108050264414, 71.4358212095723 45.859747419907904, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.1135379226463 46.23049193136291, 72.27088081667466 44.899121229750946, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C28.68456582427025 0, 57.3691316485405 0, 72 0 M0 0 C21.353178030252458 0, 42.706356060504916 0, 72 0 M72 0 C72 15.29855439811945, 72 30.5971087962389, 72 46 M72 0 C72 10.088826654851436, 72 20.177653309702873, 72 46 M72 46 C46.18587675690651 46, 20.37175351381302 46, 0 46 M72 46 C55.007525628805155 46, 38.01505125761032 46, 0 46 M0 46 C0 34.933114741742614, 0 23.86622948348522, 0 0 M0 46 C0 33.28402233272791, 0 20.568044665455815, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1043.0038774082552 1977.5509344581742) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(1229.4931932183251 1978.6798043610443) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1043.0038774082552 2028.5509344581742) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(1229.4931932183251 2035.6798043610443) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">0</text></g><g transform="translate(1195.4985353132902 1967.6153694096092) rotate(0 52.5 20.5)"><path d="M0 0 C24.69663779810071 0, 49.39327559620142 0, 105 0 M0 0 C32.88236404582858 0, 65.76472809165716 0, 105 0 M105 0 C105 12.499904776364566, 105 24.999809552729133, 105 41 M105 0 C105 11.410576330870391, 105 22.821152661740783, 105 41 M105 41 C67.12486464902759 41, 29.249729298055158 41, 0 41 M105 41 C76.42338036373258 41, 47.846760727465146 41, 0 41 M0 41 C0 28.070744704455137, 0 15.141489408910275, 0 0 M0 41 C0 29.45000053718686, 0 17.90000107437372, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1196.4985353132902 2024.6153694096092) rotate(0 51 22.5)"><path d="M0 0 C23.705919201672078 0, 47.411838403344156 0, 102 0 M0 0 C20.977860300242902 0, 41.955720600485805 0, 102 0 M102 0 C102 11.528187688440084, 102 23.05637537688017, 102 45 M102 0 C102 15.508610609918835, 102 31.01722121983767, 102 45 M102 45 C71.24600254744291 45, 40.492005094885826 45, 0 45 M102 45 C66.05086522847414 45, 30.101730456948275 45, 0 45 M0 45 C0 29.324226681143045, 0 13.64845336228609, 0 0 M0 45 C0 31.80285060033202, 0 18.60570120066404, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1418.120455430812 2182.6533764214173) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1501.303345058886 2172.07664661918) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C0.965600441736221 4.986216104651257, 2.19265770753389 3.574648192226626, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.6133162138232222 4.241104343583608, 3.4880892517078927 2.084424670091326, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.648335235138404 7.997095100192062, 7.163525089409197 3.9533317464551168, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.3500231501029547 8.34026389830907, 6.566900919338298 4.639669342689134, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.57169766949688 11.881532066893634, 11.271706782187598 5.324421662782372, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C4.36987753206746 13.264067984154451, 8.86806650732876 8.089493497304003, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.856252205894873 16.50083345769221, 13.446213650054524 8.919950007526408, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.311331628755643 18.27806128129883, 10.356372495776064 12.474405654739646, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C8.394970302828646 20.82775311818362, 16.78510666798352 11.176005311433336, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C6.507872769949093 22.998610501352967, 13.010911602224413 15.517720077772035, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.84100486007546 25.261292994600325, 19.938632606538597 13.645301047190852, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.544475196613628 27.903148165692713, 15.345573279614934 18.929011389375628, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C11.607492680632955 29.326193560913538, 23.077006042724527 16.132027742964173, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C12.681829312882755 28.090310640453154, 25.225679307224127 13.660261902043398, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C12.948784612455563 33.88022604450763, 24.708928672450178 20.351727853522007, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C16.383884466024085 29.928595697312886, 31.579128379587225 12.44846715913252, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C16.27626263619843 36.1494127970949, 25.720810283082795 25.284703563625595, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C22.162332380717757 29.378264120300425, 37.49294977212145 11.742406210036656, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C22.63473288331568 34.931841853379694, 33.45073536945469 22.489454300901485, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C27.60017800282167 29.219750660110254, 43.38162560846666 11.065271914362604, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C28.03773077370014 34.813416122822915, 38.61365671337049 22.647205044716983, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C28.73180183369619 34.01497870303706, 40.001798833362585 21.050330205145276, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C37.72516161241202 29.76631408565349, 53.00150298293164 12.19289359508442, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C37.506818107690144 30.01748955540745, 52.56481597348788 12.69524453459234, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C38.80601667841654 34.61994491176037, 49.52013867808756 22.29475745222724, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C39.37094751487938 33.97006632522858, 50.65000035101323 20.995000279163666, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C41.875261774474744 37.18619466606027, 50.671613462341355 27.067149585533315, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C45.35421394435585 33.18411799959606, 57.629517802103564 19.062996252604908, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C52.548988125867446 31.004489430918987, 66.37599172827363 15.09834132017981, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C50.037153528279696 33.89402459614863, 61.35232253309814 20.8774116506391, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C52.899531906204274 36.69824728749493, 62.09006388108468 26.125749658037993, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C52.95433495201077 36.63520359497965, 62.19966997269768 25.99966227300743, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C56.56838460507855 38.57472739878568, 63.78469484198013 30.273312085548547, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C56.00765249429886 39.219775903940985, 62.66323062042075 31.563409095859154, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C58.72711598403206 42.188403367584165, 63.11514219202454 37.14055664785182, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C58.148177076502364 42.85439639651738, 61.95726437696514 38.47254270571823, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C64.14588211683927 42.05183834910684, 68.30960002078587 37.262028815826206, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.17158984037532 42.022264996129415, 68.36101546785792 37.202882109871354, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.11690508387139 44.73107973752833, 69.26463054698746 42.26040421737549, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.50200064914837 44.28807796547278, 70.03482167754143 41.374400673264375, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.7788649183992 46.61548918219853, 71.60153480818046 45.669115731422174, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.01649475781093 46.342127322326256, 72.07679448700394 45.12239201167763, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C24.9990046441555 0, 49.998009288311 0, 72 0 M0 0 C18.89581623673439 0, 37.79163247346878 0, 72 0 M72 0 C72 12.982670523226261, 72 25.965341046452522, 72 46 M72 0 C72 17.12471861690283, 72 34.24943723380566, 72 46 M72 46 C51.709999948740005 46, 31.41999989748001 46, 0 46 M72 46 C43.89292697310448 46, 15.785853946208952 46, 0 46 M0 46 C0 36.00459248870611, 0 26.00918497741222, 0 0 M0 46 C0 33.257136107981204, 0 20.514272215962407, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1509.803345058886 2182.57664661918) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1182.2293654670493 2176.6061805114414) rotate(0 71.39795918367349 18.459183673469397)"><path d="M1.672937735915184 -1.6225496381521225 L144.7023351262419 0.6433686167001724 L142.84241510137008 37.36221537571777 L0.9881971925497055 38.18504891377319" stroke="none" stroke-width="0" fill="#fff"></path><path d="M-0.5037814229726791 1.9669316858053207 C33.604280259034475 0.22042079466490105, 61.80305544201819 -0.6517978141913001, 142.04475621088432 0.6515453904867172 M-0.9637814238667488 -0.6931114718317986 C34.712639796779484 -0.3664882219217871, 67.35481266537494 0.17317866917623537, 142.61823804630922 -0.8067768141627312 M141.08415058954643 -0.7424022108316422 C142.32678374701774 10.92371443514616, 143.7166186278466 23.0008265645224, 144.241448200783 38.08895713668693 M143.4214473511188 0.9095756486058235 C143.07612582708353 14.524902024606648, 142.96508976484293 28.620887885470754, 142.61397393002198 36.27843323182691 M140.9726150403826 36.24347144943107 C101.76526391678019 37.67087043235949, 65.58319660665431 39.26389230201892, 1.8075202852487564 35.73005658965934 M142.8144610668628 35.993015675064214 C107.07914770222747 36.24827819009365, 71.14698832929136 35.37344007630886, -0.8270853236317635 36.682710318084844 M0.10155333578586578 34.99943906646598 C-0.3544819420226377 23.263524874451793, 0.09794449154059473 9.050910853336042, -0.45962439477443695 1.4708398431539536 M0.24138277024030685 36.7060696167909 C0.18824374114524345 28.4890349346916, 0.08326541816245536 18.01681989370558, 0.1552075669169426 0.02446264773607254" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1216.8773246507226 2183.0653641849112) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1247.3773246507226 2182.5653641849112) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1336.3773246507226 2175.5653641849112) rotate(0 17.75 17.75)"><path d="M0.6433686167001724 0.04649673402309418 L35.94384802877903 0.9881971925497055 L36.76668156683445 35.485912665724754 L-0.00971280038356781 34.65341268479824" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0.26330743730068207 -1.8866940587759018 C9.146876802667975 -1.0634566342458127, 20.163121338933706 -1.5647502934560178, 34.20532165467739 0.581592932343483 M0.04875726252794266 -0.43818051367998123 C7.817000098899007 -0.46741115961223845, 13.650375076383353 -0.21332728777080778, 35.575800843536854 0.9825183674693108 M35.06613479554653 0.8927158266305923 C35.65389089290053 9.057142027840019, 36.538813397325576 16.762965323776008, 36.33467184007168 33.94439209997654 M36.10715574771166 0.015097789466381073 C36.60829468693584 12.688683455064893, 35.18296524014324 25.481585221737625, 34.94225139170885 36.41692917793989 M34.94316564500332 36.54884995520115 C29.122550920024516 36.970293610058725, 19.484416053444146 34.214208691082895, -0.16343463957309723 34.34651316702366 M35.653476901352406 35.98350518196821 C24.062812204286455 34.79696834180504, 13.832689804583786 36.223613859154284, -0.6298890635371208 36.12501399964094 M-0.3658539205789566 35.36539973318577 C-0.9488696200028062 24.21011843420565, 1.8114496129378677 12.749554542452096, -0.06487669050693512 0.3372725397348404 M0.6342539265751839 35.57128971070051 C-0.6990090813860297 24.148298732563852, 0.09982834193855522 13.322651199251414, -0.8112748190760612 0.9532083794474602" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1337.3773246507226 2176.5653641849112) rotate(0 17.5 4.5)"><path d="M0.04649673402309418 0.44384802877902985 L35.988197192549706 1.2666815668344498 L34.985912665724754 8.990287199616432 L-0.8465873152017593 7.383707895874977" stroke="none" stroke-width="0" fill="#000"></path><path d="M-1.8866940587759018 -0.6089111715555191 C13.190968293696642 0.6054665889590979, 26.631805084645748 -1.3407571468502284, 35.58159293234348 -1.9275628477334976 M-0.43818051367998123 0.5884241536259651 C7.896429527550936 -0.8238418560475111, 16.22462324798107 0.05873580165207387, 35.98251836746931 -0.85588388890028 M35.40172212198377 0.12855027839541433 C35.62811806760728 2.892684410512447, 35.670513063296674 5.107440338283777, 34.29997644498944 9.56297608539462 M35.00679400525987 0.3534278992563486 C34.929477049857375 3.2306477319449187, 34.89450029328465 6.786249293759465, 35.41261813007295 8.589756751433015 M36.04884995520115 10.036187514662743 C27.488588105887175 7.714243230596185, 15.413571007549763 8.739119348302484, -1.1534868329763412 9.037085399031639 M35.48350518196821 8.278638787567616 C26.911645527929068 8.818059886619448, 21.077021896839142 8.42382785193622, 0.6250139996409416 9.050776667892933 M-0.06057012006640439 8.419734274595976 C-0.18041477613151077 7.825395655632019, -0.4640528004616499 5.297840941697359, 0.15177264288067815 0.21724449321627615 M0.03208036981523038 8.65153135098517 C0.05397928208112715 6.502076148614288, 0.36054686754941934 3.6170622456818817, 0.4289437707513571 0.14475793875753878" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1344.8773246507226 2168.0653641849112) rotate(0 3 7)"><path d="M0.44384802877902985 0.9881971925497055 L7.26668156683445 -0.014087334275245667 L5.990287199616432 13.15341268479824 L-1.6162921041250229 13.963841781020164" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.1826733514666557 -0.2253486469388008 C2.5232852697372437 -0.3191359229385853, 4.267239801585675 0.2310787014663219, 5.421731145679951 -0.41586688309907915 M0.17652724608778952 -0.05330409631133079 C2.047652880102396 0.049211896061897256, 4.602112101763487 0.3210463410615921, 5.743234833329916 -0.11136033162474632 M6.1999670997262 1.0118708834052086 C6.789042288511991 4.929835316538812, 6.553899723738432 6.615107794106008, 6.875740577280521 15.273405908048153 M6.549776732176542 -0.1273611061275005 C5.637047924101353 4.19481799826026, 5.694577778875828 9.395025209337474, 5.361843835562468 13.763786435872316 M6.310856254398823 14.542256085574627 C4.41346735060215 13.746896108537912, 3.490890763700008 13.685419448763133, 0.011125619709491774 13.444788996875285 M5.783591636270285 13.75187440291047 C3.919634724408388 14.208878369033336, 1.7916969470679756 14.216514510810375, 0.01523300036787989 13.712160757929087 M-0.9026355728507041 13.678262923657893 C1.0551976646482943 9.447966909408569, -0.019803908914327664 6.657928158342838, 0.3379358783364297 -0.297216822206974 M-0.5420623429119587 14.10864529684186 C-0.07167169630527495 10.138752960413694, -0.6566879230737686 5.77369537129998, 0.22517901584506028 0.016273856908082918" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1359.8773246507226 2168.0653641849112) rotate(0 3 7)"><path d="M0.9881971925497055 1.2666815668344498 L5.985912665724754 -0.00971280038356781 L5.153412684798241 12.383707895874977 L-0.03615821897983551 12.606611624360085" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.2253486469388008 0.19546361714601512 C1.5032554864883423 -0.3701361684501171, 3.932462342083454 -0.5600015191733837, 5.5841331169009205 0.4896859243512154 M-0.05330409631133079 -0.24203304424881933 C1.8521754227578642 -0.012565196156501757, 3.9532642252743244 -0.2920706158876419, 5.888639668375253 -0.07456785812973976 M7.011870883405209 0.8194128528237341 C7.140308762341737 4.480564644932747, 4.973564577847719 8.668329291045666, 7.273405908048153 14.176587392389774 M5.8726388938725 -0.44795388057827945 C5.8203442963957785 4.659594020992518, 6.643964115083218 9.461879929155113, 5.7637864358723165 13.335045992583037 M6.542256085574627 13.643506772816181 C4.082480916380883 13.982055233567953, 2.388054563105106 14.042211245149375, -0.5552110031247138 14.209668166935444 M5.751874402910471 13.929302891343832 C4.287781917303801 13.76775160819292, 2.4033319182693957 13.971951327621937, -0.2878392420709133 13.91194599494338 M-0.3217370763421059 15.029587890207768 C-0.7535297621786594 10.010684061050414, 0.2455127964913846 6.450131367146968, -0.297216822206974 0.24677524715662003 M0.10864529684185986 14.017123853415251 C0.5755476480722427 8.3441521294415, 0.6572653061151504 4.049268021434545, 0.016273856908082918 0.1553468100726605" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1353.3773246507226 2184.5653641849112) rotate(0 0.66275480017066 12.672529354691505)"><path d="M0.6515453904867172 -1.2524558156728745 C-1.2725264270231127 6.03360120985657, -1.802578280903399 9.51591570597142, 2.132286414504051 26.597514525055885 M-0.8067768141627312 0.07640355080366135 C0.3598242185637355 7.77222840782255, -0.44376953955739734 13.207330573685468, 0.25144047290086746 26.283067397773266" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1343.8773246507226 2183.5653641849112) rotate(0 -0.32747064530849457 13.575839783996344)"><path d="M-1.2524558156728745 -1.294678345322609 C0.6112074511498213 5.61833264861256, -1.2218312041461468 16.725706270672383, 0.5975145250558853 27.123638972640038 M0.07640355080366135 0.07580084353685379 C1.1121890852600336 9.195737625323238, -0.06210917994379994 19.85064961727709, 0.28306739777326584 28.446357913315296" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1363.3773246507226 2185.0653641849112) rotate(0 0.3258397839963436 13.629220619797707)"><path d="M-1.294678345322609 0.581592932343483 C-1.4508279721811415 5.315438725464046, 2.5026841283217074 13.491444444693625, 0.6236389726400375 26.67684830725193 M0.07580084353685379 0.9825183674693108 C-0.6382640246674418 6.8245680226758125, 0.39240828897804025 14.175751707218588, 1.9463579133152962 25.64283364266157" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1372.3773246507226 2193.0653641849112) rotate(0 -16.43732399493456 -0.6023989655077457)"><path d="M0.581592932343483 -1.9275628477334976 C-12.693850107491016 1.4121910957992077, -22.517739802598953 -1.096456346064806, -32.82315169274807 -0.35536064207553864 M0.9825183674693108 -0.85588388890028 C-7.8281599372625355 -0.22278724029660224, -15.130850265920163 0.47213020011782647, -33.85716635733843 0.7227649167180061" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1371.8773246507226 2202.0653641849112) rotate(0 -18.60562226548791 0.0995359756052494)"><path d="M-1.9275628477334976 -1.386222943663597 C-6.919766509011389 0.43957846209406853, -17.973993482664227 1.7562894535064697, -36.35536064207554 -0.6135536283254623 M-0.85588388890028 -0.3712011054158211 C-11.322854301556944 0.4444143651425838, -21.702989644035696 1.1118209651112556, -35.277235083281994 1.585294894874096" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(919.1800970778143 2588.468754522708) rotate(0 412 242.19444444444457)"><path d="M0 0 C280.227407926321 0, 560.454815852642 0, 824 0 M0 0 C238.9694912016392 0, 477.9389824032784 0, 824 0 M824 0 C824 136.2877507086844, 824 272.5755014173688, 824 484.38888888888914 M824 0 C824 153.1982566141835, 824 306.396513228367, 824 484.38888888888914 M824 484.38888888888914 C619.1304369986058 484.38888888888914, 414.26087399721143 484.38888888888914, 0 484.38888888888914 M824 484.38888888888914 C639.4241226136685 484.38888888888914, 454.84824522733686 484.38888888888914, 0 484.38888888888914 M0 484.38888888888914 C0 332.7260584450846, 0 181.06322800128004, 0 0 M0 484.38888888888914 C0 361.3100231517936, 0 238.23115741469806, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1012.180097077814 2658.468754522708) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1049.5967637444805 2842.6195481735012) rotate(0 227.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current stock will change from 15 to 17 after update</text></g><g transform="translate(1009.2526079003255 2961.9681914365974) rotate(0 288.5 28.5)"><path d="M0 0 C210.21055428311226 0, 420.4211085662245 0, 576.9999999999999 0 M0 0 C145.5924633927643 0, 291.1849267855286 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 13.76067228242755, 576.9999999999999 27.5213445648551, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 14.453476584702731, 576.9999999999999 28.906953169405462, 576.9999999999999 57 M576.9999999999999 57 C439.1269875355064 57, 301.2539750710129 57, 0 57 M576.9999999999999 57 C399.0728687815367 57, 221.14573756307357 57, 0 57 M0 57 C0 35.215562392026186, 0 13.431124784052372, 0 0 M0 57 C0 44.99653309509158, 0 32.99306619018316, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1504.252607900325 2961.9681914365974) rotate(0 41 29)"><path d="M0 0 C25.931014920771123 0, 51.862029841542245 0, 82 0 M0 0 C21.203527615964415 0, 42.40705523192883 0, 82 0 M82 0 C82 20.769845850765705, 82 41.53969170153141, 82 58 M82 0 C82 15.434329311549666, 82 30.868658623099332, 82 58 M82 58 C60.65659751743078 58, 39.313195034861565 58, 0 58 M82 58 C54.86310951858759 58, 27.726219037175177 58, 0 58 M0 58 C0 37.05103646963835, 0 16.102072939276695, 0 0 M0 58 C0 43.39726181775331, 0 28.794523635506625, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1543.252607900325 2973.9681914365974) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1543.252607900325 2973.9681914365974) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-4.479791978043323 21.69187269647201, -3.1452415195502788 25.358519946304465, 0 34 M-5.814342436536368 18.025225446639556 C-4.092619901678256 22.755619233728396, -2.370897366820143 27.486013020817232, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1543.252607900325 2973.9681914365974) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C4.479791978043323 21.69187269647201, 3.1452415195502788 25.358519946304465, 0 34 M5.814342436536368 18.025225446639556 C4.092619901678256 22.755619233728396, 2.370897366820143 27.486013020817232, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1040.238523322765 2977.9681914365974) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(924.8979747308113 2517.6019687952994) rotate(0 275 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: updating stock</text></g><g transform="translate(1060.2653209606888 2913.4520033282333) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(1414.979606674974 2904.6662890425177) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.0694736183042561 4.8667236839697, 2.4004040606699606 3.335663350863511, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.441522668299273 4.438730210918843, 3.1445021606599943 2.4796764047617965, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.449943352066549 9.375687261948018, 4.766741323265487 6.71051606996703, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.270109001809981 8.432194609795282, 6.407072622752351 4.823530765661556, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.9077817690326295 12.645279943839396, 9.943874981259098 6.851917416673896, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.511206114358018 11.951119640829049, 11.150723671909875 5.4635968106532005, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.673260726100951 15.56097266761675, 15.08023069046668 7.040228427375489, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C8.553656316296054 14.54819339479961, 16.841021870856885 5.014669881741206, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C9.240123310904831 19.855515798424975, 18.47541268413589 9.231530671916044, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C8.74180051347356 20.428770601187903, 17.478767089273347 10.378040277441904, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C7.938773350425098 27.449560026522423, 16.134169587237874 18.021835111035053, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.911517967119886 27.48091375840344, 16.07965882062745 18.084542574797084, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C14.102280369671952 26.456268620719165, 28.06658142080252 10.392177862575423, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C11.953988300397254 28.927595946696226, 23.769997282253126 15.334832514529545, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C13.63346182153386 33.0925950140397, 26.07828309060677 18.776465792586155, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C13.834373281759817 32.861472817547124, 26.480106011058684 18.314221399600996, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C19.934335677402537 31.941281139186813, 33.03695636549101 16.86844024780943, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C19.423769074466428 32.52862082898667, 32.01582315961879 18.043119627409144, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C26.401377920165906 30.59881240177138, 40.98402544315514 13.823395397684855, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C23.885941807439863 33.49249063623425, 35.95315321770305 19.61075186661059, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C33.05256944474344 29.04450414834451, 48.64333405545709 11.109381095760178, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C33.514412316278964 28.51321469982981, 49.567019798528136 10.046802198730774, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C37.292288074414145 30.264278128088236, 52.135755906935884 13.18882167995391, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C33.45126223718552 34.68287290255567, 44.453704232478636 22.02601122888878, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C38.520159473394585 34.94878600939413, 48.94842426804365 22.952439647494757, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C43.84148948937288 28.827296074615823, 59.59108430000025 10.70945977793815, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C47.34793195115798 30.89060779166325, 61.61695381570783 14.47597583673928, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C48.8976173679153 29.107898647094505, 64.71632464922247 10.91055754760179, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C49.905834415763785 34.045089954451235, 61.08968430806631 21.17954236724431, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C46.02654066942349 38.507706922571145, 53.33109681538572 30.10477630348413, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C51.65369853686655 38.13141463624275, 59.59839714240923 28.99208435553363, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C54.28661543049228 35.10259022297723, 64.8642309296607 22.93443552900258, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C55.18257527926562 40.168918665633115, 61.013076190354276 33.46169461924342, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C56.40510873276693 38.76255480395442, 63.45814309735688 30.64896689588603, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C60.20503176150179 40.48825574864951, 66.070973746964 33.7402614099825, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C60.938011574559695 39.64505892857694, 67.5369333730798 32.05386776983735, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C65.06426690316256 40.99535750524812, 70.14636959343241 35.149067128108776, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C62.61570580044832 43.81210484096082, 65.24924738800394 40.78256179953418, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C68.15218823756568 43.54012270499029, 71.33519685437605 39.87849015229939, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.22962937584751 44.60140527331265, 69.49007913093969 42.00105528894413, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C71.22166828660232 46.106102176806594, 72.48714154458672 44.6503417206383, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.19178496651935 46.140479004132914, 72.42737490442077 44.71909537529095, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C15.607046788930894 0, 31.21409357786179 0, 72 0 M0 0 C27.95016422867775 0, 55.9003284573555 0, 72 0 M72 0 C72 15.406367994844913, 72 30.812735989689827, 72 46 M72 0 C72 16.16315798610449, 72 32.32631597220898, 72 46 M72 46 C57.02690897583962 46, 42.05381795167923 46, 0 46 M72 46 C54.47378405928612 46, 36.947568118572235 46, 0 46 M0 46 C0 36.32309472411871, 0 26.64618944823742, 0 0 M0 46 C0 33.51145796924829, 0 21.022915938496585, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1045.6130286524176 2712.5638470792755) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(1232.1023444624875 2713.6927169821456) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">3</text></g><g transform="translate(1045.6130286524176 2763.5638470792755) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(1232.1023444624875 2770.6927169821456) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">1</text></g><g transform="translate(1198.1076865574526 2702.6282820307106) rotate(0 52.5 20.5)"><path d="M0 0 C26.49429576471448 0, 52.98859152942896 0, 105 0 M0 0 C25.34860683605075 0, 50.6972136721015 0, 105 0 M105 0 C105 10.39636035040021, 105 20.79272070080042, 105 41 M105 0 C105 9.796869169920683, 105 19.593738339841366, 105 41 M105 41 C72.62157924100757 41, 40.24315848201513 41, 0 41 M105 41 C64.87077282741666 41, 24.741545654833317 41, 0 41 M0 41 C0 32.36592731401324, 0 23.731854628026486, 0 0 M0 41 C0 29.648827327042817, 0 18.297654654085633, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1199.1076865574526 2759.6282820307106) rotate(0 51 22.5)"><path d="M0 0 C26.37511971741915 0, 52.7502394348383 0, 102 0 M0 0 C36.526280634105206 0, 73.05256126821041 0, 102 0 M102 0 C102 11.974910672754051, 102 23.949821345508102, 102 45 M102 0 C102 11.71284282580018, 102 23.42568565160036, 102 45 M102 45 C68.24435574263333 45, 34.48871148526668 45, 0 45 M102 45 C65.15871930867434 45, 28.31743861734867 45, 0 45 M0 45 C0 33.67028934136033, 0 22.340578682720658, 0 0 M0 45 C0 35.718918677419424, 0 26.437837354838848, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1431.979606674974 2915.1662890425177) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1515.162496303048 2904.5895592402812) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.3753640436643175 4.514837002564089, 3.0121849113900834 2.6318899880522895, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.8493351942651193 5.119963972199745, 1.9601272125916869 3.842143927323601, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.034463156220709 8.70327414585406, 5.935780931573807 5.365689837779114, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.961258922947706 8.78748598308616, 5.789372465027801 5.534113512243313, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.4253787671312494 12.049852709554312, 10.979068977456338 5.661062948103725, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.178774409341544 12.333538571838613, 10.485860261876928 6.22843467267233, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.788121109505969 15.428840911306326, 15.309951457276716 6.775964914754638, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.330556112387134 18.255946042684027, 10.394821463039046 12.430175177510039, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C6.3299650457229895 23.203269926703264, 12.655096153772206 15.92703892847263, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C8.944233705451841 20.19589785256318, 17.88363347322991 9.912294780192461, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C11.134114539684509 23.77374047210639, 22.524851965756696 10.670196002202985, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.814464994959845 27.59256043140325, 15.885552876307369 18.30783592079671, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.337943974365544 28.48590546958986, 24.537908630189705 14.451451560316809, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C13.741365245342406 26.87145397743612, 27.34475117214343 11.22254857600933, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C16.215279177930732 30.122553894025874, 31.24191780340052 12.836383552558495, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C9.581212113384062 37.75417505646579, 17.973783674307175 28.099625877438324, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C17.757773431649383 34.445129583051255, 28.6838318739847 21.876137135538308, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C18.475860205318625 33.6190652449789, 30.120005421323178 20.2240084593936, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C27.21453877819911 29.663377840701255, 42.61034715922155 11.952526275544606, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C24.78789339673942 32.45491402306125, 37.75705639630217 17.535598640264602, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C32.95671218411056 29.154775312579325, 48.45161953419132 11.329923424229804, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C28.098486788354222 34.7435243230162, 38.73516874267865 22.50742144510356, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C32.99473548210508 35.20804685865133, 43.54065072231776 23.076359141080104, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C35.35900949885364 32.488260723770274, 48.26919875581487 17.636786871317987, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C39.758227506288875 33.52455165836228, 51.424560333832225 20.103970945431055, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C43.60212757494806 29.102650458862083, 59.11236047115059 11.26016854643067, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C48.00875136482454 30.130422015302933, 62.93859264304095 12.955604284018648, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C42.69862404602975 36.23902472116566, 52.318338005451366 25.17280969574411, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C48.85826304400882 35.25018296482732, 58.9945415645564 23.589728387996466, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C47.190995580249954 37.16815478152302, 55.66000663703865 27.425672021387886, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C54.787307700832244 34.52660965343837, 65.86561547034063 21.782474389924868, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.14701488272454 38.71428750442301, 58.585029834125216 30.15783009189416, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C58.51149848727816 36.33943057707068, 67.67092260637935 25.80271844211855, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C57.82154168796805 37.133135081344356, 66.29100900775913 27.390127450665904, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C61.9156957815482 38.52036190461843, 69.49230178705682 29.80447372192035, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C58.40411740517944 42.559970728273505, 62.4691450343193 37.88369136923049, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C63.34098199809981 42.97777001667317, 66.69979978330691 39.11389215095886, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.5650515727087 41.569639049802646, 69.14793893252468 36.29763021721783, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.23811460726664 44.59164413116013, 69.50704959377795 41.98153300463908, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.56727982892488 44.21298285940858, 70.16538003709445 41.22421046113598, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C70.82153976332255 46.566397388815645, 71.68688449802717 45.570932144656396, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.10737221247501 46.23758476955204, 72.2585493963321 44.9133069061292, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C17.131241887807846 0, 34.26248377561569 0, 72 0 M0 0 C16.469169813394547 0, 32.938339626789094 0, 72 0 M72 0 C72 17.891359259188174, 72 35.78271851837635, 72 46 M72 0 C72 16.85603842586279, 72 33.71207685172558, 72 46 M72 46 C50.652469736337665 46, 29.304939472675322 46, 0 46 M72 46 C44.20580386519432 46, 16.41160773038864 46, 0 46 M0 46 C0 34.4678519949317, 0 22.935703989863395, 0 0 M0 46 C0 30.86363368183374, 0 15.727267363667483, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1523.662496303048 2915.0895592402812) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(1196.0885167112117 2909.119093132542) rotate(0 71.39795918367349 18.459183673469397)"><path d="M-1.1418243795633316 -1.0046259015798569 L144.4990232060759 -1.426329717040062 L144.43414712651656 35.85880122166503 L1.6835669130086899 36.37682518940795" stroke="none" stroke-width="0" fill="#fff"></path><path d="M-0.3727961629629135 0.7564176172018051 C36.729751035495084 -0.37391743593526106, 72.55191798492964 0.4629704005210378, 141.37068775995658 -1.3066493421792984 M0.16271203011274338 0.27240557223558426 C33.05778231309082 -1.5164845041489725, 67.19790111901811 -0.5159411482072, 143.68534429325746 -0.5211303755640984 M141.7684805761187 0.5503796190023422 C142.3601679568647 11.758133697312084, 142.19128701738106 24.685477009020264, 144.12463072641776 35.19265205245841 M142.03591687931703 -0.03182869404554367 C142.0532299560963 14.845505480909212, 142.76549711651194 29.45545226572725, 142.76085312618898 36.28411140870679 M143.05794600351737 37.254392927702554 C102.65255512438566 38.71467735192449, 63.151354308730504 38.18086816689642, 1.720610037446022 36.485484903868326 M142.3368285442798 36.1935180706941 C98.39024040980306 35.308574170508216, 51.90110314202552 35.750081271567176, -0.49301130324602127 36.76974835824598 M-1.9394584745168686 37.318343943174966 C-0.6244689786692664 25.33277080455906, -1.769820865418534 14.929091586841594, 0.5810288339853287 -1.157154694199562 M0.27907007187604904 37.90980687570203 C-0.48869153535335613 27.076435881016785, 0.3116646143201058 17.86483765244482, -0.028601713478565216 -0.6333113238215446" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1230.736475894885 2915.5782768060126) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1261.236475894885 2915.0782768060126) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(1350.236475894885 2908.0782768060126) rotate(0 17.75 17.75)"><path d="M-1.426329717040062 1.6382287591695786 L34.440433874726295 1.6835669130086899 L34.958457842469215 34.71851383149624 L0.8811601549386978 33.981839045882225" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-1.1716939359903336 1.1620158106088638 C9.653480711206795 -1.6089075982198118, 18.66992427930236 -1.4540733277425169, 36.198420867323875 1.4736865907907486 M-0.46430235356092453 -0.33890873938798904 C11.763868709281088 1.1416969546303153, 21.93412503823638 0.767912174127996, 36.01372999697924 0.26068418473005295 M34.278960570693016 -0.794292077422142 C34.32482313815504 10.155010279640555, 33.87197708789259 19.649615981429815, 33.65919195115566 33.854674234986305 M35.58365911990404 0.30937688797712326 C35.42046035733074 7.916929476335645, 34.85444487538189 15.735595040768386, 34.934196658432484 35.606899194419384 M37.1436623185873 36.723780527710915 C25.494754079356788 37.27222864385694, 12.836919448524714 35.88320915456861, -1.7926498800516129 34.497640028595924 M34.60587138682604 35.017713479697704 C26.027147359773515 35.291196866966786, 17.310918755084277 35.79700498197228, -0.285099558532238 35.459210090339184 M-0.46284259855747223 33.624925032258034 C0.18523628171533346 28.873567626252772, -1.2281505400314927 20.07083387747407, 1.4420164674520493 -0.423099622130394 M-0.6386323496699333 36.077849082648754 C-0.9988924947008491 23.83069953136146, -0.9852723565325141 11.647690299898386, -0.5023129507899284 0.8515524193644524" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1351.236475894885 2909.0782768060126) rotate(0 17.5 4.5)"><path d="M1.6382287591695786 -1.0595661252737045 L36.68356691300869 -0.5415421575307846 L34.21851383149624 9.881160154938698 L-1.518160954117775 9.852583780884743" stroke="none" stroke-width="0" fill="#000"></path><path d="M1.1620158106088638 -0.33480609953403473 C10.398500222712755 -1.10160290338099, 22.37706532329321 0.9034673061221838, 36.47368659079075 0.32542406022548676 M-0.33890873938798904 0.5362410023808479 C12.342845428735018 -0.35862455181777475, 23.422480151057243 0.6762358207255602, 35.26068418473005 -0.5137188956141472 M34.64256856516003 -0.5727911598980426 C35.56977706037462 2.1710989460349084, 35.87051556192338 5.0669780351221565, 34.25960340574384 8.315998660773039 M35.13921959958971 -0.030708205327391602 C34.947960516959434 2.3859390031546357, 34.88796239808202 5.08809684701264, 35.04810463748872 9.058956218138338 M36.223780527710915 10.109853133559227 C25.632565271109343 8.854758462682366, 13.15550101548433 7.4949910257011645, -1.0023599714040756 8.081820353865623 M34.517713479697704 8.547378487884998 C26.54396116361022 8.538435996696354, 19.086741745471954 8.401955426856876, -0.04078990966081619 8.030270762741566 M-0.8437837354838849 9.115304457396268 C0.29430051393806944 5.599290919303893, -0.42357212953269485 3.888928473740816, -0.1903948299586773 0.25116306468844407 M0.2600320871919393 8.708880842104554 C0.007154864966869359 5.12271432839334, -0.23688484936952597 1.5893680203706027, 0.38319858871400353 -0.320924186334014" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1358.736475894885 2900.5782768060126) rotate(0 3 7)"><path d="M-1.0595661252737045 1.6835669130086899 L5.458457842469215 -0.7814861685037613 L6.881160154938698 12.481839045882225 L0.8525837808847427 13.071687087416649" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.10044182986021044 -0.42756918221712115 C1.7184451818466187 -0.41597937539219854, 4.430406229197979 0.40724550291895867, 6.097627218067646 0.16344334334135058 M0.16087230071425435 0.26682777777314187 C1.2172873176634313 0.1750268679857254, 2.9013718597590925 0.09853977978229525, 5.845884331315756 0.08255694285035131 M5.108991529047489 0.9300986513495446 C4.706526533812285 5.160126301646233, 4.6259616057574755 6.372696371376515, 4.93599791675806 13.955439828336239 M5.952231680601836 -0.024545668810606047 C5.5677331200242035 3.5180775426328186, 5.615649937689304 7.3467366732656965, 6.091709672659635 14.117608953267336 M6.332955940067768 14.516183011233807 C3.982892552018165 14.136190580278635, 1.6877196058630939 13.736005424410104, -0.27545389384031294 13.565090434253216 M5.8642135463655 13.852096609026194 C3.6750624664127827 13.974901852309705, 1.3537664584815499 13.958825835883617, -0.2909187711775303 14.059996489435434 M0.17936248928308496 14.40672018378973 C-0.7776093040406704 9.600835561752318, 1.0418105091154577 6.325442577898501, 0.3906981006264687 1.3880153402686117 M-0.4528520233929157 13.979978800565004 C-0.3539093977212906 9.711871360987423, -0.26221053659915916 6.819106481224297, -0.4992154009640216 0.5733800657093524" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1373.7364758948847 2900.5782768060126) rotate(0 3 7)"><path d="M1.6835669130086899 -0.5415421575307846 L5.218513831496239 0.8811601549386978 L4.481839045882225 14.852583780884743 L-0.9283129125833511 15.40739668905735" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.42756918221712115 -0.3919948026537895 C1.0673729181289673 0.21957044318318364, 3.3390896156430245 0.10767140105366711, 6.163443343341351 -0.4263735696673393 M0.26682777777314187 -0.1563391126692295 C1.6761109314858915 0.1219445389509201, 3.1210424311459066 -0.186290128827095, 6.082556942850351 -0.09381167069077492 M6.930098651349544 -1.2080007061362266 C7.342313652783632 2.0567067652940754, 4.792671519070863 5.626542715728283, 5.955439828336239 13.435953618586064 M5.975454331189394 -0.4439791567623615 C5.925436688363552 3.921060428768396, 6.118142200410366 8.725955208390952, 6.117608953267336 14.10178316757083 M6.516183011233807 13.870135267078876 C3.8063784688711166 13.495500629991293, 1.1130946174263954 13.757841700166464, -0.4349095657467842 14.48035183697939 M5.852096609026193 13.955414303392171 C4.740316211432219 13.88731284171343, 3.460717362910509 13.68192393809557, 0.05999648943543434 13.890541537851096 M0.40672018378973007 13.189991714060307 C-0.578715347200632 11.133401346206664, -0.16491448774933815 6.638677738606929, 1.3880153402686117 -0.3115098938345908 M-0.020021199434995696 13.556682073324918 C-0.48053740441799164 10.342466223984957, 0.5151878219842909 6.888955178111791, 0.5733800657093524 -0.37084814384579656" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(1367.236475894885 2917.0782768060126) rotate(0 -0.5165169888082914 12.3095419742167)"><path d="M-1.3066493421792984 -1.2703963369131088 C0.8331094544008374 10.96692061636597, 0.6391630425676703 19.527716119103133, -0.9212452322244644 25.57139529287815 M-0.5211303755640984 0.5156411454081535 C0.7888884256407619 7.308128895722331, -0.10550590392202136 14.37452636111528, 0.18729443103075027 25.889480285346508" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1357.736475894885 2916.0782768060126) rotate(0 -0.308343032559776 14.058291979134083)"><path d="M-1.2703963369131088 0.6984208673238754 C1.415784153267741 8.259962144233288, 0.6285385544598103 14.44456431243569, -0.42860470712184906 27.322182521224022 M0.5156411454081535 0.5137299969792366 C0.6865354941040278 9.390361562930048, 0.8778531689941884 19.492849696241322, -0.11051971465349197 27.60285396128893" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1377.2364758948847 2917.5782768060126) rotate(0 0.5400990881577172 13.416583094745874)"><path d="M0.6984208673238754 1.4736865907907486 C0.5019339735433459 6.387170092575253, -0.9016276144608855 10.74658521655947, 0.8221825212240219 26.572482004761696 M0.5137299969792366 0.26068418473005295 C-0.06319325249642127 10.185491303391755, 0.721233963035047 19.217817890308798, 1.102853961288929 24.863565377891064" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1386.2364758948847 2925.5782768060126) rotate(0 -16.581374015659094 0.6196436536490637)"><path d="M1.4736865907907486 0.32542406022548676 C-7.433884973824025 -1.3025136847794057, -16.885996943712236 -0.5590859313309193, -32.927517995238304 1.7788518518209457 M0.26068418473005295 -0.5137188956141472 C-8.152966913580896 -0.13803736999630928, -17.466833160817625 -0.22247783973813057, -34.636434622108936 0.664356179535389" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(1385.7364758948847 2934.5782768060126) rotate(0 -17.505109880119562 0.33789916107161844)"><path d="M0.32542406022548676 0.5448111444711685 C-9.93103972144425 -0.42686093762516975, -17.70089856155217 1.8107059574127198, -34.221148148179054 -0.042260751128196716 M-0.5137188956141472 0.2751898095011711 C-12.96387639246881 -0.16510929360985754, -25.704624985679985 0.16053991347551344, -35.33564382046461 0.1371423527598381" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1830.430097077814 2584.8713272638206) rotate(0 412 257.81944444444457)"><path d="M0 0 C289.3018495619297 0, 578.6036991238594 0, 824 0 M0 0 C266.4378617227078 0, 532.8757234454156 0, 824 0 M824 0 C824 168.13916297101736, 824 336.2783259420347, 824 515.6388888888891 M824 0 C824 103.9498607670889, 824 207.8997215341778, 824 515.6388888888891 M824 515.6388888888891 C509.1749795854091 515.6388888888891, 194.34995917081824 515.6388888888891, 0 515.6388888888891 M824 515.6388888888891 C495.227893358469 515.6388888888891, 166.45578671693795 515.6388888888891, 0 515.6388888888891 M0 515.6388888888891 C0 387.63720168262324, 0 259.63551447635734, 0 0 M0 515.6388888888891 C0 312.98472778310804, 0 110.330566677327, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1923.430097077814 2654.8713272638206) rotate(0 25 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Stock</text></g><g transform="translate(1957.8467637444805 2719.272120914614) rotate(0 31 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">current</text></g><g transform="translate(1909.2526079003255 3004.620764177708) rotate(0 288.5 28.5)"><path d="M0 0 C192.039151353389 0, 384.078302706778 0, 576.9999999999999 0 M0 0 C136.73794285580513 0, 273.47588571161026 0, 576.9999999999999 0 M576.9999999999999 0 C576.9999999999999 22.734716738015415, 576.9999999999999 45.46943347603083, 576.9999999999999 57 M576.9999999999999 0 C576.9999999999999 19.196387576311828, 576.9999999999999 38.392775152623656, 576.9999999999999 57 M576.9999999999999 57 C404.0372541241347 57, 231.07450824826947 57, 0 57 M576.9999999999999 57 C368.86377132609476 57, 160.72754265218964 57, 0 57 M0 57 C0 41.1177114598453, 0 25.235422919690603, 0 0 M0 57 C0 44.01384368017316, 0 31.027687360346317, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2404.252607900325 3004.620764177708) rotate(0 41 29)"><path d="M0 0 C26.443931220471864 0, 52.88786244094373 0, 82 0 M0 0 C24.761952497065067 0, 49.523904994130135 0, 82 0 M82 0 C82 18.225900404155254, 82 36.45180080831051, 82 58 M82 0 C82 14.270743449032308, 82 28.541486898064615, 82 58 M82 58 C64.16376731544733 58, 46.32753463089466 58, 0 58 M82 58 C50.749854277074334 58, 19.49970855414867 58, 0 58 M0 58 C0 42.38057781010866, 0 26.761155620217323, 0 0 M0 58 C0 43.396501176059246, 0 28.793002352118492, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2443.252607900325 3016.620764177708) rotate(0 0 17)"><path d="M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34 M0 0 C0 5.666666666666667, 0 28.333333333333332, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2443.252607900325 3016.620764177708) rotate(0 0 17)"><path d="M-5.814342436536368 18.025225446639556 C-3.672683048188459 23.909386256288435, -1.5310236598405504 29.79354706593731, 0 34 M-5.814342436536368 18.025225446639556 C-4.438116949352489 21.8063738967552, -3.0618914621686106 25.587522346870845, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2443.252607900325 3016.620764177708) rotate(0 0 17)"><path d="M5.814342436536368 18.025225446639556 C3.672683048188459 23.909386256288435, 1.5310236598405504 29.79354706593731, 0 34 M5.814342436536368 18.025225446639556 C4.438116949352489 21.8063738967552, 3.0618914621686106 25.587522346870845, 0 34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1940.238523322765 3020.620764177708) rotate(0 35.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">address</text></g><g transform="translate(2138.8360795545505 2714.400990817485) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">15</text></g><g transform="translate(1960.2653209606888 2956.104576069344) rotate(0 54 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">next restock</text></g><g transform="translate(2314.979606674974 2947.3188617836304) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.6911573955638532 4.151558307328436, 3.643771615189155 1.9053325975809834, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.7997554922365635 5.176998895052827, 1.8609678085345753 3.956213773029765, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.062912716240784 7.520178263586619, 7.992680051613957 2.999498073244231, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C4.049132298871088 7.536030820367036, 7.965119216874566 3.0312031868050653, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.8136656135422236 12.75354819572466, 9.755642670278286 7.068453920444423, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.449655413071555 14.322662437473312, 7.027622269336949 10.206682403941727, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C5.600209395927803 17.945745424395433, 10.934128030120384 11.80977394093285, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C7.778368749644854 15.440059717986403, 15.290446737554486 6.798402528114792, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C9.557007825434983 19.49098186417192, 19.109181713196193 8.50246280340994, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C8.269400297414974 20.97220488530608, 16.533966657156174 11.46490884567826, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C12.381592583292894 22.33868114203743, 25.019808052973467 7.800077342065066, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C10.375073706732453 24.646917066125155, 21.006770299852583 12.416549190240513, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C12.866313075108254 27.878086348743665, 25.594646831675124 13.235813318624427, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C9.116221042623618 32.19207374708522, 18.094462766705853 21.863788115307525, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C16.041378277651415 30.322603995694497, 30.89411600284188 13.23648375589574, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C10.575569958155524 36.61029720636832, 19.9624993638501 25.811870177243396, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C21.993421283387114 29.57257411029863, 37.15512757746016 12.131026190033062, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C19.326236998072215 32.64081864836124, 31.82075900683036 18.26751526615828, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C20.41523953522264 37.485076881063115, 29.0117486732686 27.59592435626833, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C24.005080671428516 33.3554370510295, 36.19143094568036 19.33664469620109, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C27.30168755303564 35.66013699022456, 37.141570272041484 24.340646779520274, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C29.966561935749773 32.59454969113763, 42.471319037469755 18.20947218134641, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C34.096683995671754 33.94040010226008, 45.7445477494511 20.5410656282976, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C34.61132520278209 33.34837311654626, 46.77383016367178 19.35701165686996, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C37.873037623192126 35.69321454148944, 47.65418056763873 24.44129671168539, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C42.805641640247245 30.01890271493779, 57.51938860174897 13.092673058582086, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C41.52728154943033 37.58650012328902, 49.97565301225252 27.867760499990823, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C45.01406722213595 33.57541204265762, 56.949224357663766 19.84558433872802, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C50.628924475497925 33.21326999415753, 62.53586442753459 19.5159024466569, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C49.1254441827442 34.94282622382081, 59.52890384202714 22.97501490598346, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C52.9683870680543 36.61903848462857, 62.22777420478474 25.967332052305267, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C52.332436774433255 37.35061561097314, 60.95587361754265 27.430486304994417, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C54.29645130424312 41.188287691380104, 59.24082824030926 35.500432670737396, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C55.52636925090044 39.77342894207139, 61.7006641336239 32.67071517211997, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C61.848158492637964 38.59805466809013, 69.35722720923634 29.95985924886374, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C61.44910340704825 39.05711503129342, 68.55911703805691 30.877979975270318, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C65.10228084440658 40.95162746820704, 70.22239747592046 35.061607054026624, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C63.95140054181702 42.27556380879902, 67.92063687074135 37.70947973521058, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.92874239103999 43.7971677475582, 70.88830516132467 40.39258023743522, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.98336749725237 43.734328751130384, 70.99755537374942 40.2669022445796, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C71.01105223518607 46.34838822840949, 72.0659094417542 45.134913823844094, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C71.2324011335524 46.093755448755694, 72.50860723848686 44.625648264536494, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C23.49195336699486 0, 46.98390673398972 0, 72 0 M0 0 C20.274025350809097 0, 40.548050701618195 0, 72 0 M72 0 C72 16.314749504625798, 72 32.629499009251596, 72 46 M72 0 C72 17.762838457524776, 72 35.52567691504955, 72 46 M72 46 C46.1160380423069 46, 20.232076084613794 46, 0 46 M72 46 C43.98897323012352 46, 15.97794646024704 46, 0 46 M0 46 C0 36.70909492820501, 0 27.418189856410027, 0 0 M0 46 C0 28.832874299585818, 0 11.665748599171636, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1956.8630286524176 2765.9664198203873) rotate(0 40 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">incoming</text></g><g transform="translate(2143.3523444624875 2767.0952897232573) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">3</text></g><g transform="translate(1956.8630286524176 2816.9664198203873) rotate(0 42 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">notify lost</text></g><g transform="translate(2143.3523444624875 2824.0952897232573) rotate(0 5.5 11.5)"><text x="5.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">1</text></g><g transform="translate(1956.8630286524176 2880.9664198203873) rotate(0 53.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">after update</text></g><g transform="translate(2143.8523444624875 2882.0952897232573) rotate(0 11 11.5)"><text x="11" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">17</text></g><g transform="translate(2109.3576865574523 2756.0308547718223) rotate(0 52.5 20.5)"><path d="M0 0 C31.698343444615606 0, 63.39668688923121 0, 105 0 M0 0 C31.209965955466036 0, 62.41993191093207 0, 105 0 M105 0 C105 11.741622019559145, 105 23.48324403911829, 105 41 M105 0 C105 9.947629534453155, 105 19.89525906890631, 105 41 M105 41 C79.51778180524707 41, 54.03556361049414 41, 0 41 M105 41 C71.71336268261075 41, 38.4267253652215 41, 0 41 M0 41 C0 31.30118932649493, 0 21.602378652989863, 0 0 M0 41 C0 28.524867499619724, 0 16.049734999239444, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2110.3576865574523 2813.0308547718223) rotate(0 51 22.5)"><path d="M0 0 C28.886803181469443 0, 57.773606362938885 0, 102 0 M0 0 C37.173303835093975 0, 74.34660767018795 0, 102 0 M102 0 C102 13.430311616510155, 102 26.86062323302031, 102 45 M102 0 C102 16.05211198702455, 102 32.1042239740491, 102 45 M102 45 C62.9927919074893 45, 23.985583814978597 45, 0 45 M102 45 C75.50919366627932 45, 49.01838733255863 45, 0 45 M0 45 C0 35.4621723331511, 0 25.924344666302204, 0 0 M0 45 C0 31.916056986898184, 0 18.832113973796368, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2331.979606674974 2957.8188617836304) rotate(0 21.5 11.5)"><text x="21.5" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(2415.162496303048 2947.242131981392) rotate(0 36 23)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.4970441935106777 4.374860002394918, 3.255545211082804 2.3519359877139463, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.0193498443811764 4.924384489941499, 2.300156512823801 3.450984962807109, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.695068028794355 7.943335170789077, 7.256990676721099 3.8458118876491483, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C3.2546144924552527 8.450019003842351, 6.376083604042894 4.8591795537556965, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C6.041150369986185 11.341488511566151, 12.210612183166209 4.244334552127405, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.344291267072877 12.143133207841995, 10.816893977339593 5.847623944679093, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C8.414446457345543 14.708336018509971, 16.56260215295586 5.33495512916193, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C6.4214844934095785 17.000976498615042, 12.576678225083935 9.92023608937207, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C6.6772876169141355 22.8037210136902, 13.349741296154498 15.127941102446501, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C6.9544811298598415 22.484846353710854, 13.90412832204591 14.490191782487804, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C6.872640764250068 28.676005271567007, 14.001904414887814 20.474725601124224, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C10.997917141478736 23.93041765614801, 22.25245716934515 10.983550370286228, 31.89026953414718 -0.10348448890603734 M0.13797931854138312 42.520359378862906 C11.256541242895041 29.72991700735603, 22.3751031672487 16.939474635849148, 36.877284942009794 0.25662288638767805 M0.13797931854138312 42.520359378862906 C11.845267458656053 29.05266496824178, 23.552555598770724 15.58497055762065, 36.877284942009794 0.25662288638767805 M1.1886405524609494 47.40872423549325 C16.24604414541426 30.08716284738364, 31.303447738367574 12.76560145927403, 42.520359378862906 -0.13797931854138312 M1.1886405524609494 47.40872423549325 C15.538740655247599 30.900822436788527, 29.88884075803425 14.392920638083801, 42.520359378862906 -0.13797931854138312 M6.831714989314069 47.014122030564195 C15.158556348394061 37.43518679913732, 23.485397707474053 27.85625156771044, 47.50737478672552 0.22212805675232872 M6.831714989314069 47.014122030564195 C20.84115003976956 30.898110545505528, 34.85058509022505 14.78209906044686, 47.50737478672552 0.22212805675232872 M11.818730397176676 47.3742294058579 C21.211286517846283 36.56932958158926, 30.60384263851589 25.76442975732062, 53.15044922357863 -0.1724741481767289 M11.818730397176676 47.3742294058579 C23.229577828526097 34.24755102121452, 34.640425259875514 21.120872636571143, 53.15044922357863 -0.1724741481767289 M17.461804834029795 46.979627200928846 C30.306104344153184 32.20395083159861, 43.150403854276576 17.42827446226838, 58.13746463144125 0.1876332271169865 M17.461804834029795 46.979627200928846 C29.255969708044375 33.41199254030672, 41.05013458205896 19.84435787968459, 58.13746463144125 0.1876332271169865 M22.448820241892403 47.33973457622256 C35.24597035382438 32.6182973849912, 48.04312046575637 17.896860193759842, 63.78053906829436 -0.20696897781207468 M22.448820241892403 47.33973457622256 C32.40388163119093 35.88774646202785, 42.35894302048945 24.435758347833136, 63.78053906829436 -0.20696897781207468 M28.091894678745525 46.9451323712935 C39.423679421275665 33.90940520605776, 50.7554641638058 20.873678040822032, 68.76755447615697 0.1531383974816336 M28.091894678745525 46.9451323712935 C36.86256421475625 36.85563122689101, 45.63323375076697 26.766130082488527, 68.76755447615697 0.1531383974816336 M33.07891008660813 47.30523974658722 C49.00458898942211 28.98484187324387, 64.93026789223609 10.66444399990052, 73.09851085502908 1.2679553529981291 M33.07891008660813 47.30523974658722 C43.409127522921416 35.42168396812881, 53.7393449592347 23.5381281896704, 73.09851085502908 1.2679553529981291 M38.72198452346125 46.91063754165816 C46.490976769857184 37.97343430545929, 54.25996901625312 29.036231069260428, 73.49311305995813 6.911029789851241 M38.72198452346125 46.91063754165816 C48.95423324235933 35.13978188061016, 59.186481961257414 23.36892621956216, 73.49311305995813 6.911029789851241 M43.70899993132386 47.27074491695187 C54.52169625510804 34.8321606691958, 65.33439257889222 22.39357642143973, 73.23165623589668 13.30881380692713 M43.70899993132386 47.27074491695187 C51.29617725229622 38.54269582692153, 58.88335457326858 29.814646736891184, 73.23165623589668 13.30881380692713 M49.35207436817697 46.87614271202281 C58.04537107987899 36.87564882028253, 66.73866779158101 26.87515492854224, 72.97019941183524 19.706597824003016 M49.35207436817697 46.87614271202281 C54.34157254094606 41.13638164618229, 59.33107071371516 35.396620580341775, 72.97019941183524 19.706597824003016 M54.339089776039586 47.23625008731652 C61.493812828987195 39.005682724789736, 68.6485358819348 30.775115362262955, 73.3648016167643 25.34967226085613 M54.339089776039586 47.23625008731652 C59.64835957910307 41.12863384045977, 64.95762938216656 35.02101759360303, 73.3648016167643 25.34967226085613 M59.9821642128927 46.84164788238747 C62.83992437265473 43.554170879182365, 65.69768453241676 40.26669387597726, 73.10334479270284 31.747456277932027 M59.9821642128927 46.84164788238747 C64.76384713880717 41.34095091106735, 69.54553006472162 35.84025393974723, 73.10334479270284 31.747456277932027 M64.96917962075531 47.20175525768118 C67.32639823571641 44.490085434116665, 69.68361685067751 41.77841561055215, 73.49794699763191 37.39053071478514 M64.96917962075531 47.20175525768118 C67.3437430886772 44.47013246324269, 69.71830655659907 41.7385096688042, 73.49794699763191 37.39053071478514 M69.95619502861793 47.561862632974886 C71.15470381114815 46.183135993775196, 72.35321259367839 44.8044093545755, 73.23649017357046 43.78831473186103 M69.95619502861793 47.561862632974886 C70.82261474171672 46.56516076763254, 71.68903445481551 45.5684589022902, 73.23649017357046 43.78831473186103" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C26.955635350942615 0, 53.91127070188523 0, 72 0 M0 0 C26.84984226822853 0, 53.69968453645706 0, 72 0 M72 0 C72 16.324836088716985, 72 32.64967217743397, 72 46 M72 0 C72 16.064654491841793, 72 32.129308983683586, 72 46 M72 46 C47.6831055700779 46, 23.36621114015579 46, 0 46 M72 46 C47.87400836348533 46, 23.74801672697067 46, 0 46 M0 46 C0 28.00655534118414, 0 10.013110682368279, 0 0 M0 46 C0 30.18186087757349, 0 14.36372175514698, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2423.662496303048 2957.742131981392) rotate(0 25 11.5)"><text x="25" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">never</text></g><g transform="translate(2096.0885167112115 2951.7716658736545) rotate(0 71.39795918367349 18.459183673469397)"><path d="M-1.39677394926548 0.32469500601291656 L144.1485536168425 1.0561283975839615 L143.16979814275192 36.469005918320306 L0.8744791597127914 36.901885843094476" stroke="none" stroke-width="0" fill="#fff"></path><path d="M1.97709359228611 0.2847932428121567 C45.81741551317428 -1.4418476016348118, 94.41446656003902 -1.710245409709763, 144.25420788629935 1.0933693498373032 M0.36778729408979416 -0.539526991546154 C29.279434256453307 -1.328064804577402, 57.55820005426604 -0.7314215951200045, 143.34479577793763 0.8614866212010384 M142.79116085871146 -1.6496993452310562 C142.0197555263934 12.957873272211563, 143.791121168941 28.65417574805262, 143.7805507551043 38.108356779631265 M143.4031320358722 0.810993380844593 C142.26080883488825 14.157132057313394, 143.62355817303828 30.160688031145476, 143.17326481594728 37.808787731643804 M142.3686511884539 36.304374998625406 C109.32375555161619 36.44563753010511, 71.09515935325503 34.99013645054578, 0.7016643434762955 34.95789129119743 M142.0741914059131 36.43621197175611 C86.5527700301488 35.87242209359239, 29.673369833583735 36.724482173166976, 0.9116184040904045 37.65035119485486 M0.7120059877634048 38.15940267425407 C-1.6189305448547313 27.43336544385063, 0.17987040042725105 15.783362759345628, 0.8774517923593521 -0.5245310217142105 M-0.6241615489125252 36.01623979043592 C-0.7518961163610218 25.33034535988554, -0.20926091395318497 13.12852838294845, -0.6112461760640144 0.5358352139592171" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2130.7364758948847 2958.2308495471234) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2161.2364758948847 2957.7308495471234) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(2250.2364758948847 2950.7308495471234) rotate(0 17.75 17.75)"><path d="M1.0561283975839615 0.3738797754049301 L35.05063857138157 0.8744791597127914 L35.48351849615574 35.92132793366909 L1.9206861406564713 36.94069562852383" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.055244579911231995 1.2888831049203873 C13.835698738321664 1.590593797005713, 29.412051602452998 -1.8358097254857422, 36.66893516480923 1.669340506196022 M-0.1361897513270378 -0.015486307442188263 C12.416098352149131 0.3740075787529349, 24.77277640923858 0.6458221634849906, 35.12330622226 0.6146547123789787 M34.35250221192837 1.1342719942331314 C35.65061199370771 11.626187809929252, 36.39716779891401 23.263016345351936, 35.518439158797264 33.57663430273533 M34.9268779233098 0.824236087501049 C36.48808241333813 13.18295497186482, 36.6783230349049 25.09360714480281, 35.95243377238512 34.930626802146435 M35.840311869978905 34.69427575170994 C24.49321003295481 36.55228977914899, 13.756322429329153 37.71093874689191, -0.8161541372537613 34.9236406236887 M34.865563578903675 34.619517259299755 C27.62975437156856 36.36826742265374, 20.056245274096728 35.048971777893605, -0.9880843684077263 35.67945259064436 M0.08543048799037933 35.31508578360081 C-2.2144235760346054 24.463641164079306, -0.7448147923126817 12.03542957678437, 0.8267763704061508 1.3221758753061295 M0.1576504185795784 35.44335525482893 C0.021761129833757886 26.45452732257545, 0.8560904965177178 17.933160022646188, 0.16234750300645828 0.6763176247477531" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2251.2364758948847 2951.7308495471234) rotate(0 17.5 4.5)"><path d="M0.3738797754049301 -0.4493614286184311 L35.87447915971279 -0.01648150384426117 L35.42132793366909 10.920686140656471 L1.4406956285238266 8.818684473633766" stroke="none" stroke-width="0" fill="#000"></path><path d="M1.2888831049203873 -0.32364238798618317 C12.456677693873644 1.1030371513217687, 20.028662346303463 1.1786029662936925, 36.66934050619602 0.7355745881795883 M-0.015486307442188263 0.46045345813035965 C9.16053294017911 0.9091530341655016, 18.044003054499626 -0.32902736477553846, 35.61465471237898 -0.0023787543177604675 M35.51042239740491 0.199545132368803 C35.41441536031663 2.4931305393576624, 35.86228826127947 3.923568210750818, 34.1344854362309 9.546492301672696 M35.37090623937547 0.3150808934122324 C35.12133970931173 3.830583215877414, 34.71860935166478 7.0633832830935726, 34.7437820609659 8.903864884749055 M34.19427575170994 7.8848107904195786 C26.276150953024626 7.099078521504998, 18.009286530315876 8.243400439992547, -0.5763593763113022 7.556546077132225 M34.119517259299755 9.21762365847826 C27.099162217229605 9.509931100532413, 16.967410385608673 7.78986288420856, 0.1794525906443596 9.356002993881702 M-0.08321139737963679 8.10263708755374 C0.48412048883736136 5.505495142936707, 0.25264005728065975 2.8551175720989703, 0.5949791438877582 -0.5617453940212727 M-0.025490135326981522 8.865677631273865 C-0.22112558394670487 7.3530406471341845, -0.2603389433026314 5.29688597805798, 0.30434293113648897 0.23762888945639132" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2258.7364758948847 2943.2308495471234) rotate(0 3 7)"><path d="M-0.4493614286184311 0.8744791597127914 L5.983518496155739 0.42132793366909027 L7.920686140656471 15.440695628523827 L-0.18131552636623383 13.718226775527" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.09709271639585493 0.4374868556857109 C1.9043979406356812 -0.6291006942093372, 3.503454820811749 0.4621356587111949, 6.2206723764538765 -0.3237161949276924 M0.1381360374391079 0.16466322317719456 C2.354000631719828 0.24713677704334253, 4.0781011573970325 0.18532559216022487, 5.999286373704672 -0.24745490178465843 M6.31040353924036 0.6892426714301108 C6.752667757719754 5.13996829688549, 5.93258256599307 6.161712904274465, 6.850099135935306 15.13539073318243 M6.490125834196806 0.2641425140202045 C6.54305717855692 3.0837545178830625, 6.236466549932957 6.493601659685374, 5.850456487387419 13.785102678090334 M5.665443237125873 14.210499303042889 C3.019250813126564 14.533922265917063, 0.9699410185217858 13.802920459657908, -0.43303617686033247 13.710706774890422 M6.065287097543478 14.273485521227121 C4.492864418774843 13.824867863357067, 2.2501132182776926 14.001006812751294, 0.10680089816451072 14.1861552990973 M-1.395897863805294 14.614216254651547 C-0.3731518493592739 11.442736196517943, 0.572763325124979 9.302724339067936, -0.8738261684775352 -1.2629785791039465 M-0.20894590690732 13.57212767675519 C0.35301359117031095 10.150121044367552, 0.09157219350337978 7.751406190544366, 0.36964493915438656 0.13085792139172558" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(2273.7364758948847 2943.2308495471234) rotate(0 3 7)"><path d="M0.8744791597127914 -0.01648150384426117 L6.42132793366909 1.9206861406564713 L7.440695628523827 13.818684473633766 L-0.2817732244729996 12.524681463837624" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0.4374868556857109 0.32801080495119095 C1.1929924249649049 0.36038982108235357, 4.067655403912068 0.23038164809346198, 5.676283805072307 0.29555464535951614 M0.16466322317719456 0.2584459863603115 C1.9644937478005886 -0.14063534080982207, 3.620968712121248 -0.028340833783149694, 5.752545098215341 -0.09556404724717141 M6.689242671430111 0.8329926028847692 C7.4653328903019425 3.4096589595079427, 4.73304305151105 7.643602232635022, 7.13539073318243 13.246081449091434 M6.264142514020205 0.6232942692935466 C5.753066121041774 3.7143327333033085, 5.716831813752651 6.645714672654868, 5.785102678090334 14.69137409850955 M6.210499303042889 13.411857183277608 C4.6560004323720925 13.72169808253646, 2.022905065119266 13.533508146852256, -0.28929322510957717 13.926730735599994 M6.273485521227121 14.219595154374838 C4.458358680456877 13.716632108986378, 3.2151594825088976 14.119858317673208, 0.18615529909729955 13.702442725747824 M0.6142162546515464 13.632828284800052 C0.26707957848906516 10.318942117691039, 0.938467107862234 4.606571148335933, -1.2629785791039465 -0.8389919266104697 M-0.42787232324481006 14.375084649771452 C-0.48094061791896814 9.69293266609311, 0.48135260164737703 5.528223767131567, 0.13085792139172558 -0.15727650001645088" stroke="#000000" stroke-width="1" fill="none"></path></g><g><g transform="translate(2267.2364758948847 2959.7308495471234) rotate(0 0.8333176635205746 12.12975324690342)"><path d="M1.0933693498373032 -1.9681140035390854 C1.3082746260240674 11.323464896418153, 1.0571361270174384 18.23284052114934, 1.4851821511983871 26.227620497345924 M0.8614866212010384 0.8206919953227043 C-0.10544792670756575 8.520871610604228, 0.41809278134256606 14.990328510887922, 0.181453175842762 25.926251105964184" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2257.7364758948847 2958.7308495471234) rotate(0 -0.5737110041081905 14.095221109688282)"><path d="M-1.9681140035390854 1.168935164809227 C2.015409979149699 8.67974182162434, -0.5910755698382855 16.876250041462484, 0.22762049734592438 27.969027385115623 M0.8206919953227043 -0.3766937777400017 C0.9413630602508783 9.682583495341241, -0.3251186518371105 19.05568510826677, -0.07374889403581619 28.567135997116566" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2277.236475894885 2960.2308495471234) rotate(0 0.845221109688282 13.517780814319849)"><path d="M1.168935164809227 1.669340506196022 C1.4343508942052723 8.445026008598507, 2.243330171145499 18.761019625701014, 1.4690273851156235 26.42090691626072 M-0.3766937777400017 0.6146547123789787 C0.7496319456771017 8.212477344460787, 0.9657954874262213 17.513215520046653, 2.0671359971165657 25.721716813743114" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2286.2364758948847 2968.2308495471234) rotate(0 -16.05447134003043 0.48556103962073394)"><path d="M1.669340506196022 0.7355745881795883 C-13.137781877815724 1.1839011292159558, -24.38983529210091 0.16774636313319208, -33.07909308373928 1.0977548211812973 M0.6146547123789787 -0.0023787543177604675 C-13.95390418469906 -0.44662270858883857, -26.83185791820288 0.43906011268496514, -33.778283186256886 0.49231619387865067" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g><g transform="translate(2285.7364758948847 2977.2308495471234) rotate(0 -17.38605460897088 0.8219596296548843)"><path d="M0.7355745881795883 -1.079053983092308 C-6.266367966607214 0.22969608828425414, -14.539917526319625 1.6245768070220947, -34.9022451788187 2.7229732424020767 M-0.0023787543177604675 -0.8248496726155281 C-14.076804945096374 0.029596001952886586, -26.945613306984306 1.2071309092640878, -35.50768380612135 1.5949947163462639" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(1824.15996631772 1770.2890595675917) rotate(0 244 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: initial state</text></g><g transform="translate(1855.6599663177199 2520.2890595675917) rotate(0 275 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"># updating product: updating stock</text></g><g transform="translate(1230.1599663177199 1685.2890595675917) rotate(0 98 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">alternative 1</text></g><g transform="translate(2047.6599663177199 1692.7890595675917) rotate(0 98 21)"><text x="0" y="33" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">alternative 2</text></g><g transform="translate(1410.3187679866014 1348.8928867907737) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.4245540883265806 4.458250329234831, 3.1105650007146095 2.5187166413937736, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.0416068012916408 4.8987807898708216, 2.34467042664473 3.399777562665754, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C3.2858567810230084 8.414078862104724, 6.438568181178406 4.78729927028044, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.2853176939057724 9.56506741811414, 4.437490006943934 7.089276382299275, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C4.912805019729161 12.639501354936556, 9.953921482652161 6.840360238868213, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.6729466808315223 14.06579521743392, 7.474204804856884 9.69294796386294, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.506626709289865 15.75266317612456, 14.746962656844508 7.423609444391108, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C7.989401072722355 15.19729480061559, 15.712511383709488 6.312872693373166, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.108748000762871 22.307382619143166, 14.212662063851969 14.13526431335243, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C5.260777704715555 24.433229265198854, 10.516721471757338 18.386957605463806, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C7.808664564618157 27.599233063216616, 15.873952015623992 18.321181184423438, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.298340080009983 28.1862942277412, 14.853303046407644 19.49530351347261, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C9.780493364357602 31.427915854371093, 17.454830323202298 22.599601070547592, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C13.368263852888148 27.300658032005664, 24.63037130026339 14.345085425816738, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C14.974944290744936 31.549395962618448, 22.85671676811436 22.482453911748596, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C19.465076460800553 26.384089769939752, 31.836981108225594 12.151841526391202, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C24.22926903898746 27.000525488899985, 35.72229182774629 13.779315169240725, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C23.12470329438591 28.27118302518816, 33.51316033854319 16.320630241817078, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C31.052683527570583 25.24809737868312, 44.382105397049926 9.914351573513276, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C29.795719308109998 26.694069305757793, 41.86817695812876 12.806295427662622, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C32.38282151190626 29.81496101108199, 42.055365957858655 18.687971463017305, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C33.30055899329363 28.75922480637138, 43.890840920633394 16.576499053596084, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C40.44468713249262 26.63785784472148, 52.53602276217828 12.728367335225354, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C39.953269725938405 27.203168903979936, 51.55318794906984 13.858989453742264, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C41.61400376214182 31.389725282758157, 49.88764061361407 21.871994836004983, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C47.01201809050106 25.18002013768735, 60.68366927033254 9.452584545863374, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C48.167500219764634 29.947802347897323, 57.35155909200658 19.38275117121238, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C50.12455077371315 27.696473219300575, 61.26566019990361 14.880092914018881, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C54.950211905547526 28.242197456107192, 65.92996705570975 15.6114340123384, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C54.90763642626359 28.291174942397724, 65.84481609714189 15.70938898491946, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C63.22322650475755 24.82219517552106, 76.83292181727667 9.166031656095196, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C63.59626442284176 24.393064139861472, 77.5789976534451 8.307769584776025, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C65.05761358779769 28.80898657540063, 75.51468057549434 16.779507080560627, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C64.76865042983721 29.141400663169186, 74.93675425957338 17.444335256097734, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.21785733748133 31.27055435347016, 76.19209363800852 22.097244841628747, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C70.07031189293934 29.139549157058624, 79.89700274892454 17.83523444880568, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C73.59383619649051 31.183210463001203, 81.95703594816428 21.562449685397112, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C72.01490616069215 32.99956169339599, 78.79917587656755 25.19515214618668, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C74.86289472535546 35.82033797130387, 78.85207856904103 31.231306906931508, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C75.0218753433517 35.637451691000514, 79.17003980503353 30.865534346324804, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.60436868969872 36.46290846588569, 83.34801108986494 32.156340520801436, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.53695074376839 36.54046394096367, 83.21317519800428 32.3114514709574, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.00849972587095 38.64391601465589, 84.51319872535629 36.9129578232709, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.04239395530837 38.60492516392397, 84.58098718423113 36.83497612180707, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C32.74580490000551 0, 65.49160980001102 0, 85.70000000000013 0 M0 0 C19.455593113824758 0, 38.911186227649516 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 13.62363718673587, 85.70000000000013 27.24727437347174, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 7.82143609598279, 85.70000000000013 15.64287219196558, 85.70000000000013 39 M85.70000000000013 39 C56.40713170640179 39, 27.114263412803453 39, 0 39 M85.70000000000013 39 C63.53586610801528 39, 41.37173221603042 39, 0 39 M0 39 C0 25.79555174186826, 0 12.591103483736518, 0 0 M0 39 C0 26.93629071637988, 0 14.872581432759759, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1425.6641836672989 840.473507170834) rotate(0 21.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1406.5687679866014 833.2238730819438) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.4555318372155774 4.422614505586104, 3.172520498492603 2.447444994096318, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C1.1870386413074132 4.7314805957126564, 2.6355341066762747 3.0651771743494245, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C2.762329949078644 9.016327589906023, 5.391514517289677 5.991796725883039, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.8222380466076165 8.947411207171978, 5.511330712347622 5.853963960414949, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C6.064106454627717 11.315080557041043, 12.256524352449272 4.1915186430771865, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C3.24002755519765 14.56381170244487, 6.608366553589139 10.68898093388484, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C7.425263986294751 15.846260282183612, 14.58423721085428 7.610803656509212, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C5.943864090256802 17.55041592104616, 11.621437418778381 11.019114934234302, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C7.471353066344916 21.89025320739928, 14.937872195016059 13.301005489864657, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C5.357450122959003 24.322020369401937, 10.710066308244233 18.164539813869972, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C9.23323120088127 25.960456610878424, 18.72308528815022 15.043628279747054, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C11.791742976225766 23.01722549501918, 23.84010883883921 9.157166048028564, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C16.01034648426053 24.261289643607526, 29.914536563008152 8.266348649020458, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C10.806458292634769 30.24767821396427, 19.506760179756633 20.23912578973395, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C16.157048766897635 30.189540319217848, 25.220925720419753 19.76274262494739, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C18.769447291424736 27.18431958953109, 30.445722769473957 13.752301165573876, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C20.590166252526892 31.18683436507417, 28.44408625482515 22.15193292158909, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.099060073144468 29.451050583984703, 31.461873896060304 18.680365359410164, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C26.69354294820576 30.2627149838195, 35.663824238320274 19.943586783786042, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C25.080546201339722 32.118255482364454, 32.4378307445882 23.65466778087595, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C34.63283553007527 27.226615968775953, 46.55539399419669 13.51128137840523, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C32.99233191221806 29.11379950269066, 43.274386758482265 17.285648446234642, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C35.61771520518492 32.19065385243897, 42.88207890756287 23.833959350660326, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C42.3260917323811 24.47354943180949, 56.29883196195524 8.399750509401372, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C47.27698140814668 24.87521470799536, 61.213595905623784 8.842973686479397, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C42.363685514622375 30.527315079234448, 51.38700411857516 20.14717442895757, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C50.201826110359974 27.607578113364703, 61.42021087319726 14.702302702147133, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C52.71637700351996 24.714918207524086, 66.44931265951723 8.9169828904659, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C56.20877578647401 26.794385329019903, 68.44709481756271 12.715809758163818, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C51.65038491822366 32.038214171619856, 59.330313081062 23.20346744336373, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C59.5617026089435 29.03429658755033, 69.50987402564857 17.59023448015374, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C63.703566274137145 24.269627480094943, 77.79360135603586 8.060896265242967, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C62.270941628361385 32.01468595882482, 69.94133665662174 23.190905847409006, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C66.07942668606204 27.633525069072714, 77.55830677202303 14.428584067904794, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C68.07941910245168 31.42980932539971, 75.91521716794921 22.41575478548785, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C70.04673423488033 29.16667215000598, 79.8498474328065 17.889480434700385, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C73.69913319971204 31.062080117120125, 82.1676299546073 21.320188993634957, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C72.41038734454995 32.54461263383561, 79.59013824428314 24.28525402706593, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C77.2753954624955 33.04507334090056, 83.67708004332113 25.680777646124895, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C75.51899407856129 35.06558200337773, 80.16427727545272 29.721794971079234, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C80.54315134759022 35.38296255500032, 85.22557640564797 29.996448699030687, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.81466061450901 36.22099527929023, 83.76859493948552 31.67251414761051, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.66022725489519 37.89418925515019, 85.81665378340477 35.41350430425949, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.60362085695817 37.95930746698352, 85.70344098753074 35.54374072792615, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C21.05272591106597 0, 42.10545182213194 0, 85.70000000000013 0 M0 0 C20.390299159362943 0, 40.780598318725886 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 12.150230193883182, 85.70000000000013 24.300460387766364, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 11.797666608542205, 85.70000000000013 23.59533321708441, 85.70000000000013 39 M85.70000000000013 39 C57.69139444358655 39, 29.682788887172975 39, 0 39 M85.70000000000013 39 C66.92966488234708 39, 48.15932976469404 39, 0 39 M0 39 C0 24.39182279035449, 0 9.783645580708978, 0 0 M0 39 C0 27.356765545159575, 0 15.713531090319155, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(1415.6641836672989 260.31505805746497) rotate(0 21.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">clear</text></g><g transform="translate(1396.5687679866014 253.0654239685748) rotate(0 42.85000000000002 19.5)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.2614568240614483 6.397784017075889 C1.5247266891293827 4.343014934002126, 3.3109102023202137 2.2882458509283623, 4.98701540786261 0.36010737529371317 M-0.2614568240614483 6.397784017075889 C0.880941370554186 5.083605225523745, 2.0233395651698203 3.769426433971601, 4.98701540786261 0.36010737529371317 M0.13314538086761107 12.040858453929006 C4.035985275130465 7.551154741127234, 7.938825169393319 3.061451028325461, 10.630089844715727 -0.03449482963534578 M0.13314538086761107 12.040858453929006 C2.67071835281382 9.121714675984162, 5.208291324760029 6.202570898039318, 10.630089844715727 -0.03449482963534578 M-0.12831144319383903 18.438642471004897 C5.889131394208884 11.516366338598454, 11.906574231611607 4.594090206192011, 15.617105252578337 0.32561254565836695 M-0.12831144319383903 18.438642471004897 C5.443982487779084 12.028451577064278, 11.016276418752007 5.618260683123658, 15.617105252578337 0.32561254565836695 M0.26629076173522215 24.081716907858013 C6.002808864492601 17.48260771499452, 11.73932696724998 10.883498522131026, 21.260179689431453 -0.06898965927069156 M0.26629076173522215 24.081716907858013 C7.539391620207313 15.714971457739715, 14.812492478679404 7.348226007621417, 21.260179689431453 -0.06898965927069156 M0.004833937673772937 30.4795009249339 C9.906993859538126 19.08836898757109, 19.80915378140248 7.69723705020828, 26.247195097294064 0.29111771602302206 M0.004833937673772937 30.4795009249339 C6.400185529507074 23.12249050061828, 12.795537121340375 15.765480076302657, 26.247195097294064 0.29111771602302206 M-0.25662288638767805 36.877284942009794 C10.422681947823541 24.592150049650606, 21.10198678203476 12.307015157291417, 31.89026953414718 -0.10348448890603734 M-0.25662288638767805 36.877284942009794 C7.042802353155541 28.480256755567623, 14.34222759269876 20.083228569125453, 31.89026953414718 -0.10348448890603734 M2.1061564055129054 40.256230638194594 C14.487790658574035 26.01278976370757, 26.869424911635164 11.769348889220545, 36.877284942009794 0.25662288638767805 M2.1061564055129054 40.256230638194594 C14.621202188054145 25.859317355034616, 27.136247970595385 11.462404071874637, 36.877284942009794 0.25662288638767805 M7.093171813375516 40.6163380134883 C18.525508948093822 27.464938553008803, 29.95784608281213 14.313539092529304, 42.520359378862906 -0.13797931854138312 M7.093171813375516 40.6163380134883 C18.172062686112294 27.87153196644271, 29.250953558849073 15.12672591939712, 42.520359378862906 -0.13797931854138312 M12.736246250228632 40.221735808559245 C20.949472545463763 30.773499757163894, 29.162698840698894 21.325263705768542, 47.50737478672552 0.22212805675232872 M12.736246250228632 40.221735808559245 C22.747549333443413 28.705049026514676, 32.75885241665819 17.188362244470106, 47.50737478672552 0.22212805675232872 M17.723261658091243 40.58184318385296 C28.57316026875329 28.100462600596217, 39.423058879415336 15.619082017339469, 53.15044922357863 -0.1724741481767289 M17.723261658091243 40.58184318385296 C29.94616841392979 26.52099740752805, 42.16907516976834 12.46015163120314, 53.15044922357863 -0.1724741481767289 M22.710277065953854 40.941950559146676 C30.60928203926329 31.855184789369865, 38.50828701257272 22.768419019593058, 58.13746463144125 0.1876332271169865 M22.710277065953854 40.941950559146676 C34.24309550344289 27.674960582443322, 45.77591394093193 14.407970605739969, 58.13746463144125 0.1876332271169865 M28.353351502806966 40.54734835421761 C36.5691095068009 31.09619990504985, 44.784867510794825 21.645051455882093, 63.78053906829436 -0.20696897781207468 M28.353351502806966 40.54734835421761 C41.74632990408641 25.140489122792403, 55.139308305365844 9.73362989136719, 63.78053906829436 -0.20696897781207468 M33.34036691066958 40.90745572951133 C40.74931779527038 32.384432701214415, 48.158268679871185 23.8614096729175, 68.76755447615697 0.1531383974816336 M33.34036691066958 40.90745572951133 C43.37070339550055 29.368873523565544, 53.401039880331524 17.83029131761976, 68.76755447615697 0.1531383974816336 M38.98344134752269 40.51285352458227 C50.89271684618298 26.81279923803224, 62.80199234484327 13.112744951482213, 74.41062891301009 -0.2414638074474169 M38.98344134752269 40.51285352458227 C52.38933444687543 25.09113763250474, 65.79522754622818 9.669421740427207, 74.41062891301009 -0.2414638074474169 M43.97045675538531 40.872960899875984 C57.25506820175456 25.59076358976619, 70.53967964812381 10.3085662796564, 79.3976443208727 0.11864356784629138 M43.97045675538531 40.872960899875984 C54.01549183903063 29.317469890223748, 64.06052692267596 17.761978880571508, 79.3976443208727 0.11864356784629138 M49.613531192238426 40.47835869494692 C61.766908843533585 26.49749700387109, 73.92028649482874 12.516635312795263, 85.04071875772581 -0.27595863708276624 M49.613531192238426 40.47835869494692 C57.59209042061002 31.300076223486606, 65.5706496489816 22.12179375202629, 85.04071875772581 -0.27595863708276624 M54.60054660010103 40.838466070240635 C67.59245085286398 25.892989868221868, 80.58435510562691 10.947513666203104, 88.0595570786169 2.348277478879261 M54.60054660010103 40.838466070240635 C66.71712723226744 26.899934507450432, 78.83370786443385 12.961402944660232, 88.0595570786169 2.348277478879261 M60.24362103695415 40.44386386531157 C67.69401400131045 31.873167177734402, 75.14440696566675 23.302470490157237, 88.45415928354596 7.991351915732373 M60.24362103695415 40.44386386531157 C68.99039340992344 30.381853262294154, 77.73716578289273 20.319842659276738, 88.45415928354596 7.991351915732373 M65.23063644481675 40.80397124060529 C69.92256920361453 35.40652002607902, 74.6145019624123 30.009068811552748, 88.19270245948451 14.389135932808273 M65.23063644481675 40.80397124060529 C71.3558200342678 33.75775355087222, 77.48100362371885 26.71153586113916, 88.19270245948451 14.389135932808273 M70.87371088166988 40.40936903567623 C77.66819664726737 32.59320726762002, 84.46268241286487 24.77704549956382, 87.93124563542307 20.786919949884158 M70.87371088166988 40.40936903567623 C76.62275497634309 33.79585033744366, 82.3717990710163 27.182331639211093, 87.93124563542307 20.786919949884158 M75.8607262895325 40.76947641096994 C79.17495020512054 36.95689792402115, 82.48917412070857 33.144319437072355, 88.32584784035213 26.429994386737274 M75.8607262895325 40.76947641096994 C79.07364264013388 37.07343894619429, 82.28655899073526 33.37740148141864, 88.32584784035213 26.429994386737274 M81.5038007263856 40.374874206040886 C83.683618584539 37.86728060852505, 85.8634364426924 35.35968701100921, 88.06439101629068 32.82777840381317 M81.5038007263856 40.374874206040886 C83.62134083128937 37.938922968336136, 85.73888093619314 35.50297173063139, 88.06439101629068 32.82777840381317" stroke="#fa5252" stroke-width="0.5" fill="none"></path><path d="M0 0 C32.98184273473924 0, 65.96368546947848 0, 85.70000000000013 0 M0 0 C32.54269795306032 0, 65.08539590612064 0, 85.70000000000013 0 M85.70000000000013 0 C85.70000000000013 9.08658194616437, 85.70000000000013 18.17316389232874, 85.70000000000013 39 M85.70000000000013 0 C85.70000000000013 10.802469717711212, 85.70000000000013 21.604939435422423, 85.70000000000013 39 M85.70000000000013 39 C52.886900880411346 39, 20.073801760822562 39, 0 39 M85.70000000000013 39 C68.02522339455794 39, 50.35044678911574 39, 0 39 M0 39 C0 26.94815444871783, 0 14.896308897435663, 0 0 M0 39 C0 28.28894211128354, 0 17.57788422256708, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g></svg> \ No newline at end of file
diff --git a/backoffice-tip-create.confirmation.svg b/backoffice-tip-create.confirmation.svg
index 50e8c0b4..3b8e01d3 100644
--- a/backoffice-tip-create.confirmation.svg
+++ b/backoffice-tip-create.confirmation.svg
@@ -13,4 +13,4 @@
}
</style>
</defs>
- <rect x="0" y="0" width="571.8889058430991" height="551.2222391764319" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 10) rotate(0 275.94445292154955 265.61111958821596)"><path d="M0 0 C140.46 0, 280.92 0, 551.89 0 M0 0 C204.72 0, 409.45 0, 551.89 0 M551.89 0 C551.89 152.39, 551.89 304.77, 551.89 531.22 M551.89 0 C551.89 107.04, 551.89 214.08, 551.89 531.22 M551.89 531.22 C332.47 531.22, 113.04 531.22, 0 531.22 M551.89 531.22 C417.98 531.22, 284.07 531.22, 0 531.22 M0 531.22 C0 418.2, 0 305.18, 0 0 M0 531.22 C0 346.7, 0 162.17, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(85.44442749023415 48.555572509765625) rotate(0 142 16)"><text x="0" y="26" font-family="Helvetica, Segoe UI Emoji" font-size="28px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Tip created succesfully</text></g><g transform="translate(65.88732457160927 145.118741350042) rotate(0 33.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">amount</text></g><g stroke-linecap="round" transform="translate(267.5406907399499 142.480934712235) rotate(0 77.99999999999989 14.152236652236752)"><path d="M0 0 C55.83 0, 111.67 0, 156 0 M0 0 C38.38 0, 76.75 0, 156 0 M156 0 C156 7.9, 156 15.79, 156 28.3 M156 0 C156 10.3, 156 20.6, 156 28.3 M156 28.3 C117.29 28.3, 78.59 28.3, 0 28.3 M156 28.3 C96.97 28.3, 37.93 28.3, 0 28.3 M0 28.3 C0 21.43, 0 14.55, 0 0 M0 28.3 C0 22.04, 0 15.77, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(66.88732457160927 204.63380306959152) rotate(0 49.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">justification</text></g><g transform="translate(73.66508539517713 338.18934167093744) rotate(0 33.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">tip URL</text></g><g stroke-linecap="round" transform="translate(206.4295796288386 142.52151912781937) rotate(0 30.5 14.097222222222399)"><path d="M0 0 C14.45 0, 28.9 0, 61 0 M0 0 C13.99 0, 27.98 0, 61 0 M61 0 C61 10.08, 61 20.16, 61 28.19 M61 0 C61 7.45, 61 14.91, 61 28.19 M61 28.19 C41.93 28.19, 22.86 28.19, 0 28.19 M61 28.19 C41.66 28.19, 22.33 28.19, 0 28.19 M0 28.19 C0 21.41, 0 14.63, 0 0 M0 28.19 C0 20.05, 0 11.91, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(213.09624629550513 145.118741350042) rotate(0 21 11.5)"><text x="21" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">USD</text></g><g stroke-linecap="round" transform="translate(206.4295796288386 207.92332482337952) rotate(0 134.84126984126988 51.111111111111086)"><path d="M0 0 C97.82 0, 195.63 0, 269.68 0 M0 0 C87.58 0, 175.15 0, 269.68 0 M269.68 0 C269.68 23.55, 269.68 47.1, 269.68 102.22 M269.68 0 C269.68 38.76, 269.68 77.52, 269.68 102.22 M269.68 102.22 C168.96 102.22, 68.23 102.22, 0 102.22 M269.68 102.22 C192.45 102.22, 115.21 102.22, 0 102.22 M0 102.22 C0 63.99, 0 25.75, 0 0 M0 102.22 C0 69.03, 0 35.84, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(205.3184685177273 334.0422606998018) rotate(0 85.37038167317712 15)"><path d="M0 0 C58.97 0, 117.94 0, 170.74 0 M0 0 C66.44 0, 132.88 0, 170.74 0 M170.74 0 C170.74 9.84, 170.74 19.67, 170.74 30 M170.74 0 C170.74 11.46, 170.74 22.92, 170.74 30 M170.74 30 C111.07 30, 51.39 30, 0 30 M170.74 30 C121.68 30, 72.63 30, 0 30 M0 30 C0 21.78, 0 13.56, 0 0 M0 30 C0 20.57, 0 11.13, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(76.67993668715121 404.7879712581632) rotate(0 41.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">valid until</text></g><g stroke-linecap="round" transform="translate(212.71710211484606 392.3770074341571) rotate(0 71.39795918367349 18.459183673469397)"><path d="M0.56 1.22 L141.57 0.94 L141.78 38.25 L-1.25 38.38" stroke="none" stroke-width="0" fill="#fff"></path><path d="M-0.98 1.62 C34.12 -1.49, 68.83 -1.89, 142.94 -0.88 M0.57 -0.59 C29.47 0.89, 57.16 -0.08, 142.07 0.14 M142.91 -0.64 C143.04 15.1, 140.92 25.51, 142.3 36.46 M141.87 -0.37 C142.21 8.72, 143.63 15.39, 141.94 37.29 M144.77 37.32 C105.38 36.8, 65.06 35.87, 0.85 37.03 M142.32 37.91 C86.66 35.41, 32.17 35.36, 0.24 37.86 M1.37 35.32 C1.21 25.05, 1.19 10.31, -0.4 -0.05 M-0.02 37.73 C0.2 26.83, -0.88 18.04, -0.59 0.08" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(247.36506129851932 398.8361911076265) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(277.8650612985193 398.3361911076265) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g stroke-linecap="round" transform="translate(366.8650612985193 391.3361911076265) rotate(0 17.75 17.75)"><path d="M0.94 -1.01 L36.83 -1.25 L36.96 34.45 L-0.83 36.6" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-1.6 -0.35 C6.91 0.23, 16.94 -1.91, 34.19 0.08 M0.95 -0.04 C12.64 -1.04, 25.48 0.32, 34.73 -0.09 M37.19 -1.59 C34.07 10.35, 34.93 20.04, 35.6 37.4 M36.19 0.82 C34.7 6.51, 35.74 14.48, 34.58 35.73 M35.05 35.71 C19.96 36.63, 7.29 37.51, -0.4 33.62 M35.33 35.31 C26.31 35.42, 18.5 35.89, -0.89 34.55 M1.85 36.37 C1.11 22.03, 0.24 8.62, 1.34 1.61 M-0.36 34.87 C0.29 24.35, -0.21 14.65, 0.61 -0.61" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(367.8650612985193 392.3361911076265) rotate(0 17.5 4.5)"><path d="M-1.01 1.33 L33.75 1.46 L33.95 8.17 L1.1 7.74" stroke="none" stroke-width="0" fill="#000"></path><path d="M-0.35 -2 C13.48 -0.6, 24.68 -1.03, 35.08 1.14 M-0.04 -0.02 C7.69 -0.19, 17.48 -1.1, 34.91 0.05 M34.28 -0.59 C34.8 2.2, 34.28 4.86, 35.86 8.17 M35.37 -0.3 C34.56 2.54, 34.82 4.33, 35.1 9.44 M35.21 7.62 C24.64 9.03, 14.32 8.52, -1.88 8.05 M34.81 8.78 C21.27 9.88, 7.77 8.05, -0.95 9.68 M0.39 9.37 C-0.22 7, -0.37 5.64, 0.73 -0.02 M-0.28 9.26 C-0.31 5.6, 0.09 2.41, -0.28 0.21" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(375.3650612985193 383.8361911076265) rotate(0 3 7)"><path d="M1.33 -1.25 L7.46 -1.05 L5.17 15.1 L-1.26 12.71" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.6 0.04 C1.4 -0.59, 2.93 0.04, 6.34 -0.36 M-0.01 -0.22 C2.36 0.19, 4.41 -0.02, 6.02 -0.1 M5.08 -0.34 C5.8 3.42, 6.19 10.47, 4.71 13.49 M5.53 -0.6 C6.36 3.16, 5.45 6.86, 6.69 14.14 M5.59 14.26 C3.76 14.53, 1.34 13.44, -0.28 14.6 M5.93 14.07 C5.03 14.2, 3.24 13.7, 0.21 13.76 M0.58 13.72 C-0.16 8.67, 0.81 5.49, -0.03 1.14 M0.41 13.59 C0.11 9.37, 0.48 3.91, 0.33 -0.35" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(390.3650612985193 383.8361911076265) rotate(0 3 7)"><path d="M-1.25 1.46 L4.95 -0.83 L7.1 12.74 L-1.29 14.58" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0.04 -0.26 C0.72 -0.33, 2.67 0.4, 5.64 0.52 M-0.22 0.04 C2.56 -0.23, 4.71 0.02, 5.9 0.08 M5.66 -0.32 C4.73 5.45, 7.22 9.47, 5.49 13.15 M5.4 0.26 C5.93 4.52, 6.28 11.02, 6.14 14.55 M6.26 14.03 C4.85 13.84, 2.11 13.67, 0.6 13.45 M6.07 14.28 C4.52 13.72, 2.32 14.19, -0.24 14.13 M-0.28 13.97 C-0.57 9.44, 1.07 2.97, 1.14 0.63 M-0.41 14.06 C0.28 10.73, -0.34 6.93, -0.35 0.47" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(383.8650612985193 400.3361911076265) rotate(0 0.6422718642013479 13.197904566302896)"><path d="M-0.88 -1.99 C-1.43 7.57, 1.15 13.78, 2.24 28.39 M0.14 0.63 C-0.63 7.75, 0.34 15.35, 0.76 27.34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(374.3650612985193 399.3361911076265) rotate(0 0.19790456630289555 13.301320007070899)"><path d="M-1.99 -1.31 C0.2 6.74, -0.97 12.94, 2.39 27.91 M0.63 -0.77 C-0.14 8.39, -0.23 16.93, 1.34 27.2" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(393.8650612985193 400.8361911076265) rotate(0 0.2720004984978459 12.683548028115183)"><path d="M-1.31 0.08 C1.49 6.67, 2.55 17.58, 1.41 25.45 M-0.77 -0.09 C0.43 5.46, 0.98 12.41, 0.7 24.84" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(402.8650612985193 408.8361911076265) rotate(0 -17.287554731126875 -0.13689562075796857)"><path d="M0.08 1.14 C-9.9 1.52, -15.53 -0.31, -34.05 -1.46 M-0.09 0.05 C-10.99 0, -20.5 -1.06, -34.66 -0.25" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(402.3650612985193 417.8361911076265) rotate(0 -18.16205287910998 0.04547248221933842)"><path d="M1.14 -1.19 C-5.48 2.11, -14.51 0.37, -37.46 1.28 M0.05 -0.32 C-12.82 1.18, -26.96 0.04, -36.25 0.77" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round" transform="translate(340.7275521017252 468.94674259140356) rotate(0 68.33333333333326 18.888905843098996)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.26 6.4 C1.09 4.84, 2.44 3.29, 4.99 0.36 M-0.26 6.4 C1.46 4.42, 3.17 2.45, 4.99 0.36 M0.13 12.04 C2.6 9.2, 5.07 6.36, 10.63 -0.03 M0.13 12.04 C2.36 9.48, 4.59 6.92, 10.63 -0.03 M-0.13 18.44 C3.98 13.71, 8.09 8.98, 15.62 0.33 M-0.13 18.44 C3.41 14.36, 6.95 10.29, 15.62 0.33 M0.27 24.08 C6 17.49, 11.72 10.9, 21.26 -0.07 M0.27 24.08 C7.46 15.81, 14.65 7.53, 21.26 -0.07 M0 30.48 C5.48 24.19, 10.95 17.89, 26.25 0.29 M0 30.48 C9.05 20.07, 18.1 9.66, 26.25 0.29 M-0.26 36.88 C8.64 26.64, 17.54 16.41, 31.89 -0.1 M-0.26 36.88 C12.31 22.42, 24.88 7.96, 31.89 -0.1 M2.11 40.26 C13.59 27.05, 25.06 13.85, 36.88 0.26 M2.11 40.26 C12.5 28.3, 22.9 16.34, 36.88 0.26 M7.75 39.86 C19.43 26.42, 31.11 12.99, 42.52 -0.14 M7.75 39.86 C18.98 26.94, 30.22 14.01, 42.52 -0.14 M12.74 40.22 C24.86 26.28, 36.98 12.33, 47.51 0.22 M12.74 40.22 C20.81 30.93, 28.89 21.64, 47.51 0.22 M18.38 39.83 C31.65 24.56, 44.91 9.3, 53.15 -0.17 M18.38 39.83 C28.39 28.31, 38.41 16.79, 53.15 -0.17 M23.37 40.19 C36.54 25.03, 49.71 9.88, 58.14 0.19 M23.37 40.19 C34.58 27.29, 45.8 14.38, 58.14 0.19 M29.01 39.79 C38.99 28.31, 48.97 16.82, 63.78 -0.21 M29.01 39.79 C40.04 27.1, 51.07 14.42, 63.78 -0.21 M34 40.15 C41.91 31.05, 49.83 21.94, 68.77 0.15 M34 40.15 C43.74 28.94, 53.49 17.73, 68.77 0.15 M39.64 39.76 C52.44 25.03, 65.24 10.31, 74.41 -0.24 M39.64 39.76 C47.47 30.75, 55.31 21.73, 74.41 -0.24 M44.63 40.12 C54.86 28.34, 65.1 16.57, 79.4 0.12 M44.63 40.12 C53.28 30.16, 61.94 20.2, 79.4 0.12 M50.27 39.72 C60.09 28.43, 69.91 17.13, 85.04 -0.28 M50.27 39.72 C61.03 27.34, 71.8 14.96, 85.04 -0.28 M55.26 40.08 C64.66 29.27, 74.06 18.46, 90.03 0.08 M55.26 40.08 C65.85 27.89, 76.45 15.7, 90.03 0.08 M60.9 39.69 C72.37 26.49, 83.85 13.29, 95.67 -0.31 M60.9 39.69 C69.59 29.69, 78.29 19.68, 95.67 -0.31 M65.89 40.05 C74.2 30.49, 82.51 20.92, 100.66 0.05 M65.89 40.05 C78.29 25.78, 90.69 11.52, 100.66 0.05 M71.53 39.65 C83.8 25.54, 96.08 11.42, 106.3 -0.34 M71.53 39.65 C78.82 31.27, 86.11 22.88, 106.3 -0.34 M76.52 40.01 C89.61 24.96, 102.7 9.9, 111.29 0.02 M76.52 40.01 C85.09 30.16, 93.66 20.3, 111.29 0.02 M82.16 39.62 C90.2 30.37, 98.24 21.12, 116.27 0.38 M82.16 39.62 C93.56 26.5, 104.97 13.38, 116.27 0.38 M87.15 39.98 C95.85 29.97, 104.55 19.96, 121.92 -0.02 M87.15 39.98 C95.8 30.03, 104.45 20.07, 121.92 -0.02 M92.79 39.59 C99.78 31.55, 106.76 23.51, 126.91 0.34 M92.79 39.59 C101.4 29.69, 110 19.78, 126.91 0.34 M97.78 39.95 C108.93 27.11, 120.09 14.28, 132.55 -0.05 M97.78 39.95 C110.68 25.1, 123.59 10.25, 132.55 -0.05 M103.42 39.55 C114.64 26.64, 125.86 13.73, 137.54 0.31 M103.42 39.55 C116.21 24.83, 129.01 10.11, 137.54 0.31 M108.41 39.91 C115.34 31.93, 122.28 23.95, 137.27 6.7 M108.41 39.91 C115.74 31.48, 123.06 23.05, 137.27 6.7 M114.05 39.52 C119.08 33.73, 124.11 27.95, 137.01 13.1 M114.05 39.52 C123.16 29.04, 132.27 18.56, 137.01 13.1 M119.04 39.88 C125.53 32.41, 132.02 24.95, 137.41 18.74 M119.04 39.88 C123.99 34.17, 128.95 28.47, 137.41 18.74 M124.02 40.24 C128.33 35.28, 132.63 30.33, 137.15 25.14 M124.02 40.24 C129.2 34.28, 134.37 28.33, 137.15 25.14 M129.67 39.84 C132.79 36.25, 135.91 32.66, 137.54 30.79 M129.67 39.84 C131.44 37.8, 133.21 35.77, 137.54 30.79" stroke="#82c91e" stroke-width="0.5" fill="none"></path><path d="M0 0 C49.47 0, 98.93 0, 136.67 0 M0 0 C42.29 0, 84.59 0, 136.67 0 M136.67 0 C136.67 8.33, 136.67 16.66, 136.67 37.78 M136.67 0 C136.67 8.06, 136.67 16.12, 136.67 37.78 M136.67 37.78 C93.85 37.78, 51.03 37.78, 0 37.78 M136.67 37.78 C88.21 37.78, 39.74 37.78, 0 37.78 M0 37.78 C0 24.21, 0 10.64, 0 0 M0 37.78 C0 27.5, 0 17.22, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(372.94974041552723 475.50226423853906) rotate(0 37.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">continue</text></g><g stroke-linecap="round" transform="translate(388.95413517850125 333.70723212274766) rotate(0 42.96295166015625 15.66137767973396)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.26 6.4 C1.33 4.57, 2.92 2.74, 4.99 0.36 M-0.26 6.4 C1.67 4.17, 3.61 1.95, 4.99 0.36 M0.13 12.04 C4.31 7.24, 8.49 2.43, 10.63 -0.03 M0.13 12.04 C4.25 7.31, 8.36 2.58, 10.63 -0.03 M-0.13 18.44 C4.4 13.23, 8.93 8.02, 15.62 0.33 M-0.13 18.44 C6.09 11.29, 12.3 4.14, 15.62 0.33 M0.27 24.08 C6.1 17.37, 11.93 10.66, 21.26 -0.07 M0.27 24.08 C8.03 15.15, 15.79 6.22, 21.26 -0.07 M0 30.48 C7.44 21.92, 14.88 13.36, 26.25 0.29 M0 30.48 C9.23 19.87, 18.45 9.26, 26.25 0.29 M2.37 33.86 C8.28 27.06, 14.18 20.27, 31.89 -0.1 M2.37 33.86 C11.1 23.81, 19.83 13.77, 31.89 -0.1 M8.01 33.46 C18.87 20.98, 29.72 8.49, 36.88 0.26 M8.01 33.46 C15.44 24.92, 22.87 16.37, 36.88 0.26 M13 33.82 C24.34 20.78, 35.68 7.73, 42.52 -0.14 M13 33.82 C20.91 24.72, 28.83 15.61, 42.52 -0.14 M18.64 33.43 C27.12 23.68, 35.59 13.93, 47.51 0.22 M18.64 33.43 C27.73 22.98, 36.81 12.53, 47.51 0.22 M23.63 33.79 C30.77 25.58, 37.9 17.37, 53.15 -0.17 M23.63 33.79 C30.05 26.4, 36.47 19.01, 53.15 -0.17 M29.27 33.39 C38.11 23.22, 46.96 13.05, 58.14 0.19 M29.27 33.39 C35.28 26.48, 41.29 19.57, 58.14 0.19 M34.26 33.75 C42.43 24.35, 50.6 14.95, 63.78 -0.21 M34.26 33.75 C40.39 26.7, 46.52 19.64, 63.78 -0.21 M39.9 33.36 C49.33 22.51, 58.76 11.66, 68.77 0.15 M39.9 33.36 C45.83 26.54, 51.75 19.73, 68.77 0.15 M44.89 33.72 C56.26 20.63, 67.64 7.55, 74.41 -0.24 M44.89 33.72 C54.79 22.33, 64.69 10.95, 74.41 -0.24 M50.53 33.33 C56.58 26.37, 62.62 19.42, 79.4 0.12 M50.53 33.33 C57.93 24.81, 65.34 16.29, 79.4 0.12 M55.52 33.69 C66.01 21.61, 76.51 9.54, 85.04 -0.28 M55.52 33.69 C62.29 25.89, 69.07 18.1, 85.04 -0.28 M61.16 33.29 C67.92 25.51, 74.68 17.74, 88.06 2.35 M61.16 33.29 C69.1 24.16, 77.03 15.03, 88.06 2.35 M66.15 33.65 C74.36 24.21, 82.56 14.77, 88.45 7.99 M66.15 33.65 C74.01 24.61, 81.87 15.57, 88.45 7.99 M71.79 33.26 C76.57 27.76, 81.35 22.26, 88.19 14.39 M71.79 33.26 C77.32 26.9, 82.85 20.54, 88.19 14.39 M76.78 33.62 C80.26 29.61, 83.75 25.6, 88.59 20.03 M76.78 33.62 C80.31 29.55, 83.84 25.49, 88.59 20.03 M82.42 33.22 C83.67 31.79, 84.91 30.35, 88.33 26.43 M82.42 33.22 C84.67 30.63, 86.92 28.04, 88.33 26.43" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C34.37 0, 68.73 0, 85.93 0 M0 0 C30.8 0, 61.6 0, 85.93 0 M85.93 0 C85.93 7.3, 85.93 14.59, 85.93 31.32 M85.93 0 C85.93 10.75, 85.93 21.51, 85.93 31.32 M85.93 31.32 C64.61 31.32, 43.3 31.32, 0 31.32 M85.93 31.32 C64.26 31.32, 42.59 31.32, 0 31.32 M0 31.32 C0 19.26, 0 7.2, 0 0 M0 31.32 C0 20.81, 0 10.29, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(396.7054353242104 338.1304988211914) rotate(0 35 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">copy url</text></g><g transform="translate(78.68102157642033 106.96052345493217) rotate(0 20 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">from</text></g><g transform="translate(211.63556703096583 97.86961436402316) rotate(0 71.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">video promotion</text></g></svg> \ No newline at end of file
+ <rect x="0" y="0" width="571.8889058430991" height="551.2222391764319" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 10) rotate(0 275.94445292154955 265.61111958821596)"><path d="M0 0 C140.46 0, 280.92 0, 551.89 0 M0 0 C204.72 0, 409.45 0, 551.89 0 M551.89 0 C551.89 152.39, 551.89 304.77, 551.89 531.22 M551.89 0 C551.89 107.04, 551.89 214.08, 551.89 531.22 M551.89 531.22 C332.47 531.22, 113.04 531.22, 0 531.22 M551.89 531.22 C417.98 531.22, 284.07 531.22, 0 531.22 M0 531.22 C0 418.2, 0 305.18, 0 0 M0 531.22 C0 346.7, 0 162.17, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(85.44442749023415 48.555572509765625) rotate(0 142 16)"><text x="0" y="26" font-family="Helvetica, Segoe UI Emoji" font-size="28px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Tip created successfully</text></g><g transform="translate(65.88732457160927 145.118741350042) rotate(0 33.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">amount</text></g><g stroke-linecap="round" transform="translate(267.5406907399499 142.480934712235) rotate(0 77.99999999999989 14.152236652236752)"><path d="M0 0 C55.83 0, 111.67 0, 156 0 M0 0 C38.38 0, 76.75 0, 156 0 M156 0 C156 7.9, 156 15.79, 156 28.3 M156 0 C156 10.3, 156 20.6, 156 28.3 M156 28.3 C117.29 28.3, 78.59 28.3, 0 28.3 M156 28.3 C96.97 28.3, 37.93 28.3, 0 28.3 M0 28.3 C0 21.43, 0 14.55, 0 0 M0 28.3 C0 22.04, 0 15.77, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(66.88732457160927 204.63380306959152) rotate(0 49.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">justification</text></g><g transform="translate(73.66508539517713 338.18934167093744) rotate(0 33.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">tip URL</text></g><g stroke-linecap="round" transform="translate(206.4295796288386 142.52151912781937) rotate(0 30.5 14.097222222222399)"><path d="M0 0 C14.45 0, 28.9 0, 61 0 M0 0 C13.99 0, 27.98 0, 61 0 M61 0 C61 10.08, 61 20.16, 61 28.19 M61 0 C61 7.45, 61 14.91, 61 28.19 M61 28.19 C41.93 28.19, 22.86 28.19, 0 28.19 M61 28.19 C41.66 28.19, 22.33 28.19, 0 28.19 M0 28.19 C0 21.41, 0 14.63, 0 0 M0 28.19 C0 20.05, 0 11.91, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(213.09624629550513 145.118741350042) rotate(0 21 11.5)"><text x="21" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">USD</text></g><g stroke-linecap="round" transform="translate(206.4295796288386 207.92332482337952) rotate(0 134.84126984126988 51.111111111111086)"><path d="M0 0 C97.82 0, 195.63 0, 269.68 0 M0 0 C87.58 0, 175.15 0, 269.68 0 M269.68 0 C269.68 23.55, 269.68 47.1, 269.68 102.22 M269.68 0 C269.68 38.76, 269.68 77.52, 269.68 102.22 M269.68 102.22 C168.96 102.22, 68.23 102.22, 0 102.22 M269.68 102.22 C192.45 102.22, 115.21 102.22, 0 102.22 M0 102.22 C0 63.99, 0 25.75, 0 0 M0 102.22 C0 69.03, 0 35.84, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(205.3184685177273 334.0422606998018) rotate(0 85.37038167317712 15)"><path d="M0 0 C58.97 0, 117.94 0, 170.74 0 M0 0 C66.44 0, 132.88 0, 170.74 0 M170.74 0 C170.74 9.84, 170.74 19.67, 170.74 30 M170.74 0 C170.74 11.46, 170.74 22.92, 170.74 30 M170.74 30 C111.07 30, 51.39 30, 0 30 M170.74 30 C121.68 30, 72.63 30, 0 30 M0 30 C0 21.78, 0 13.56, 0 0 M0 30 C0 20.57, 0 11.13, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(76.67993668715121 404.7879712581632) rotate(0 41.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">valid until</text></g><g stroke-linecap="round" transform="translate(212.71710211484606 392.3770074341571) rotate(0 71.39795918367349 18.459183673469397)"><path d="M0.56 1.22 L141.57 0.94 L141.78 38.25 L-1.25 38.38" stroke="none" stroke-width="0" fill="#fff"></path><path d="M-0.98 1.62 C34.12 -1.49, 68.83 -1.89, 142.94 -0.88 M0.57 -0.59 C29.47 0.89, 57.16 -0.08, 142.07 0.14 M142.91 -0.64 C143.04 15.1, 140.92 25.51, 142.3 36.46 M141.87 -0.37 C142.21 8.72, 143.63 15.39, 141.94 37.29 M144.77 37.32 C105.38 36.8, 65.06 35.87, 0.85 37.03 M142.32 37.91 C86.66 35.41, 32.17 35.36, 0.24 37.86 M1.37 35.32 C1.21 25.05, 1.19 10.31, -0.4 -0.05 M-0.02 37.73 C0.2 26.83, -0.88 18.04, -0.59 0.08" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(247.36506129851932 398.8361911076265) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g transform="translate(277.8650612985193 398.3361911076265) rotate(0 5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">/</text></g><g stroke-linecap="round" transform="translate(366.8650612985193 391.3361911076265) rotate(0 17.75 17.75)"><path d="M0.94 -1.01 L36.83 -1.25 L36.96 34.45 L-0.83 36.6" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-1.6 -0.35 C6.91 0.23, 16.94 -1.91, 34.19 0.08 M0.95 -0.04 C12.64 -1.04, 25.48 0.32, 34.73 -0.09 M37.19 -1.59 C34.07 10.35, 34.93 20.04, 35.6 37.4 M36.19 0.82 C34.7 6.51, 35.74 14.48, 34.58 35.73 M35.05 35.71 C19.96 36.63, 7.29 37.51, -0.4 33.62 M35.33 35.31 C26.31 35.42, 18.5 35.89, -0.89 34.55 M1.85 36.37 C1.11 22.03, 0.24 8.62, 1.34 1.61 M-0.36 34.87 C0.29 24.35, -0.21 14.65, 0.61 -0.61" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(367.8650612985193 392.3361911076265) rotate(0 17.5 4.5)"><path d="M-1.01 1.33 L33.75 1.46 L33.95 8.17 L1.1 7.74" stroke="none" stroke-width="0" fill="#000"></path><path d="M-0.35 -2 C13.48 -0.6, 24.68 -1.03, 35.08 1.14 M-0.04 -0.02 C7.69 -0.19, 17.48 -1.1, 34.91 0.05 M34.28 -0.59 C34.8 2.2, 34.28 4.86, 35.86 8.17 M35.37 -0.3 C34.56 2.54, 34.82 4.33, 35.1 9.44 M35.21 7.62 C24.64 9.03, 14.32 8.52, -1.88 8.05 M34.81 8.78 C21.27 9.88, 7.77 8.05, -0.95 9.68 M0.39 9.37 C-0.22 7, -0.37 5.64, 0.73 -0.02 M-0.28 9.26 C-0.31 5.6, 0.09 2.41, -0.28 0.21" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(375.3650612985193 383.8361911076265) rotate(0 3 7)"><path d="M1.33 -1.25 L7.46 -1.05 L5.17 15.1 L-1.26 12.71" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M-0.6 0.04 C1.4 -0.59, 2.93 0.04, 6.34 -0.36 M-0.01 -0.22 C2.36 0.19, 4.41 -0.02, 6.02 -0.1 M5.08 -0.34 C5.8 3.42, 6.19 10.47, 4.71 13.49 M5.53 -0.6 C6.36 3.16, 5.45 6.86, 6.69 14.14 M5.59 14.26 C3.76 14.53, 1.34 13.44, -0.28 14.6 M5.93 14.07 C5.03 14.2, 3.24 13.7, 0.21 13.76 M0.58 13.72 C-0.16 8.67, 0.81 5.49, -0.03 1.14 M0.41 13.59 C0.11 9.37, 0.48 3.91, 0.33 -0.35" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(390.3650612985193 383.8361911076265) rotate(0 3 7)"><path d="M-1.25 1.46 L4.95 -0.83 L7.1 12.74 L-1.29 14.58" stroke="none" stroke-width="0" fill="#868e96"></path><path d="M0.04 -0.26 C0.72 -0.33, 2.67 0.4, 5.64 0.52 M-0.22 0.04 C2.56 -0.23, 4.71 0.02, 5.9 0.08 M5.66 -0.32 C4.73 5.45, 7.22 9.47, 5.49 13.15 M5.4 0.26 C5.93 4.52, 6.28 11.02, 6.14 14.55 M6.26 14.03 C4.85 13.84, 2.11 13.67, 0.6 13.45 M6.07 14.28 C4.52 13.72, 2.32 14.19, -0.24 14.13 M-0.28 13.97 C-0.57 9.44, 1.07 2.97, 1.14 0.63 M-0.41 14.06 C0.28 10.73, -0.34 6.93, -0.35 0.47" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(383.8650612985193 400.3361911076265) rotate(0 0.6422718642013479 13.197904566302896)"><path d="M-0.88 -1.99 C-1.43 7.57, 1.15 13.78, 2.24 28.39 M0.14 0.63 C-0.63 7.75, 0.34 15.35, 0.76 27.34" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(374.3650612985193 399.3361911076265) rotate(0 0.19790456630289555 13.301320007070899)"><path d="M-1.99 -1.31 C0.2 6.74, -0.97 12.94, 2.39 27.91 M0.63 -0.77 C-0.14 8.39, -0.23 16.93, 1.34 27.2" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(393.8650612985193 400.8361911076265) rotate(0 0.2720004984978459 12.683548028115183)"><path d="M-1.31 0.08 C1.49 6.67, 2.55 17.58, 1.41 25.45 M-0.77 -0.09 C0.43 5.46, 0.98 12.41, 0.7 24.84" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(402.8650612985193 408.8361911076265) rotate(0 -17.287554731126875 -0.13689562075796857)"><path d="M0.08 1.14 C-9.9 1.52, -15.53 -0.31, -34.05 -1.46 M-0.09 0.05 C-10.99 0, -20.5 -1.06, -34.66 -0.25" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(402.3650612985193 417.8361911076265) rotate(0 -18.16205287910998 0.04547248221933842)"><path d="M1.14 -1.19 C-5.48 2.11, -14.51 0.37, -37.46 1.28 M0.05 -0.32 C-12.82 1.18, -26.96 0.04, -36.25 0.77" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round" transform="translate(340.7275521017252 468.94674259140356) rotate(0 68.33333333333326 18.888905843098996)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.26 6.4 C1.09 4.84, 2.44 3.29, 4.99 0.36 M-0.26 6.4 C1.46 4.42, 3.17 2.45, 4.99 0.36 M0.13 12.04 C2.6 9.2, 5.07 6.36, 10.63 -0.03 M0.13 12.04 C2.36 9.48, 4.59 6.92, 10.63 -0.03 M-0.13 18.44 C3.98 13.71, 8.09 8.98, 15.62 0.33 M-0.13 18.44 C3.41 14.36, 6.95 10.29, 15.62 0.33 M0.27 24.08 C6 17.49, 11.72 10.9, 21.26 -0.07 M0.27 24.08 C7.46 15.81, 14.65 7.53, 21.26 -0.07 M0 30.48 C5.48 24.19, 10.95 17.89, 26.25 0.29 M0 30.48 C9.05 20.07, 18.1 9.66, 26.25 0.29 M-0.26 36.88 C8.64 26.64, 17.54 16.41, 31.89 -0.1 M-0.26 36.88 C12.31 22.42, 24.88 7.96, 31.89 -0.1 M2.11 40.26 C13.59 27.05, 25.06 13.85, 36.88 0.26 M2.11 40.26 C12.5 28.3, 22.9 16.34, 36.88 0.26 M7.75 39.86 C19.43 26.42, 31.11 12.99, 42.52 -0.14 M7.75 39.86 C18.98 26.94, 30.22 14.01, 42.52 -0.14 M12.74 40.22 C24.86 26.28, 36.98 12.33, 47.51 0.22 M12.74 40.22 C20.81 30.93, 28.89 21.64, 47.51 0.22 M18.38 39.83 C31.65 24.56, 44.91 9.3, 53.15 -0.17 M18.38 39.83 C28.39 28.31, 38.41 16.79, 53.15 -0.17 M23.37 40.19 C36.54 25.03, 49.71 9.88, 58.14 0.19 M23.37 40.19 C34.58 27.29, 45.8 14.38, 58.14 0.19 M29.01 39.79 C38.99 28.31, 48.97 16.82, 63.78 -0.21 M29.01 39.79 C40.04 27.1, 51.07 14.42, 63.78 -0.21 M34 40.15 C41.91 31.05, 49.83 21.94, 68.77 0.15 M34 40.15 C43.74 28.94, 53.49 17.73, 68.77 0.15 M39.64 39.76 C52.44 25.03, 65.24 10.31, 74.41 -0.24 M39.64 39.76 C47.47 30.75, 55.31 21.73, 74.41 -0.24 M44.63 40.12 C54.86 28.34, 65.1 16.57, 79.4 0.12 M44.63 40.12 C53.28 30.16, 61.94 20.2, 79.4 0.12 M50.27 39.72 C60.09 28.43, 69.91 17.13, 85.04 -0.28 M50.27 39.72 C61.03 27.34, 71.8 14.96, 85.04 -0.28 M55.26 40.08 C64.66 29.27, 74.06 18.46, 90.03 0.08 M55.26 40.08 C65.85 27.89, 76.45 15.7, 90.03 0.08 M60.9 39.69 C72.37 26.49, 83.85 13.29, 95.67 -0.31 M60.9 39.69 C69.59 29.69, 78.29 19.68, 95.67 -0.31 M65.89 40.05 C74.2 30.49, 82.51 20.92, 100.66 0.05 M65.89 40.05 C78.29 25.78, 90.69 11.52, 100.66 0.05 M71.53 39.65 C83.8 25.54, 96.08 11.42, 106.3 -0.34 M71.53 39.65 C78.82 31.27, 86.11 22.88, 106.3 -0.34 M76.52 40.01 C89.61 24.96, 102.7 9.9, 111.29 0.02 M76.52 40.01 C85.09 30.16, 93.66 20.3, 111.29 0.02 M82.16 39.62 C90.2 30.37, 98.24 21.12, 116.27 0.38 M82.16 39.62 C93.56 26.5, 104.97 13.38, 116.27 0.38 M87.15 39.98 C95.85 29.97, 104.55 19.96, 121.92 -0.02 M87.15 39.98 C95.8 30.03, 104.45 20.07, 121.92 -0.02 M92.79 39.59 C99.78 31.55, 106.76 23.51, 126.91 0.34 M92.79 39.59 C101.4 29.69, 110 19.78, 126.91 0.34 M97.78 39.95 C108.93 27.11, 120.09 14.28, 132.55 -0.05 M97.78 39.95 C110.68 25.1, 123.59 10.25, 132.55 -0.05 M103.42 39.55 C114.64 26.64, 125.86 13.73, 137.54 0.31 M103.42 39.55 C116.21 24.83, 129.01 10.11, 137.54 0.31 M108.41 39.91 C115.34 31.93, 122.28 23.95, 137.27 6.7 M108.41 39.91 C115.74 31.48, 123.06 23.05, 137.27 6.7 M114.05 39.52 C119.08 33.73, 124.11 27.95, 137.01 13.1 M114.05 39.52 C123.16 29.04, 132.27 18.56, 137.01 13.1 M119.04 39.88 C125.53 32.41, 132.02 24.95, 137.41 18.74 M119.04 39.88 C123.99 34.17, 128.95 28.47, 137.41 18.74 M124.02 40.24 C128.33 35.28, 132.63 30.33, 137.15 25.14 M124.02 40.24 C129.2 34.28, 134.37 28.33, 137.15 25.14 M129.67 39.84 C132.79 36.25, 135.91 32.66, 137.54 30.79 M129.67 39.84 C131.44 37.8, 133.21 35.77, 137.54 30.79" stroke="#82c91e" stroke-width="0.5" fill="none"></path><path d="M0 0 C49.47 0, 98.93 0, 136.67 0 M0 0 C42.29 0, 84.59 0, 136.67 0 M136.67 0 C136.67 8.33, 136.67 16.66, 136.67 37.78 M136.67 0 C136.67 8.06, 136.67 16.12, 136.67 37.78 M136.67 37.78 C93.85 37.78, 51.03 37.78, 0 37.78 M136.67 37.78 C88.21 37.78, 39.74 37.78, 0 37.78 M0 37.78 C0 24.21, 0 10.64, 0 0 M0 37.78 C0 27.5, 0 17.22, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(372.94974041552723 475.50226423853906) rotate(0 37.5 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">continue</text></g><g stroke-linecap="round" transform="translate(388.95413517850125 333.70723212274766) rotate(0 42.96295166015625 15.66137767973396)"><path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.26 6.4 C1.33 4.57, 2.92 2.74, 4.99 0.36 M-0.26 6.4 C1.67 4.17, 3.61 1.95, 4.99 0.36 M0.13 12.04 C4.31 7.24, 8.49 2.43, 10.63 -0.03 M0.13 12.04 C4.25 7.31, 8.36 2.58, 10.63 -0.03 M-0.13 18.44 C4.4 13.23, 8.93 8.02, 15.62 0.33 M-0.13 18.44 C6.09 11.29, 12.3 4.14, 15.62 0.33 M0.27 24.08 C6.1 17.37, 11.93 10.66, 21.26 -0.07 M0.27 24.08 C8.03 15.15, 15.79 6.22, 21.26 -0.07 M0 30.48 C7.44 21.92, 14.88 13.36, 26.25 0.29 M0 30.48 C9.23 19.87, 18.45 9.26, 26.25 0.29 M2.37 33.86 C8.28 27.06, 14.18 20.27, 31.89 -0.1 M2.37 33.86 C11.1 23.81, 19.83 13.77, 31.89 -0.1 M8.01 33.46 C18.87 20.98, 29.72 8.49, 36.88 0.26 M8.01 33.46 C15.44 24.92, 22.87 16.37, 36.88 0.26 M13 33.82 C24.34 20.78, 35.68 7.73, 42.52 -0.14 M13 33.82 C20.91 24.72, 28.83 15.61, 42.52 -0.14 M18.64 33.43 C27.12 23.68, 35.59 13.93, 47.51 0.22 M18.64 33.43 C27.73 22.98, 36.81 12.53, 47.51 0.22 M23.63 33.79 C30.77 25.58, 37.9 17.37, 53.15 -0.17 M23.63 33.79 C30.05 26.4, 36.47 19.01, 53.15 -0.17 M29.27 33.39 C38.11 23.22, 46.96 13.05, 58.14 0.19 M29.27 33.39 C35.28 26.48, 41.29 19.57, 58.14 0.19 M34.26 33.75 C42.43 24.35, 50.6 14.95, 63.78 -0.21 M34.26 33.75 C40.39 26.7, 46.52 19.64, 63.78 -0.21 M39.9 33.36 C49.33 22.51, 58.76 11.66, 68.77 0.15 M39.9 33.36 C45.83 26.54, 51.75 19.73, 68.77 0.15 M44.89 33.72 C56.26 20.63, 67.64 7.55, 74.41 -0.24 M44.89 33.72 C54.79 22.33, 64.69 10.95, 74.41 -0.24 M50.53 33.33 C56.58 26.37, 62.62 19.42, 79.4 0.12 M50.53 33.33 C57.93 24.81, 65.34 16.29, 79.4 0.12 M55.52 33.69 C66.01 21.61, 76.51 9.54, 85.04 -0.28 M55.52 33.69 C62.29 25.89, 69.07 18.1, 85.04 -0.28 M61.16 33.29 C67.92 25.51, 74.68 17.74, 88.06 2.35 M61.16 33.29 C69.1 24.16, 77.03 15.03, 88.06 2.35 M66.15 33.65 C74.36 24.21, 82.56 14.77, 88.45 7.99 M66.15 33.65 C74.01 24.61, 81.87 15.57, 88.45 7.99 M71.79 33.26 C76.57 27.76, 81.35 22.26, 88.19 14.39 M71.79 33.26 C77.32 26.9, 82.85 20.54, 88.19 14.39 M76.78 33.62 C80.26 29.61, 83.75 25.6, 88.59 20.03 M76.78 33.62 C80.31 29.55, 83.84 25.49, 88.59 20.03 M82.42 33.22 C83.67 31.79, 84.91 30.35, 88.33 26.43 M82.42 33.22 C84.67 30.63, 86.92 28.04, 88.33 26.43" stroke="#4c6ef5" stroke-width="0.5" fill="none"></path><path d="M0 0 C34.37 0, 68.73 0, 85.93 0 M0 0 C30.8 0, 61.6 0, 85.93 0 M85.93 0 C85.93 7.3, 85.93 14.59, 85.93 31.32 M85.93 0 C85.93 10.75, 85.93 21.51, 85.93 31.32 M85.93 31.32 C64.61 31.32, 43.3 31.32, 0 31.32 M85.93 31.32 C64.26 31.32, 42.59 31.32, 0 31.32 M0 31.32 C0 19.26, 0 7.2, 0 0 M0 31.32 C0 20.81, 0 10.29, 0 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(396.7054353242104 338.1304988211914) rotate(0 35 11.5)"><text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">copy url</text></g><g transform="translate(78.68102157642033 106.96052345493217) rotate(0 20 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">from</text></g><g transform="translate(211.63556703096583 97.86961436402316) rotate(0 71.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">video promotion</text></g></svg> \ No newline at end of file
diff --git a/cf/captcha-payment.txt b/cf/captcha-payment.txt
index 4e24ffd9..7fa02142 100644
--- a/cf/captcha-payment.txt
+++ b/cf/captcha-payment.txt
@@ -6,7 +6,7 @@ Overview:
In many IP reputation systems, CAPTCHAS are used to separate humans from
bots, allowing humans to access resources while blocking automated
attackers. However, especially complex modern Web sites require many
- resources and thus can triger a large number of CAPTCHAS, limiting
+ resources and thus can trigger a large number of CAPTCHAS, limiting
usability. The problem is compounded if users access the Web via
Tor or VPNs, as this means mechanisms to track users cannot be used to
reduce the number of CAPTCHAS.
diff --git a/checklist-demo-upgrade.rst b/checklist-demo-upgrade.rst
index 5eb00513..7ff892f4 100644
--- a/checklist-demo-upgrade.rst
+++ b/checklist-demo-upgrade.rst
@@ -8,7 +8,6 @@ GNU Taler Demo Upgrade Checklist
Post-upgrade checks:
-- |check| Run ``taler-deployment-arm -I`` to verify that all services are running.
- |check| Run the headless wallet to check that services are actually working:
.. code-block:: console
@@ -23,15 +22,25 @@ Post-upgrade checks:
Basics:
- |check| Visit https://demo.taler.net/ to see if the landing page is displayed correctly
+- |check| landing language switcher
- |check| Visit the wallet installation page, install the wallet, and see if the presence
indicator is updated correctly.
-- |check| Visit https://bank.demo.taler.net/, register a new user and withdraw coins into the
- browser wallet.
+- |check| Visit https://bank.demo.taler.net/, register a new user
+- |check| bank language switcher
+- |check| bank logout
+- |check| bank login
+- |check| bank-integrated withdraw process, abort in bank
+- |check| transaction history: delete pending withdraw
+- |check| do bank-integrated withdraw process (5 KUDOS)
+- |check| do wallet-initiated withdraw process (5 KUDOS)
+- |check| withdraw process of large amount (20 KUDOS) runs into KYC check
Blog demo:
-- |check| Visit https://shop.demo.taler.net/ and purchase an article.
+- |check| Visit https://shop.demo.taler.net/
+- |check| blog page article list renders
+- |check| payment for blog article
- |check| Verify that the balance in the wallet was updated correctly.
- |check| Go back to https://shop.demo.taler.net/ and click on the same article
link. Verify that the article is shown and **no** repeated payment is
@@ -41,16 +50,53 @@ Blog demo:
- |check| Delete cookies on https://shop.demo.taler.net/ and click on the same article again.
Verify that the wallet detects that the article has already purchased and successfully
redirects to the article without spending more money.
+- |check| payment for other blog article
+- |check| refund of 2nd blog article (button at the end)
+- |check| wallet transaction history rendering
+- |check| delete history entry
Donation demo:
-- |check| Make a donation on https://donations.demo.taler.net
+- |check| Reset wallet
+- |check| Withdraw age-restricted coins (< 14)
+- |check| Try to make a donation on https://donations.demo.taler.net/, fail due to age-restriction
+- |check| Withdraw age-restricted coins (>= 14)
+- |check| Make a donation on https://donations.demo.taler.net/
- |check| Make another donation with the same parameters and verify
that the payment is requested again, instead of showing the previous
fulfillment page.
+Merchant SPA:
+
+- |check| test SPA loads
+- |check| try to login with wrong password
+- |check| try to login with correct password
+- |check| create instance
+- |check| modify instance
+- |check| add product
+- |check| add order with inventory product
+- |check| pay for order with wallet
+- |check| trigger refund
+- |check| accept refund with wallet
+- |check| TBD: tipping
+- |check| TBD: products with previews
+- |check| TBD: inventory management
+- |check| TBD: adding transactions
+- |check| TBD: test various settings
+- |check| TBD: ...
+
Survey/Tipping:
- |check| Visit https://survey.demo.taler.net/ and receive a tip.
- |check| Verify that the survey stats page (https://survey.demo.taler.net/survey-stats) is working,
and that the survey reserve has sufficient funds.
+
+P2P payments:
+
+- |check| generating push payment (to self is OK)
+- |check| accepting push payment (from self is OK)
+- |check| generating pull payment (to self is OK)
+- |check| accepting pull payment (from self is OK)
+- |check| sending money back from wallet to bank account
+- |check| wallet transaction history rendering
+- |check| delete history entry
diff --git a/checklist-release.rst b/checklist-release.rst
index 90ca6cb6..33bfd614 100644
--- a/checklist-release.rst
+++ b/checklist-release.rst
@@ -1,70 +1,142 @@
-###########################
-GNU Taler Release Checklist
-###########################
+############################
+GNU Taler Release Checklists
+############################
+.. |check| raw:: html
-Release checklists for GNU Taler:
-
-Wallet:
-
-- [ ] build wallet
-- [ ] verify wallet works against 'test.taler.net'
-- [ ] tag repo.
-- [ ] upgrade 'demo.taler.net' to 'test.taler.net'
-- [ ] upload new wallet release to app store
-- [ ] Update bug tracker (mark release, resolved -> closed)
-- [ ] Send announcement to taler@gnu.org
-- [ ] Send announcement to info-gnu@gnu.org (major releases only)
-- [ ] Send announcement to coordinator@translationproject.org
+ <input type="checkbox">
For exchange:
-- [ ] check no compiler warnings at "-Wall"
-- [ ] ensure Coverity static analysis passes
-- [ ] make check.
-- [ ] upgrade 'demo.taler.net' to 'test.taler.net'
-- [ ] make dist, make check on result of 'make dist'.
-- [ ] Change version number in configure.ac.
-- [ ] make dist for release.
-- [ ] tag repo.
-- [ ] Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
-- [ ] Update bug tracker (mark release, resolved -> closed)
-- [ ] Send announcement to taler@gnu.org
-- [ ] Send announcement to info-gnu@gnu.org (major releases only)
-- [ ] Send announcement to coordinator@translationproject.org
+- |check| no compiler warnings at "-Wall" with gcc
+- |check| no compiler warnings at "-Wall" with clang
+- |check| ensure Coverity static analysis passes
+- |check| make check.
+- |check| make dist, make check on result of 'make dist'.
+- |check| Change version number in configure.ac.
+- |check| update man pages / info page documentation (prebuilt branch)
+- |check| make dist for release
+- |check| verify dist builds from source
+- |check| upgrade 'demo.taler.net'
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| tag repo.
+- |check| use deployment.git/packaging/*-docker/ to build Debian and Ubuntu packages
+- |check| upload packages to deb.taler.net/ (note: only Florian/Christian can sign)
+- |check| change 'demo.taler.net' deployment to use new tag.
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
For merchant (C backend):
-- [ ] check no compiler warnings at "-Wall"
-- [ ] ensure Coverity static analysis passes
-- [ ] make check.
-- [ ] upgrade 'demo.taler.net' to 'test.taler.net'
-- [ ] make dist, make check on result of 'make dist'.
-- [ ] Change version number in configure.ac.
-- [ ] make dist for release.
-- [ ] tag repo.
-- [ ] Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
-- [ ] Update bug tracker (mark release, resolved -> closed)
-- [ ] Send announcement to taler@gnu.org
-- [ ] Send announcement to info-gnu@gnu.org (major releases only)
-- [ ] Send announcement to coordinator@translationproject.org
+- |check| no compiler warnings at "-Wall" with gcc
+- |check| no compiler warnings at "-Wall" with clang
+- |check| ensure Coverity static analysis passes
+- |check| make check.
+- |check| make dist, make check on result of 'make dist'.
+- |check| update SPA (prebuilt branch)
+- |check| Change version number in configure.ac.
+- |check| make dist for release.
+- |check| verify dist builds from source
+- |check| upgrade 'demo.taler.net'
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| tag repo.
+- |check| use deployment.git/packaging/*-docker/ to build Debian and Ubuntu packages
+- |check| upload packages to deb.taler.net/ (note: only Florian/Christian can sign)
+- |check| change 'demo.taler.net' deployment to use new tag.
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
+
+For sync:
+
+- |check| no compiler warnings at "-Wall" with gcc
+- |check| no compiler warnings at "-Wall" with clang
+- |check| ensure Coverity static analysis passes
+- |check| make check.
+- |check| make dist, make check on result of 'make dist'.
+- |check| Change version number in configure.ac.
+- |check| make dist for release
+- |check| verify dist builds from source
+- |check| upgrade 'demo.taler.net'
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| tag repo.
+- |check| use deployment.git/packaging/*-docker/ to build Debian and Ubuntu packages
+- |check| upload packages to deb.taler.net/ (note: only Florian/Christian can sign)
+- |check| change 'demo.taler.net' deployment to use new tag.
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
+
+For taler-mdb:
+
+- |check| no compiler warnings at "-Wall" with gcc
+- |check| ensure Coverity static analysis passes
+- |check| Change version number in configure.ac.
+- |check| make dist for release.
+- |check| tag repo.
+- |check| use deployment.git/packaging/*-docker/ to build Debian and Ubuntu packages
+- |check| upload packages to deb.taler.net/ (note: only Florian/Christian can sign)
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
+
+For taler-twister:
+
+- |check| no compiler warnings at "-Wall" with gcc
+- |check| no compiler warnings at "-Wall" with clang
+- |check| ensure Coverity static analysis passes
+- |check| make check.
+- |check| make dist, make check on result of 'make dist'.
+- |check| Change version number in configure.ac.
+- |check| make dist for release.
+- |check| verify dist builds from source
+- |check| upgrade 'demo.taler.net'
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| tag repo.
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
+
+For libeufin:
+
+- |check| update SPA of bank
+- |check| build libeufin
+- |check| upgrade 'demo.taler.net'
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| make dist for release.
+- |check| verify dist builds from source
+- |check| tag repo.
+- |check| use deployment.git/packaging/*-docker/ to build Debian and Ubuntu packages
+- |check| upload packages to deb.taler.net/ (note: only Florian/Christian can sign)
+- |check| change 'demo.taler.net' deployment to use new tag.
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
-For bank:
+For Python merchant frontend:
-- TBD
+- |check| upgrade 'demo.taler.net'
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| change 'demo.taler.net' deployment to use new tag.
-For Python merchant frontend:
+Wallet-core:
-- TBD
+- |check| build wallet
+- |check| run integration test
+- |check| make dist for release.
+- |check| verify dist builds from source
+- |check| tag repo.
+- |check| use deployment.git/packaging/*-docker/ to build Debian and Ubuntu packages
+- |check| upload packages to deb.taler.net/ (note: only Florian/Christian can sign)
+- |check| change 'demo.taler.net' deployment to use new tag.
+- |check| Upload triplet to ftp-upload.gnu.org/incoming/ftp or /incoming/alpha
-For PHP merchant frontend:
+Android-Wallet:
-- TBD
+- |check| build wallet
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| tag repo.
+- |check| upload new wallet release to app store
-For auditor:
+Webextension-Wallet:
-- TBD
+- |check| build wallet
+- |check| run :doc:`demo upgrade checklist <checklist-demo-upgrade>`
+- |check| tag repo.
+- |check| upload new wallet release to app store
-For libebics:
+Release announcement:
-- TBD
+- |check| Update bug tracker (mark release, resolved -> closed)
+- |check| Send announcement to taler@gnu.org
+- |check| Send announcement to info-gnu@gnu.org (major releases only)
+- |check| Send announcement to coordinator@translationproject.org
diff --git a/conf.py b/conf.py
index f7aa52e1..b4e2927f 100644
--- a/conf.py
+++ b/conf.py
@@ -35,61 +35,61 @@
import sys
import os
-sys.path.append(os.path.abspath('_exts'))
+sys.path.append(os.path.abspath("_exts"))
import taler_sphinx_theme
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '2.2.0'
+needs_sphinx = "2.2.0"
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'ebicsdomain',
- 'typescriptdomain',
- 'taler_sphinx_theme',
- 'sphinx.ext.todo',
- 'sphinx.ext.imgmath',
- 'httpdomain.httpdomain',
- 'recommonmark',
- 'sphinx.ext.graphviz',
+ "ebicsdomain",
+ "typescriptdomain",
+ "taler_sphinx_theme",
+ "sphinx.ext.todo",
+ "sphinx.ext.imgmath",
+ "httpdomain.httpdomain",
+ "recommonmark",
+ "sphinx.ext.graphviz",
]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
source_suffix = {
- '.rst': 'restructuredtext',
- '.txt': 'markdown',
- '.md': 'markdown',
+ ".rst": "restructuredtext",
+ ".txt": "markdown",
+ ".md": "markdown",
}
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'GNU Taler'
-copyright = u'2014-2021 Taler Systems SA (GPLv3+ or GFDL 1.3+)'
+project = "GNU Taler"
+copyright = "2014-2022 Taler Systems SA (GPLv3+ or GFDL 1.3+)"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '0.8'
+version = "0.9"
# The full version, including alpha/beta/rc tags.
-release = '0.8.2'
+release = "0.9.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -97,46 +97,53 @@ release = '0.8.2'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ['_build', '_exts', 'cf', 'prebuilt', '**/README.md']
+exclude_patterns = [
+ "_build",
+ "_exts",
+ "cf",
+ "prebuilt",
+ "**/README.md",
+ "extract-tsdefs",
+]
# The reST default role (used for this markup: `text`) to use for all
# documents.
default_role = "ts:type"
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'taler_sphinx_theme'
+html_theme = "taler_sphinx_theme"
html_theme_path = taler_sphinx_theme.html_theme_path()
-html_sidebars = {'**': ['logo-text.html', 'globaltoc.html', 'searchbox.html']}
+html_sidebars = {"**": ["logo-text.html", "globaltoc.html", "searchbox.html"]}
html_theme_options = {
# Set the name of the project to appear in the sidebar
@@ -146,84 +153,82 @@ html_theme_options = {
}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-#html_favicon = None
+# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
-
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
-
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
@@ -232,123 +237,359 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- ('taler-auditor-manual', 'taler-auditor-manual.tex',
- 'GNU Taler Auditor Manual', 'GNU Taler team', 'manual'),
- ('taler-exchange-manual', 'taler-exchange-manual.tex',
- 'GNU Taler Exchange Manual', 'GNU Taler team', 'manual'),
- ('taler-merchant-manual', 'taler-merchant-manual.tex',
- 'GNU Taler Merchant Manual', 'GNU Taler team', 'manual'),
- ('taler-merchant-pos-terminal', 'taler-merchant-pos-terminal.tex',
- 'GNU Taler Merchant POS Terminal', 'GNU Taler team', 'manual'),
- ('taler-merchant-api-tutorial', 'taler-merchant-api-tutorial.tex',
- 'GNU Taler Merchant API Tutorial', 'GNU Taler team', 'manual'),
- ('taler-bank-manual', 'taler-bank-manual.tex', 'GNU Taler Bank Manual',
- 'GNU Taler team', 'manual'),
- ('taler-backoffice-manual', 'taler-backoffice-manual.tex',
- 'GNU Taler Back Office Manual', 'GNU Taler team', 'manual'),
- ('taler-developer-manual', 'taler-developer-manual.tex', 'GNU Taler Developer Manual',
- 'GNU Taler team', 'manual'),
+ (
+ "taler-auditor-manual",
+ "taler-auditor-manual.tex",
+ "GNU Taler Auditor Manual",
+ "GNU Taler team",
+ "manual",
+ ),
+ (
+ "taler-exchange-manual",
+ "taler-exchange-manual.tex",
+ "GNU Taler Exchange Manual",
+ "GNU Taler team",
+ "manual",
+ ),
+ (
+ "taler-merchant-manual",
+ "taler-merchant-manual.tex",
+ "GNU Taler Merchant Manual",
+ "GNU Taler team",
+ "manual",
+ ),
+ (
+ "taler-merchant-pos-terminal",
+ "taler-merchant-pos-terminal.tex",
+ "GNU Taler Merchant POS Terminal",
+ "GNU Taler team",
+ "manual",
+ ),
+ (
+ "taler-merchant-api-tutorial",
+ "taler-merchant-api-tutorial.tex",
+ "GNU Taler Merchant API Tutorial",
+ "GNU Taler team",
+ "manual",
+ ),
+ (
+ "taler-developer-manual",
+ "taler-developer-manual.tex",
+ "GNU Taler Developer Manual",
+ "GNU Taler team",
+ "manual",
+ ),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
-#latex_logo = None
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
latex_appendices = ["fdl-1.3"]
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ("manpages/taler-auditor-exchange.1", "taler-auditor-exchange",
- "add or remove exchange from auditor’s list", "GNU Taler contributors",
- 1),
- ("manpages/taler-auditor-dbinit.1", "taler-auditor-dbinit",
- "setup auditor database", "GNU Taler contributors", 1),
- ("manpages/taler-auditor-sync.1", "taler-auditor-sync",
- "tool to safely synchronize auditor database", "GNU Taler contributors", 1),
- ("manpages/taler-auditor-httpd.1", "taler-auditor-httpd",
- "HTTP server providing a RESTful API to access a Taler auditor",
- "GNU Taler contributors", 1),
- ("manpages/taler-auditor.1", "taler-auditor", "audit exchange",
- "GNU Taler contributors", 1),
- ("manpages/taler-exchange-aggregator.1", "taler-exchange-aggregator",
- "aggregate deposits into wire transfers", "GNU Taler contributors",
- 1),
- ("manpages/taler-exchange-closer.1", "taler-exchange-closer",
- "close idle reserves", "GNU Taler contributors",
- 1),
- ("manpages/taler-exchange-transfer.1", "taler-exchange-transfer",
- "execute wire transfers", "GNU Taler contributors",
- 1),
- ("manpages/taler-exchange-benchmark.1", "taler-exchange-benchmark",
- "measure exchange performance", "GNU Taler contributors", 1),
- ("manpages/taler-exchange-dbinit.1", "taler-exchange-dbinit",
- "initialize Taler exchange database", "GNU Taler contributors", 1),
- ("manpages/taler-exchange-httpd.1", "taler-exchange-httpd",
- "run Taler exchange (with RESTful API)", "GNU Taler contributors", 1),
- ("manpages/taler-auditor-offline.1", "taler-auditor-offline",
- "Taler auditor certifies that it audits a Taler exchange",
- "GNU Taler contributors", 1),
- ("manpages/taler-exchange-offline.1", "taler-exchange-offline",
- "operations using the offline key of a Taler exchange",
- "GNU Taler contributors", 1),
- ("manpages/taler-exchange-wirewatch.1", "taler-exchange-wirewatch",
- "watch for incoming wire transfers", "GNU Taler contributors", 1),
- ("manpages/taler-merchant-benchmark.1", "taler-merchant-benchmark",
- "generate Taler-style benchmarking payments", "GNU Taler contributors",
- 1),
- ("manpages/taler-merchant-dbinit.1", "taler-merchant-dbinit",
- "initialize Taler merchant database", "GNU Taler contributors", 1),
- ("manpages/taler-merchant-httpd.1", "taler-merchant-httpd",
- "run Taler merchant backend (with RESTful API)", "GNU Taler contributors",
- 1),
- ("manpages/taler-merchant-setup-reserve.1", "taler-merchant-setup-reserve",
- "setup reserve for tipping at a Taler merchant backend", "GNU Taler contributors",
- 1),
- ("manpages/taler-exchange-wire-gateway-client.1", "taler-exchange-wire-gateway-client",
- "trigger a transfer at the bank", "GNU Taler contributors", 1),
- ("manpages/taler-config.1", "taler-config", "Taler configuration inspection and editing",
- "GNU Taler contributors", 1),
- ("manpages/taler.conf.5", "taler.conf", "Taler configuration file",
- "GNU Taler contributors", 5),
- ("manpages/taler-exchange-secmod-eddsa.1", "taler-exchange-secmod-eddsa",
- "handle private EDDSA key operations for a Taler exchange",
- "GNU Taler contributors", 1),
- ("manpages/taler-exchange-secmod-rsa.1", "taler-exchange-secmod-rsa",
- "handle private RSA key operations for a Taler exchange",
- "GNU Taler contributors", 1),
- ("manpages/taler-helper-auditor-aggregation.1", "taler-helper-auditor-aggregation",
- "audit Taler exchange aggregation activity", "GNU Taler contributors", 1),
- ("manpages/taler-helper-auditor-coins.1", "taler-helper-auditor-coins",
- "audit Taler coin processing", "GNU Taler contributors", 1),
- ("manpages/taler-helper-auditor-deposits.1", "taler-helper-auditor-deposits",
- "audit Taler exchange database for deposit confirmation consistency",
- "GNU Taler contributors", 1),
- ("manpages/taler-helper-auditor-reserves.1", "taler-helper-auditor-reserves",
- "audit Taler exchange reserve handling", "GNU Taler contributors", 1),
- ("manpages/taler-helper-auditor-wire.1", "taler-helper-auditor-wire",
- "audit exchange database for consistency with the bank's wire transfers",
- "GNU Taler contributors", 1),
+ (
+ "manpages/sync-config.1",
+ "sync-config",
+ "manipulate Sync configuration files",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/sync-dbinit.1",
+ "sync-dbinit",
+ "initialize the Sync database",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/sync-httpd.1",
+ "sync-httpd",
+ "provide the Sync HTTP interface",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/sync.conf.5",
+ "sync.conf",
+ "Sync configuration file",
+ "GNU Taler contributors",
+ 5,
+ ),
+ (
+ "manpages/taler-auditor-exchange.1",
+ "taler-auditor-exchange",
+ "add or remove exchange from auditor’s list",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-auditor-dbinit.1",
+ "taler-auditor-dbinit",
+ "setup auditor database",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-auditor-sync.1",
+ "taler-auditor-sync",
+ "tool to safely synchronize auditor database",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-auditor-httpd.1",
+ "taler-auditor-httpd",
+ "HTTP server providing a RESTful API to access a Taler auditor",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-auditor.1",
+ "taler-auditor",
+ "audit exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-aggregator.1",
+ "taler-exchange-aggregator",
+ "aggregate deposits into wire transfers",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-closer.1",
+ "taler-exchange-closer",
+ "close idle reserves",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-drain.1",
+ "taler-exchange-drain",
+ "drain profits from exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-kyc-tester.1",
+ "taler-exchange-kyc-tester",
+ "test KYC service integration",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-expire.1",
+ "taler-exchange-expire",
+ "refund expired purses",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-router.1",
+ "taler-exchange-router",
+ "route payments to partner exchanges",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-transfer.1",
+ "taler-exchange-transfer",
+ "execute wire transfers",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-benchmark.1",
+ "taler-exchange-benchmark",
+ "measure exchange performance",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-dbinit.1",
+ "taler-exchange-dbinit",
+ "initialize Taler exchange database",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-httpd.1",
+ "taler-exchange-httpd",
+ "run Taler exchange (with RESTful API)",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-auditor-offline.1",
+ "taler-auditor-offline",
+ "Taler auditor certifies that it audits a Taler exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-offline.1",
+ "taler-exchange-offline",
+ "operations using the offline key of a Taler exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-wirewatch.1",
+ "taler-exchange-wirewatch",
+ "watch for incoming wire transfers",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-merchant-benchmark.1",
+ "taler-merchant-benchmark",
+ "generate Taler-style benchmarking payments",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-merchant-dbinit.1",
+ "taler-merchant-dbinit",
+ "initialize Taler merchant database",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-merchant-httpd.1",
+ "taler-merchant-httpd",
+ "run Taler merchant backend (with RESTful API)",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-merchant-setup-reserve.1",
+ "taler-merchant-setup-reserve",
+ "setup reserve for tipping at a Taler merchant backend",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-wire-gateway-client.1",
+ "taler-exchange-wire-gateway-client",
+ "trigger a transfer at the bank",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-config.1",
+ "taler-config",
+ "Taler configuration inspection and editing",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler.conf.5",
+ "taler.conf",
+ "Taler configuration file",
+ "GNU Taler contributors",
+ 5,
+ ),
+ (
+ "manpages/taler-exchange-secmod-eddsa.1",
+ "taler-exchange-secmod-eddsa",
+ "handle private EDDSA key operations for a Taler exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-secmod-cs.1",
+ "taler-exchange-secmod-cs",
+ "handle private CS key operations for a Taler exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-exchange-secmod-rsa.1",
+ "taler-exchange-secmod-rsa",
+ "handle private RSA key operations for a Taler exchange",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-helper-auditor-aggregation.1",
+ "taler-helper-auditor-aggregation",
+ "audit Taler exchange aggregation activity",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-helper-auditor-coins.1",
+ "taler-helper-auditor-coins",
+ "audit Taler coin processing",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-helper-auditor-deposits.1",
+ "taler-helper-auditor-deposits",
+ "audit Taler exchange database for deposit confirmation consistency",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-helper-auditor-purses.1",
+ "taler-helper-auditor-purses",
+ "audit Taler exchange purse handling",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-helper-auditor-reserves.1",
+ "taler-helper-auditor-reserves",
+ "audit Taler exchange reserve handling",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/taler-helper-auditor-wire.1",
+ "taler-helper-auditor-wire",
+ "audit exchange database for consistency with the bank's wire transfers",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/libeufin-sandbox.1",
+ "libeufin-sandbox",
+ "simulate core banking system with EBICS access to bank accounts",
+ "GNU Taler contributors",
+ 1,
+ ),
+ (
+ "manpages/libeufin-nexus.1",
+ "libeufin-nexus",
+ "service to interface to various bank access APIs",
+ "GNU Taler contributors",
+ 1,
+ ),
]
# If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -356,33 +597,83 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- ("taler-auditor-manual", "taler-auditor", "Taler Auditor Manual",
- "GNU Taler team", "MENU ENTRY", "DESCRIPTION", "CATEGORY"),
- ("taler-exchange-manual", "taler-exchange", "Taler Exchange Manual",
- "GNU Taler team", "MENU ENTRY", "DESCRIPTION", "CATEGORY"),
- ("taler-merchant-manual", "taler-merchant", "Taler Merchant Manual",
- "GNU Taler team", "MENU ENTRY", "DESCRIPTION", "CATEGORY"),
- ("taler-merchant-api-tutorial", "taler-merchant-api-tutorial",
- "Taler Merchant API Tutorial", "GNU Taler team", "MENU ENTRY",
- "DESCRIPTION", "CATEGORY"),
- ("taler-bank-manual", "taler-bank", "Taler Bank Manual", "GNU Taler team",
- "MENU ENTRY", "DESCRIPTION", "CATEGORY"),
- ("taler-developer-manual", "taler-developer-manual", "Taler Developer Manual", "GNU Taler team",
- "MENU ENTRY", "DESCRIPTION", "CATEGORY"),
+ (
+ "taler-auditor-manual",
+ "taler-auditor",
+ "Taler Auditor Manual",
+ "GNU Taler team",
+ "MENU ENTRY",
+ "DESCRIPTION",
+ "CATEGORY",
+ ),
+ (
+ "taler-exchange-manual",
+ "taler-exchange",
+ "Taler Exchange Manual",
+ "GNU Taler team",
+ "MENU ENTRY",
+ "DESCRIPTION",
+ "CATEGORY",
+ ),
+ (
+ "taler-merchant-manual",
+ "taler-merchant",
+ "Taler Merchant Manual",
+ "GNU Taler team",
+ "MENU ENTRY",
+ "DESCRIPTION",
+ "CATEGORY",
+ ),
+ (
+ "taler-merchant-api-tutorial",
+ "taler-merchant-api-tutorial",
+ "Taler Merchant API Tutorial",
+ "GNU Taler team",
+ "MENU ENTRY",
+ "DESCRIPTION",
+ "CATEGORY",
+ ),
+ (
+ "taler-developer-manual",
+ "taler-developer-manual",
+ "Taler Developer Manual",
+ "GNU Taler team",
+ "MENU ENTRY",
+ "DESCRIPTION",
+ "CATEGORY",
+ ),
]
# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
# The output format for Graphviz when building HTML files.
# This must be either 'png' or 'svg'; the default is 'png'.
-graphviz_output_format = 'svg'
+graphviz_output_format = "svg"
+
+myst_heading_anchors = 3
+
+myst_enable_extensions = [
+ "amsmath",
+ "colon_fence",
+ "deflist",
+ "dollarmath",
+ "fieldlist",
+ "html_admonition",
+ "html_image",
+ "linkify",
+ "replacements",
+ "smartquotes",
+ "strikethrough",
+ "substitution",
+ "tasklist",
+]
diff --git a/core/api-auditor.rst b/core/api-auditor.rst
index cd0071e5..321d203e 100644
--- a/core/api-auditor.rst
+++ b/core/api-auditor.rst
@@ -162,6 +162,9 @@ paid out first.
// Hash over the contract for which this deposit is made.
h_contract_terms: HashCode;
+ // Hash over the extensions.
+ h_extensions: HashCode;
+
// Hash over the wiring information of the merchant.
h_wire: HashCode;
@@ -172,6 +175,9 @@ paid out first.
// request? Zero if refunds are not allowed.
refund_deadline: Timestamp;
+ // By what time does the exchange have to wire the funds?
+ wire_deadline: Timestamp;
+
// Amount to be deposited, excluding fee. Calculated from the
// amount with fee and the fee from the deposit request.
amount_without_fee: Amount;
diff --git a/core/api-bank-access.rst b/core/api-bank-access.rst
index ddb2ec80..8ac135cd 100644
--- a/core/api-bank-access.rst
+++ b/core/api-bank-access.rst
@@ -30,13 +30,39 @@ to enabling wallets to withdraw with a better user experience ("tight integratio
Accounts and Withdrawals
------------------------
+.. http:get:: ${BANK_API_BASE_URL}/public-accounts
+
+ Show those accounts whose histories are publicly visible. For example,
+ accounts from donation receivers. As such, this request is unauthenticated.
+
+ **Response**
+
+ **Details**
+
+ .. ts:def:: PublicAccountsResponse
+
+ interface PublicAccountsResponse {
+ publicAccounts: PublicAccount[]
+ }
+
+ .. ts:def:: PublicAccount
+
+ interface PublicAccount {
+ iban: string;
+ balance: string;
+ // The account name _and_ the username of the
+ // Sandbox customer that owns such a bank account.
+ accountLabel: string;
+ }
+
The following endpoints require HTTP "Basic" authentication with the account
name and account password, at least in the GNU Taler demo bank implementation.
.. http:get:: ${BANK_API_BASE_URL}/accounts/${account_name}
- Request the current balance of an account.
+ Request the current balance of an account. (New: ) In case of a public bank
+ account, no authentication is required.
**Response**
@@ -50,10 +76,12 @@ name and account password, at least in the GNU Taler demo bank implementation.
amount: Amount;
credit_debit_indicator: "credit" | "debit";
};
+ // payto://-URI of the account. (New)
+ paytoUri: string;
}
-.. http:POST:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals
+.. http:post:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals
Create a withdrawal operation, resulting in a ``taler://withdraw`` URI.
@@ -78,8 +106,10 @@ name and account password, at least in the GNU Taler demo bank implementation.
taler_withdraw_uri: string;
}
+ :http:statuscode:`403 Forbidden`:
+ The operation was rejected due to insufficient funds.
-.. http:GET:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals/${withdrawal_id}
+.. http:get:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals/${withdrawal_id}
Query the status of a withdrawal operation.
@@ -108,13 +138,14 @@ name and account password, at least in the GNU Taler demo bank implementation.
// only non-null if ``selection_done`` is ``true``.
selected_reserve_pub: string | null;
- // Exchange account selected by the exchange,
- // only non-null if ``selection_done`` is ``true``.
+ // Exchange account selected by the wallet, or by the bank
+ // (with the default exchange) in case the wallet did not provide one
+ // through the Integration API.
selected_exchange_account: string | null;
}
-.. http:POST:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals/${withdrawal_id}/abort
+.. http:post:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals/${withdrawal_id}/abort
Abort a withdrawal operation. Has no effect on an already aborted withdrawal operation.
@@ -122,24 +153,112 @@ name and account password, at least in the GNU Taler demo bank implementation.
:http:statuscode:`409 Conflict`: The reserve operation has been confirmed previously and can't be aborted.
-.. http:POST:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals/${withdrawal_id}/confirm
+.. http:post:: ${BANK_API_BASE_URL}/accounts/${account_name}/withdrawals/${withdrawal_id}/confirm
Confirm a withdrawal operation. Has no effect on an already confirmed withdrawal operation.
+ This call is responsible of wiring the funds to the exchange.
**Response**
- :http:statuscode:`200 OK`: The withdrawal operation has been confirmed. The response is an empty JSON object.
- :http:statuscode:`409 Conflict`: The reserve operation has been aborted previously and can't be confirmed.
+ :http:statuscode:`200 OK`:
+ The withdrawal operation has been confirmed. The response is an empty JSON object.
+ :http:statuscode:`409 Conflict`:
+ The withdrawal has been aborted previously and can't be confirmed.
+ :http:statuscode:`422 Unprocessable Entity` (New):
+ The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before.
+
+------------
+Transactions
+------------
+
+.. http:get:: ${BANK_API_BASE_URL}/accounts/${account_name}/transactions
+
+ Retrieve a subset of transactions related to $account_name. Without
+ query parameters, it returns the last 5 transactions.
+
+ **Request**
+
+ :query page: page number (defaults to 1, meaning the page with the latest transactions.)
+ :query size: how many transactions per page, defaults to 5.
+
+ **Response**
+
+ .. ts:def:: BankAccountTransactionsResponse
+
+ interface BankAccountTransactionsResponse {
+ transactions: BankAccountTransactionInfo[];
+ }
+.. http:get:: ${BANK_API_BASE_URL}/accounts/${account_name}/transactions/${transaction_id}
+ **Response**
+ Retrieve the transaction whose identifier is ``transaction_id``,
+ in the following format:
+
+ .. ts:def:: BankAccountTransactionInfo
+
+ interface BankAccountTransactionInfo {
+
+ creditorIban: string;
+ creditorBic: string; // Optional
+ creditorName: string;
+
+ debtorIban: string;
+ debtorBic: string;
+ debtorName: string;
+
+ amount: number;
+ currency: string;
+ subject: string;
+
+ // Transaction unique ID. Matches
+ // $transaction_id from the URI.
+ uid: string;
+ direction: "DBIT" | "CRDT";
+ date: string; // YYYY-MM-DD ending with 'Z'
+ }
+
+
+.. http:post:: ${BANK_API_BASE_URL}/accounts/${account_name}/transactions
+
+ Create a new transaction where the bank account with the label ``account_name`` is **debited**.
+
+ **Request**
+
+ .. ts:def:: BankAccountTransactionCreate
+
+ interface CreateBankAccountTransactionCreate {
+
+ // Address in the Payto format of the wire transfer receiver.
+ // It needs at least the 'message' query string parameter.
+ paytoUri: string;
+
+ // Transaction amount (in the $currency:x.y format), optional.
+ // However, when not given, its value must occupy the 'amount'
+ // query string parameter of the 'payto' field. In case it
+ // is given in both places, the paytoUri's takes the precedence.
+ amount: string;
+ }
+
+ **Response**
+
+ :http:statuscode:`200 OK`:
+ the transaction has been created.
+
+ :http:statuscode:`400 Bad Request`:
+ the request was invalid or the payto://-URI used unacceptable features.
+
+.. http:delete:: ${BANK_API_BASE_URL}/accounts/${account_name}
+
+ Delete the bank account (and the customer entry) from the database.
+ Note, customer usernames and bank accounts have the same value.
----------------------
Registration (Testing)
----------------------
-
-.. http:POST:: ${BANK_API_BASE_URL}/testing/register
+.. http:post:: ${BANK_API_BASE_URL}/testing/register
Create a new bank account. This endpoint should be disabled for most deployments, but is useful
for automated testing / integration tests.
diff --git a/core/api-bank-integration.rst b/core/api-bank-integration.rst
index 5f33f452..5ed98be1 100644
--- a/core/api-bank-integration.rst
+++ b/core/api-bank-integration.rst
@@ -129,6 +129,8 @@ for the withdrawal operation (the ``wopid``) to interact with the withdrawal ope
The response is a `BankWithdrawalOperationPostResponse`.
:http:statuscode:`404 Not found`:
The bank does not know about a withdrawal operation with the specified ``wopid``.
+ :http:statuscode:`409 Conflict` (New):
+ The wallet selected a different exchange or reserve public key under the same withdrawal ID.
**Details**
diff --git a/core/api-common.rst b/core/api-common.rst
index 7ee4769f..ab158e79 100644
--- a/core/api-common.rst
+++ b/core/api-common.rst
@@ -117,7 +117,7 @@ handle the error as if an internal error (500) had been returned.
// Name of the object that was bogus (if applicable).
object?: string;
- // Name of the currency than was problematic (if applicable).
+ // Name of the currency that was problematic (if applicable).
currency?: string;
// Expected type (if applicable).
@@ -222,6 +222,40 @@ hashed data. See `base32`_.
// 32-byte hash code.
type ShortHashCode = string;
+.. ts:def:: WireSalt
+
+ // 16-byte salt.
+ type WireSalt = string;
+
+.. ts:def:: SHA256HashCode
+
+ type SHA256HashCode = ShortHashCode;
+
+.. ts:def:: SHA512HashCode
+
+ type SHA512HashCode = HashCode;
+
+.. ts:def:: CSNonce
+
+ // 32-byte nonce value, must only be used once.
+ type CSNonce = string;
+
+.. ts:def:: RefreshMasterSeed
+
+ // 32-byte nonce value, must only be used once.
+ type RefreshMasterSeed = string;
+
+.. ts:def:: Cs25519Point
+
+ // 32-byte value representing a point on Curve25519.
+ type Cs25519Point = string;
+
+.. ts:def:: Cs25519Scalar
+
+ // 32-byte value representing a scalar multiplier
+ // for scalar operations on points on Curve25519.
+ type Cs25519Scalar = string;
+
Safe Integers
^^^^^^^^^^^^^
@@ -249,18 +283,19 @@ Timestamps are represented by the following structure:
.. ts:def:: Timestamp
interface Timestamp {
- // Milliseconds since epoch, or the special
+ // Seconds since epoch, or the special
// value "never" to represent an event that will
// never happen.
- t_ms: number | "never";
+ t_s: number | "never";
}
.. ts:def:: RelativeTime
- interface Duration {
- // Duration in milliseconds or "forever"
- // to represent an infinite duration.
- d_ms: number | "forever";
+ interface RelativeTime {
+ // Duration in microseconds or "forever"
+ // to represent an infinite duration. Numeric
+ // values are capped at 2^53 - 1 inclusive.
+ d_us: number | "forever";
}
@@ -275,6 +310,37 @@ Integers
// JavaScript numbers restricted to integers.
type Integer = number;
+Ages
+^^^^
+
+.. ts:def:: Age
+
+ // An age is an integer between 0 and 255 measured in years.
+ type Age = number;
+
+Versions
+^^^^^^^^
+
+We use the type ``LibtoolVersion`` in the design documents to refer to a string
+that represents a version with the semantic as defined by
+`libtool <https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html>`__.
+
+.. ts:def:: LibtoolVersion
+
+ // Version information in libtool version format and semantics
+ // current[:revision[:age]], f.e. "1", "2:0" or "3:1:2".
+ // see https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html.
+ type LibtoolVersion = string;
+
+We use the type ``SemVer`` to refer to a string that represents a version with
+the semantic as defined by `semantic versioning <https://semver.org/>`__.
+
+.. ts:def:: SemVer
+
+ // Version information in semantic versioning format and semantics,
+ // like "X.Z.Y", see https://semver.org/.
+ type SemVer = string;
+
Objects
^^^^^^^
@@ -305,6 +371,20 @@ Keys
// converted to Crockford `Base32`.
type EddsaPrivateKey = string;
+.. ts:def:: Edx25519PublicKey
+
+ // Edx25519 public keys are points on Curve25519 and represented using the
+ // standard 256 bits Ed25519 compact format converted to Crockford
+ // `Base32`.
+ type Edx25519PublicKey = string;
+
+.. ts:def:: Edx25519PrivateKey
+
+ // Edx25519 private keys are always points on Curve25519
+ // and represented using the standard 256 bits Ed25519 compact format,
+ // converted to Crockford `Base32`.
+ type Edx25519PrivateKey = string;
+
.. ts:def:: EcdhePublicKey
// EdDSA and ECDHE public keys always point on Curve25519
@@ -312,6 +392,12 @@ Keys
// converted to Crockford `Base32`.
type EcdhePublicKey = string;
+.. ts:def:: CsRPublic
+
+ // Point on Curve25519 represented using the standard 256 bits Ed25519 compact format,
+ // converted to Crockford `Base32`.
+ type CsRPublic = string;
+
.. ts:def:: EcdhePrivateKey
// EdDSA and ECDHE public keys always point on Curve25519
@@ -359,6 +445,12 @@ Signatures
// binary-encoded objects with just the R and S values (base32_ binary-only).
type EddsaSignature = string;
+.. ts:def:: Edx25519Signature
+
+ // Edx25519 signatures are transmitted as 64-bytes `base32`
+ // binary-encoded objects with just the R and S values (base32_ binary-only).
+ type Edx25519Signature = string;
+
.. ts:def:: RsaSignature
// `base32` encoded RSA signature.
@@ -374,6 +466,13 @@ Signatures
// `base32` encoded RSA blinding secret.
type RsaBlindingKeySecret = string;
+.. ts:def:: DenominationBlindingKeySecret
+
+ // Union, not (!) discriminated!
+ // (Note: CS Blinding Key secret is yet to be defined&added here).
+ type DenominationBlindingKeySecret =
+ | RsaBlindingKeySecret;
+
.. _amount:
Amounts
@@ -496,12 +595,58 @@ All elliptic curve operations are on Curve25519. Public and private keys are
thus 32 bytes, and signatures 64 bytes. For hashing, including HKDFs, Taler
uses 512-bit hash codes (64 bytes).
+.. _HashCode:
.. sourcecode:: c
struct GNUNET_HashCode {
uint8_t hash[64]; // usually SHA-512
};
+.. sourcecode:: c
+
+ struct TALER_DenominationHash {
+ struct GNUNET_HashCode hash;
+ };
+
+.. sourcecode:: c
+
+ struct TALER_PrivateContractHash {
+ struct GNUNET_HashCode hash;
+ };
+
+.. sourcecode:: c
+
+ struct TALER_ExtensionsPolicyHash {
+ struct GNUNET_HashCode hash;
+ };
+
+.. sourcecode:: c
+
+ struct TALER_MerchantWireHash {
+ struct GNUNET_HashCode hash;
+ };
+
+.. _PaytoHash:
+.. sourcecode:: c
+
+ struct TALER_PaytoHash {
+ struct GNUNET_ShortHashCode hash;
+ };
+
+.. _BlindedCoinHash:
+.. sourcecode:: c
+
+ struct TALER_BlindedCoinHash {
+ struct GNUNET_HashCode hash;
+ };
+
+.. sourcecode:: c
+
+ struct TALER_CoinPubHash {
+ struct GNUNET_HashCode hash;
+ };
+
+
.. _TALER_EcdhEphemeralPublicKeyP:
.. sourcecode:: c
@@ -682,10 +827,25 @@ within the
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_AmountNBO amount_with_fee;
struct TALER_AmountNBO withdraw_fee;
- struct GNUNET_HashCode h_denomination_pub;
- struct GNUNET_HashCode h_coin_envelope;
+ struct TALER_DenominationHash h_denomination_pub;
+ struct TALER_BlindedCoinHash h_coin_envelope;
+ };
+
+.. _TALER_AgeWithdrawRequestPS:
+.. sourcecode:: c
+
+ struct TALER_AgeWithdrawRequestPS {
+ /**
+ * purpose.purpose = TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_HashCode age_restricted_coins_commitment;
+ struct GNUNET_HashCode h_denoms_h;
+ uint8 max_age_group;
};
+
.. _TALER_DepositRequestPS:
.. sourcecode:: c
@@ -694,15 +854,16 @@ within the
* purpose.purpose = TALER_SIGNATURE_WALLET_COIN_DEPOSIT
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ struct TALER_ExtensionsPolicyHash h_policy;
+ struct TALER_MerchantWireHash h_wire;
+ struct TALER_DenominationHash h_denom_pub;
struct GNUNET_TIME_AbsoluteNBO timestamp;
struct GNUNET_TIME_AbsoluteNBO refund_deadline;
struct TALER_AmountNBO amount_with_fee;
struct TALER_AmountNBO deposit_fee;
struct TALER_MerchantPublicKeyP merchant;
- union TALER_CoinSpendPublicKeyP coin_pub;
};
.. _TALER_DepositConfirmationPS:
@@ -710,11 +871,12 @@ within the
struct TALER_DepositConfirmationPS {
/**
- * purpose.purpose = TALER_SIGNATURE_WALLET_CONFIRM_DEPOSIT
+ * purpose.purpose = TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
+ struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_MerchantWireHash h_wire;
+ struct TALER_ExtensionsPolicyHash h_policy;
struct GNUNET_TIME_AbsoluteNBO timestamp;
struct GNUNET_TIME_AbsoluteNBO refund_deadline;
struct TALER_AmountNBO amount_without_fee;
@@ -730,11 +892,11 @@ within the
* purpose.purpose = TALER_SIGNATURE_WALLET_COIN_MELT
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode session_hash;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_RefreshCommitmentP session_hash;
+ struct TALER_DenominationHash h_denom_pub;
+ struct TALER_AgeCommitmentHash h_age_commitment;
struct TALER_AmountNBO amount_with_fee;
struct TALER_AmountNBO melt_fee;
- union TALER_CoinSpendPublicKeyP coin_pub;
};
.. _TALER_RefreshMeltConfirmationPS:
@@ -745,7 +907,7 @@ within the
* purpose.purpose = TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode session_hash;
+ struct TALER_RefreshCommitmentP session_hash;
uint16_t noreveal_index;
};
@@ -793,7 +955,7 @@ within the
struct TALER_AmountNBO fee_withdraw;
struct TALER_AmountNBO fee_deposit;
struct TALER_AmountNBO fee_refresh;
- struct GNUNET_HashCode denom_hash;
+ struct TALER_DenominationHash denom_hash;
};
.. _TALER_MasterWireDetailsPS:
@@ -804,7 +966,7 @@ within the
* purpose.purpose = TALER_SIGNATURE_MASTER_WIRE_DETAILS
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_wire_details;
+ struct TALER_PaytoHash h_wire_details;
};
.. _TALER_MasterWireFeePS:
@@ -822,6 +984,41 @@ within the
struct TALER_AmountNBO closing_fee;
};
+.. _TALER_GlobalFeesPS:
+.. sourcecode:: c
+
+ struct TALER_MasterWireFeePS {
+ /**
+ * purpose.purpose = TALER_SIGNATURE_MASTER_GLOBAL_FEES
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_TIME_AbsoluteNBO start_date;
+ struct GNUNET_TIME_AbsoluteNBO end_date;
+ struct GNUNET_TIME_RelativeNBO purse_timeout;
+ struct GNUNET_TIME_RelativeNBO kyc_timeout;
+ struct GNUNET_TIME_RelativeNBO history_expiration;
+ struct TALER_AmountNBO history_fee;
+ struct TALER_AmountNBO kyc_fee;
+ struct TALER_AmountNBO account_fee;
+ struct TALER_AmountNBO purse_fee;
+ uint32_t purse_account_limit;
+ };
+
+.. _TALER_MasterDrainProfitPS:
+.. sourcecode:: c
+
+ struct TALER_MasterDrainProfitPS {
+ /**
+ * purpose.purpose = TALER_SIGNATURE_MASTER_DRAIN_PROFITS
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct GNUNET_TIME_AbsoluteNBO date;
+ struct TALER_AmountNBO amount;
+ struct GNUNET_HashCode h_section;
+ struct TALER_PaytoHashP h_payto;
+ };
+
.. _TALER_DepositTrackPS:
.. sourcecode:: c
@@ -830,19 +1027,18 @@ within the
* purpose.purpose = TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
- struct TALER_MerchantPublicKeyP merchant;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_MerchantWireHash h_wire;
+ union TALER_CoinSpendPublicKeyP coin_pub;
};
.. _TALER_WireDepositDetailP:
.. sourcecode:: c
struct TALER_WireDepositDetailP {
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHash h_contract_terms;
struct GNUNET_TIME_AbsoluteNBO execution_time;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ union TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_AmountNBO deposit_value;
struct TALER_AmountNBO deposit_fee;
};
@@ -859,7 +1055,7 @@ within the
struct TALER_AmountNBO total;
struct TALER_AmountNBO wire_fee;
struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_HashCode h_wire;
+ struct TALER_MerchantWireHash h_wire;
struct GNUNET_HashCode h_details;
};
@@ -881,7 +1077,7 @@ within the
struct TALER_AmountNBO fee_withdraw;
struct TALER_AmountNBO fee_deposit;
struct TALER_AmountNBO fee_refresh;
- struct GNUNET_HashCode denom_hash;
+ struct TALER_DenominationHash denom_hash;
};
.. _TALER_PaymentResponsePS:
@@ -892,7 +1088,7 @@ within the
* purpose.purpose = TALER_SIGNATURE_MERCHANT_PAYMENT_OK
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHash h_contract_terms;
};
.. _TALER_ContractPS:
@@ -905,7 +1101,7 @@ within the
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct TALER_AmountNBO total_amount;
struct TALER_AmountNBO max_fee;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHash h_contract_terms;
struct TALER_MerchantPublicKeyP merchant_pub;
};
@@ -918,10 +1114,10 @@ within the
* purpose.purpose = TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_MerchantWireHash h_wire;
+ struct TALER_PrivateContractHash h_contract_terms;
struct TALER_WireTransferIdentifierRawP wtid;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ union TALER_CoinSpendPublicKeyP coin_pub;
struct GNUNET_TIME_AbsoluteNBO execution_time;
struct TALER_AmountNBO coin_contribution;
};
@@ -934,8 +1130,8 @@ within the
* purpose.purpose = TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND.
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_PrivateContractHash h_contract_terms;
+ union TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_MerchantPublicKeyP merchant;
uint64_t rtransaction_id;
struct TALER_AmountNBO refund_amount;
@@ -949,10 +1145,10 @@ within the
* purpose.purpose = TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION.
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
+ struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_MerchantWireHash h_wire;
struct TALER_MerchantPublicKeyP merchant;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ union TALER_CoinSpendPublicKeyP coin_pub;
};
.. _TALER_RefundRequestPS:
@@ -963,9 +1159,8 @@ within the
* purpose.purpose = TALER_SIGNATURE_MERCHANT_REFUND
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_MerchantPublicKeyP merchant;
+ struct TALER_PrivateContractHash h_contract_terms;
+ union TALER_CoinSpendPublicKeyP coin_pub;
uint64_t rtransaction_id;
struct TALER_AmountNBO refund_amount;
struct TALER_AmountNBO refund_fee;
@@ -992,10 +1187,10 @@ within the
struct TALER_RecoupRequestPS {
/**
* purpose.purpose = TALER_SIGNATURE_WALLET_COIN_RECOUP
+ * or TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_DenominationHash h_denom_pub;
struct TALER_DenominationBlindingKeyP coin_blind;
};
@@ -1009,8 +1204,8 @@ within the
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO timestamp;
struct TALER_AmountNBO recoup_amount;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ union TALER_CoinSpendPublicKeyP coin_pub;
+ union TALER_CoinSpendPublicKeyP old_coin_pub;
};
.. _TALER_RecoupConfirmationPS:
@@ -1023,7 +1218,7 @@ within the
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO timestamp;
struct TALER_AmountNBO recoup_amount;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ union TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_ReservePublicKeyP reserve_pub;
};
@@ -1037,7 +1232,7 @@ within the
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO timestamp;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_DenominationHash h_denom_pub;
};
@@ -1051,7 +1246,7 @@ within the
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO timestamp;
char operation[8];
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_DenominationHash h_denom_pub;
};
@@ -1066,7 +1261,7 @@ within the
struct GNUNET_TIME_AbsoluteNBO timestamp;
struct TALER_AmountNBO closing_amount;
struct TALER_ReservePublicKeyP reserve_pub;
- struct GNUNET_HashCode h_wire;
+ struct TALER_PaytoHash h_wire;
};
.. _TALER_CoinLinkSignaturePS:
@@ -1077,15 +1272,13 @@ within the
* purpose.purpose = TALER_SIGNATURE_WALLET_COIN_LINK
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_denom_pub;
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ struct TALER_DenominationHash h_denom_pub;
+ union TALER_CoinSpendPublicKeyP old_coin_pub;
struct TALER_TransferPublicKeyP transfer_pub;
- struct GNUNET_HashCode coin_envelope_hash;
+ struct TALER_BlindedCoinHash coin_envelope_hash;
};
-
-
.. _TALER_ReserveStatusRequestSignaturePS:
.. sourcecode:: c
@@ -1135,7 +1328,7 @@ within the
struct TALER_AmountNBO max_deposit_fees;
struct GNUNET_TIME_AbsoluteNBO purse_expiration;
struct GNUNET_TIME_AbsoluteNBO status_timestamp;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHash h_contract_terms;
};
@@ -1144,7 +1337,7 @@ within the
struct TALER_ReserveCloseRequestSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_RESERVE_CLOSE
+ * purpose.purpose = TALER_SIGNATURE_WALLET_RESERVE_CLOSE
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
};
@@ -1162,12 +1355,13 @@ within the
struct TALER_PurseRequestSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_PURSE_REQUEST
+ * purpose.purpose = TALER_SIGNATURE_WALLET_PURSE_CREATE
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO purse_expiration;
struct TALER_AmountNBO merge_value_after_fees;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ uint32_t min_age;
};
@@ -1180,9 +1374,23 @@ within the
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct TALER_AmountNBO coin_contribution;
- struct GNUNET_TIME_AbsoluteNBO purse_expiration;
- struct TALER_PursePublicKey purse_pub;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_DenominationHash h_denom_pub;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ struct TALER_PursePublicKeyP purse_pub;
+ struct GNUNET_HashCode h_exchange_base_url;
+ };
+
+
+.. _TALER_ReserveOpenDepositSignaturePS:
+.. sourcecode:: c
+
+ struct TALER_PurseDepositSignaturePS {
+ /**
+ * purpose.purpose = TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_AmountNBO coin_contribution;
};
@@ -1196,9 +1404,9 @@ within the
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct TALER_AmountNBO total_purse_amount;
struct TALER_AmountNBO total_deposit_fees;
- struct TALER_PursePublicKey purse_pub;
+ struct TALER_PursePublicKeyP purse_pub;
struct GNUNET_TIME_AbsoluteNBO purse_expiration;
- struct GNUNET_HashCode h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
};
.. _TALER_PurseMergeSignaturePS:
@@ -1206,15 +1414,11 @@ within the
struct TALER_PurseMergeSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_PURSE_MERGE
+ * purpose.purpose = TALER_SIGNATURE_WALLET_PURSE_MERGE
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct TALER_ReservePublicKey reserve_pub;
struct GNUNET_TIME_AbsoluteNBO merge_timestamp;
- struct GNUNET_TIME_AbsoluteNBO purse_expiration;
- struct TALER_AmountNBO merge_value_after_fees;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
+ struct TALER_PaytoHashP h_wire;
};
@@ -1223,59 +1427,58 @@ within the
struct TALER_AccountMergeSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_ACCOUNT_MERGE
+ * purpose.purpose = TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct TALER_PursePublicKey purse_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PursePublicKeyP purse_pub;
+ struct TALER_AmountNBO merge_amount_after_fees;
struct GNUNET_TIME_AbsoluteNBO merge_timestamp;
struct GNUNET_TIME_AbsoluteNBO purse_expiration;
- struct TALER_AmountNBO merge_value_after_fees;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ uint32_t min_age;
};
-
-.. _TALER_PurseMergeSuccessSignaturePS:
+.. _TALER_AccountSetupRequestSignaturePS:
.. sourcecode:: c
- struct TALER_PurseMergeSuccessSignaturePS {
+ struct TALER_AccountSetupRequestSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_PURSE_MERGE_SUCCESS
+ * purpose.purpose = TALER_SIGNATURE_WALLET_ACCOUNT_SETUP
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct TALER_ReservePublicKey reserve_pub;
- struct TALER_PursePublicKey purse_pub;
- struct TALER_AmountNBO merge_amount_after_fees;
- struct GNUNET_TIME_AbsoluteNBO contract_time;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
+ struct TALER_AmountNBO threshold;
};
-
-.. _TALER_AccountSetupRequestSignaturePS:
+.. _TALER_AccountSetupSuccessSignaturePS:
.. sourcecode:: c
- struct TALER_AccountSetupRequestSignaturePS {
+ struct TALER_AccountSetupSuccessSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST
+ * purpose.purpose = TALER_SIGNATURE_WALLET_ACCOUNT_SETUP_SUCCESS
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_TIME_AbsoluteNBO kyc_timestamp;
- struct TALER_AmountNBO kyc_fee;
- struct GNUNET_HashCode h_wire;
+ struct TALER_PaytoHash h_payto;
+ struct GNUNET_HashCode h_kyc;
+ struct GNUNET_TIME_AbsoluteNBO timestamp;
};
-.. _TALER_AccountSetupSuccessSignaturePS:
+.. _TALER_PurseMergeSuccessSignaturePS:
.. sourcecode:: c
- struct TALER_AccountSetupRequestSignaturePS {
+ struct TALER_PurseMergeSuccessSignaturePS {
/**
- * purpose.purpose = TALER_SIGNATURE_ACCOUNT_SETUP_SUCCESS
+ * purpose.purpose = TALER_SIGNATURE_PURSE_MERGE_SUCCESS
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct TALER_ReservePublicKey reserve_pub;
- struct GNUNET_TIME_AbsoluteNBO now;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PursePublicKeyP purse_pub;
+ struct TALER_AmountNBO merge_amount_after_fees;
+ struct GNUNET_TIME_AbsoluteNBO contract_time;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_PaytoHashP h_wire;
+ uint32_t min_age;
};
@@ -1339,10 +1542,8 @@ within the
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct TALER_PursePublicKey purse_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_MerchantPublicKeyP merchant;
- struct TALER_AmountNBO remaining_amount;
- struct TALER_AmountNBO purse_fee_share;
+ union TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_AmountNBO refunded_amount;
struct TALER_AmountNBO refund_fee;
};
@@ -1355,7 +1556,7 @@ within the
* purpose.purpose = TALER_SIGNATURE_SM_DENOMINATION_KEY
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_DenominationHash h_denom_pub;
struct GNUNET_HashCode h_section_name;
struct GNUNET_TIME_AbsoluteNBO anchor_time;
struct GNUNET_TIME_RelativeNBO duration_withdraw;
@@ -1383,7 +1584,7 @@ within the
* purpose.purpose = TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED.
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
- struct GNUNET_HashCode h_denom_pub;
+ struct TALER_DenominationHash h_denom_pub;
};
@@ -1433,7 +1634,7 @@ within the
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO start_date;
- struct GNUNET_HashCode h_wire GNUNET_PACKED;
+ struct TALER_PaytoHash h_wire;
};
.. _TALER_MasterDelWirePS:
@@ -1445,5 +1646,5 @@ within the
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
struct GNUNET_TIME_AbsoluteNBO end_date;
- struct GNUNET_HashCode h_wire GNUNET_PACKED;
+ struct TALER_PaytoHash h_wire;
};
diff --git a/core/api-error.rst b/core/api-error.rst
index fcd7374b..6015579b 100644
--- a/core/api-error.rst
+++ b/core/api-error.rst
@@ -747,7 +747,7 @@ The following list shows error codes defined in
/**
* The currency specified in the "amount" parameter is not
- * supported by this exhange. Returned with an HTTP status
+ * supported by this exchange. Returned with an HTTP status
* code of MHD_HTTP_BAD_REQUEST.
*/
TALER_EC_ADMIN_ADD_INCOMING_CURRENCY_UNSUPPORTED = 1601,
@@ -1058,7 +1058,7 @@ The following list shows error codes defined in
TALER_EC_DEPOSITS_GET_WIRE_TRANSFER_TRACE_ERROR = 2307,
/**
- * We got conflicting reports from the exhange with
+ * We got conflicting reports from the exchange with
* respect to which transfers are included in which
* aggregate.
* The response is
diff --git a/core/api-exchange.rst b/core/api-exchange.rst
index 1aae8e75..db192e07 100644
--- a/core/api-exchange.rst
+++ b/core/api-exchange.rst
@@ -1,6 +1,6 @@
..
This file is part of GNU TALER.
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -15,59 +15,18 @@
@author Christian Grothoff
-=============================
-The Exchange RESTful JSON API
-=============================
+========================
+The Exchange RESTful API
+========================
The API specified here follows the :ref:`general conventions <http-common>`
for all details not specified in the individual requests.
The `glossary <https://docs.taler.net/glossary.html#glossary>`_
defines all specific terms used in this section.
-.. _keys:
-
---------------------
-Terms of service API
---------------------
-
-These APIs allow merchants and wallets to obtain the terms of service
-and the privacy policy of an exchange.
-
-
-.. http:get:: /terms
-
- Get the terms of service of the exchange.
- The exchange will consider the "Accept" and "Accept-Language" and
- "Accept-Encoding" headers when generating a response. Specifically,
- it will try to find a response with an acceptable mime-type, then
- pick the version in the most preferred language of the user, and
- finally apply compression if that is allowed by the client and
- deemed beneficial.
-
- The exchange will set an "Etag", and subsequent requests of the
- same client should provide the tag in an "If-None-Match" header
- to detect if the terms of service have changed. If not, a
- "204 Not Modified" response will be returned.
-
- If the "Etag" is missing, the client should not cache the response and instead prompt the user again at the next opportunity. This is usually only the case if the terms of service were not configured correctly.
-
-
-.. http:get:: /privacy
-
- Get the privacy policy of the exchange.
- The exchange will consider the "Accept" and "Accept-Language" and
- "Accept-Encoding" headers when generating a response. Specifically,
- it will try to find a response with an acceptable mime-type, then
- pick the version in the most preferred language of the user, and
- finally apply compression if that is allowed by the client and
- deemed beneficial.
+.. include:: tos.rst
- The exchange will set an "Etag", and subsequent requests of the
- same client should provide the tag in an "If-None-Match" header
- to detect if the privacy policy has changed. If not, a
- "204 Not Modified" response will be returned.
-
- If the "Etag" is missing, the client should not cache the response and instead prompt the user again at the next opportunity. This is usually only the case if the privacy policy was not configured correctly.
+.. _keys:
---------------------------
Exchange status information
@@ -120,9 +79,17 @@ possibly by using HTTPS.
// The format is "current:revision:age".
version: string;
- // The exchange's currency.
+ // The exchange's base URL.
+ base_url: string;
+
+ // The exchange's currency or asset unit.
currency: string;
+ // Type of the asset. "fiat", "crypto", "regional"
+ // or "stock". Wallets should adjust their UI/UX
+ // based on this value.
+ asset_type: string;
+
// EdDSA master public key of the exchange, used to sign entries
// in ``denoms`` and ``signkeys``.
master_public_key: EddsaPublicKey;
@@ -131,16 +98,30 @@ possibly by using HTTPS.
// not signed (!), can change without notice.
reserve_closing_delay: RelativeTime;
+ // Threshold amounts beyond which wallet should
+ // trigger the KYC process of the issuing
+ // exchange. Optional option, if not given there is no limit.
+ // Currency must match ``currency``.
+ wallet_balance_limit_without_kyc?: Amount[];
+
// Denominations offered by this exchange.
+ // DEPRECATED: Will eventually be replaced by the
+ // differently structured "denominations" field.
denoms: Denom[];
+ // Denominations offered by this exchange
+ denominations: DenomGroup[];
+
+ // Compact EdDSA `signature` (binary-only) over the XOR of all
+ // .hash fields (in binary) in the list "denominations".
+ // Signature of `TALER_ExchangeKeySetPS`
+ denominations_sig: EddsaSignature;
+
// Denominations for which the exchange currently offers/requests recoup.
recoup: Recoup[];
- // Fees relevant for wallet-to-wallet (or peer-to-peer) payments.
- // If no fees are provided for a given time range, then the
- // exchange simply does not support purses/p2p-payments at that time.
- p2p_fees: P2PFees[];
+ // Array of globally applicable fees by time range.
+ global_fees: GlobalFees[];
// The date when the denomination keys were last updated.
list_issue_date: Timestamp;
@@ -151,6 +132,15 @@ possibly by using HTTPS.
// The exchange's signing keys.
signkeys: SignKey[];
+ // Optional field with a dictionary of (name, object) pairs defining the
+ // supported and enabled extensions, such as ``age_restriction``.
+ extensions?: { name: ExtensionManifest };
+
+ // Signature by the exchange master key of the SHA-256 hash of the
+ // normalized JSON-object of field extensions, if it was set.
+ // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS.
+ extensions_sig?: EddsaSignature;
+
// Compact EdDSA `signature` (binary-only) over the SHA-512 hash of the
// concatenation of all SHA-512 hashes of the RSA denomination public keys
// in ``denoms`` in the same order as they were in ``denoms``. Note that for
@@ -161,6 +151,7 @@ possibly by using HTTPS.
// different users. If an exchange were to do this, this signature allows the
// clients to demonstrate to the public that the exchange is dishonest.
// Signature of `TALER_ExchangeKeySetPS`
+ // DEPRICATED: Will eventually replaced by "denominations_sig"
eddsa_sig: EddsaSignature;
// Public EdDSA key of the exchange that was used to generate the signature.
@@ -170,13 +161,9 @@ possibly by using HTTPS.
eddsa_pub: EddsaPublicKey;
}
- .. ts:def:: P2PFees
+ .. ts:def:: GlobalFees
- .. note::
-
- This is a draft API that is not yet implemented.
-
- interface P2PFees {
+ interface GlobalFees {
// What date (inclusive) does these fees go into effect?
start_date: Timestamp;
@@ -184,15 +171,9 @@ possibly by using HTTPS.
// What date (exclusive) does this fees stop going into effect?
end_date: Timestamp;
- // KYC fee, charged when a user wants to create an account.
- // The first year of the account_annual_fee after the KYC is
- // always included.
- kyc_fee: Amount;
-
// Account history fee, charged when a user wants to
- // obtain the full account history, and not just the
- // recent transactions in an account.
- account_history_fee: Amount;
+ // obtain a reserve/account history.
+ history_fee: Amount;
// Annual fee charged for having an open account at the
// exchange. Charged to the account. If the account
@@ -200,24 +181,17 @@ possibly by using HTTPS.
// is automatically deleted/closed. (Note that the exchange
// will keep the account history around for longer for
// regulatory reasons.)
- account_annual_fee: Amount;
-
- // How long will the exchange preserve the account history?
- // After an account was deleted/closed, the exchange will
- // retain the account history for legal reasons until this time.
- legal_history_retention: RelativeTime;
-
- // How long does the exchange promise to keep funds
- // an account for which the KYC has never happened
- // after a purse was merged into an account? Basically,
- // after this time funds in an account without KYC are
- // forfeit.
- account_kyc_timeout: RelativeTime;
+ account_fee: Amount;
// Purse fee, charged only if a purse is abandoned
// and was not covered by the account limit.
purse_fee: Amount;
+ // How long will the exchange preserve the account history?
+ // After an account was deleted/closed, the exchange will
+ // retain the account history for legal reasons until this time.
+ history_expiration: RelativeTime;
+
// Non-negative number of concurrent purses that any
// account holder is allowed to create without having
// to pay the purse_fee.
@@ -229,9 +203,120 @@ possibly by using HTTPS.
// plus this value.
purse_timeout: RelativeTime;
- // Signature of `TALER_P2PFeesPS`.
+ // Signature of `TALER_GlobalFeesPS`.
+ master_sig: EddsaSignature;
+
+ }
+
+
+ .. ts:def:: AgeMask
+
+ // Binary representation of the age groups.
+ // The bits set in the mask mark the edges at the beginning of a next age
+ // group. F.e. for the age groups
+ // 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-21, 21-*
+ // the following bits are set:
+ //
+ // 31 24 16 8 0
+ // | | | | |
+ // oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
+ //
+ // A value of 0 means that the exchange does not support the extension for
+ // age-restriction.
+ type AgeMask = Integer;
+
+ .. ts:def:: DenomGroup
+
+ type DenomGroup =
+ | DenomGroupRsa
+ | DenomGroupCs
+ | DenomGroupRsaAgeRestricted
+ | DenomGroupCsAgeRestricted;
+
+ .. ts:def:: DenomGroupRsa
+
+ interface DenomGroupRsa extends DenomGroupCommon {
+ cipher: "RSA";
+
+ denoms: ({
+ rsa_pub: RsaPublicKey;
+ } & DenomCommon)[];
+ }
+
+ .. ts:def:: DenomGroupCs
+
+ interface DenomGroupCs extends DenomGroupCommon {
+ cipher: "CS";
+
+ denoms: ({
+ cs_pub: Cs25519Point;
+ } & DenomCommon)[];
+ }
+
+ .. ts:def:: DenomGroupRsaAgeRestricted
+
+ interface DenomGroupRsaAgeRestricted extends DenomGroupCommon {
+ cipher: "RSA+age_restricted";
+ age_mask: AgeMask;
+
+ denoms: ({
+ rsa_pub: RsaPublicKey;
+ } & DenomCommon)[];
+ }
+
+ .. ts:def:: DenomGroupCsAgeRestricted
+
+ interface DenomGroupCSAgeRestricted extends DenomGroupCommon {
+ cipher: "CS+age_restricted";
+ age_mask: AgeMask;
+
+ denoms: ({
+ cs_pub: Cs25519Point;
+ } & DenomCommon)[];
+ }
+
+ .. ts:def:: DenomGroupCommon
+
+ // Common attributes for all denomination groups
+ interface DenomGroupCommon {
+ // How much are coins of this denomination worth?
+ value: Amount;
+
+ // Fee charged by the exchange for withdrawing a coin of this denomination.
+ fee_withdraw: Amount;
+
+ // Fee charged by the exchange for depositing a coin of this denomination.
+ fee_deposit: Amount;
+
+ // Fee charged by the exchange for refreshing a coin of this denomination.
+ fee_refresh: Amount;
+
+ // Fee charged by the exchange for refunding a coin of this denomination.
+ fee_refund: Amount;
+
+ // XOR of all the SHA-512 hash values of the denominations' public keys
+ // in this group. Note that for hashing, the binary format of the
+ // public keys is used, and not their base32 encoding.
+ hash: HashCode;
+ }
+
+ .. ts:def:: DenomCommon
+
+ interface DenomCommon {
+ // Signature of `TALER_DenominationKeyValidityPS`.
master_sig: EddsaSignature;
+ // When does the denomination key become valid?
+ stamp_start: Timestamp;
+
+ // When is it no longer possible to deposit coins
+ // of this denomination?
+ stamp_expire_withdraw: Timestamp;
+
+ // Timestamp indicating by when legal disputes relating to these coins must
+ // be settled, as the exchange will afterwards destroy its evidence relating to
+ // transactions involving this coin.
+ stamp_expire_legal: Timestamp;
}
.. ts:def:: Denom
@@ -252,8 +337,8 @@ possibly by using HTTPS.
// transactions involving this coin.
stamp_expire_legal: Timestamp;
- // Public (RSA) key for the denomination.
- denom_pub: RsaPublicKey;
+ // Public key for the denomination.
+ denom_pub: DenominationKey;
// Fee charged by the exchange for withdrawing a coin of this denomination.
fee_withdraw: Amount;
@@ -271,6 +356,37 @@ possibly by using HTTPS.
master_sig: EddsaSignature;
}
+ .. ts:def:: DenominationKey
+
+ type DenominationKey =
+ | RsaDenominationKey
+ | CSDenominationKey;
+
+ .. ts:def:: RsaDenominationKey
+
+ interface RsaDenominationKey {
+ cipher: "RSA";
+
+ // 32-bit age mask.
+ age_mask: Integer;
+
+ // RSA public key
+ rsa_public_key: RsaPublicKey;
+ }
+
+ .. ts:def:: CSDenominationKey
+
+ interface CSDenominationKey {
+ cipher: "CS";
+
+ // 32-bit age mask.
+ age_mask: Integer;
+
+ // Public key of the denomination.
+ cs_public_key: Cs25519Point;
+
+ }
+
Fees for any of the operations can be zero, but the fields must still be
present. The currency of the ``fee_deposit``, ``fee_refresh`` and ``fee_refund`` must match the
currency of the ``value``. Theoretically, the ``fee_withdraw`` could be in a
@@ -385,7 +501,7 @@ possibly by using HTTPS.
// incoming wire transfers.
accounts: WireAccount[];
- // Object mapping names of wire methods (i.e. "sepa" or "x-taler-bank")
+ // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank")
// to wire fees.
fees: { method : AggregateTransferFee };
@@ -422,6 +538,9 @@ possibly by using HTTPS.
// Per transfer closing fee.
closing_fee: Amount;
+ // Per exchange-to-exchange transfer (wad) fee.
+ wad_fee: Amount;
+
// What date (inclusive) does this fee go into effect?
// The different fees must cover the full time period in which
// any of the denomination keys are valid without overlap.
@@ -446,9 +565,6 @@ possibly by using HTTPS.
// Public master key of the partner exchange.
partner_master_pub: EddsaPublicKey;
- // Wallet-to-wallet transfer wad fee charged.
- wad_fee: Amount;
-
// Exchange-to-exchange wad (wire) transfer frequency.
wad_frequency: RelativeTime;
@@ -832,6 +948,23 @@ Management operations authorized by master key
}
+.. http:post:: /management/global-fees
+
+ Provides global fee configuration for a timeframe.
+
+ **Request:**
+
+ The request must be a `GlobalFees` message.
+
+ **Response**
+
+ :http:statuscode:`204 No content`:
+ The configuration update has been processed successfully. The body is empty.
+ :http:statuscode:`403 Forbidden`:
+ The signature is invalid.
+ :http:statuscode:`409 Conflict`:
+ The exchange has previously received a conflicting configuration message.
+
.. http:post:: /management/wire
@@ -928,26 +1061,51 @@ Management operations authorized by master key
}
-.. http:post:: /management/p2pfees
-
- Provides fee configuration for purses.
+.. http:post:: /management/drain
- .. note::
-
- This is a draft API that is not yet implemented.
+ This request is used to drain profits from the
+ exchange's escrow account to another regular
+ bank account of the exchange. The actual drain
+ requires running the ``taler-exchange-drain`` tool.
**Request:**
- The request must be a `P2PFees` message.
+ The request must be a `DrainProfitsMessage`.
- **Response**
+ **Response:**
:http:statuscode:`204 No content`:
- The configuration update has been processed successfully. The body is empty.
+ The profit drain was scheduled.
:http:statuscode:`403 Forbidden`:
- The signature is invalid.
- :http:statuscode:`409 Conflict`:
- The exchange has previously received a conflicting configuration message.
+ The master signature is invalid.
+
+ **Details:**
+
+ .. ts:def:: DrainProfitsMessage
+
+ interface DrainProfitsMessage {
+
+ // Configuration section of the account to debit.
+ debit_account_section: string;
+
+ // Credit payto URI
+ credit_payto_uri: string;
+
+ // Wire transfer identifier to use.
+ wtid: Base32;
+
+ // Signature by the exchange master key over a
+ // `TALER_MasterDrainProfitPS`.
+ // Must have purpose ``TALER_SIGNATURE_MASTER_DRAIN_PROFITS``.
+ master_sig: EddsaSignature;
+
+ // When was the message created.
+ date: Timestamp;
+
+ // Amount to be drained.
+ amount: Amount;
+
+ }
.. http:post:: /management/partners
@@ -1062,10 +1220,43 @@ exchange.
.. note::
- Eventually the exchange will need to advertize a policy for how long it will
+ Eventually the exchange will need to advertise a policy for how long it will
keep transaction histories for inactive or even fully drained reserves. We
will therefore need some additional handler similar to ``/keys`` to
- advertize those terms of service.
+ advertise those terms of service.
+
+
+.. http:get:: /reserves/$RESERVE_PUB
+
+ Request information about a reserve.
+
+ **Request:**
+
+ :query timeout_ms=MILLISECONDS: *Optional.* If specified, the exchange will wait up to MILLISECONDS for incoming funds before returning a 404 if the reserve does not yet exist.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The exchange responds with a `ReserveSummary` object; the reserve was known to the exchange.
+ :http:statuscode:`404 Not found`:
+ The reserve key does not belong to a reserve known to the exchange.
+
+ **Details:**
+
+ .. ts:def:: ReserveSummary
+
+ interface ReserveSummary {
+ // Balance left in the reserve.
+ balance: Amount;
+
+ // If set, age restriction is required to be set for each coin to this
+ // value during the withdrawal from this reserve. The client then MUST
+ // use a denomination with support for age restriction enabled for the
+ // withdrawal.
+ // The value represents a valid age group from the list of permissible
+ // age groups as defined by the exchange's output to /keys.
+ maximum_age_group?: number;
+ }
.. http:post:: /reserves/$RESERVE_PUB/status
@@ -1074,24 +1265,15 @@ exchange.
**Request:**
- :query history=BOOLEAN: *Optional.* If specified, the exchange
- will return the recent account history.
- This is still free of charge.
- :query full_history=BOOLEAN: *Optional.* If 'true' is specified,
- the exchange will return the full account history. This
- may incur a fee that will be charged to the account.
-
The request body must be a `ReserveStatusRequest` object.
**Response:**
:http:statuscode:`200 OK`:
The exchange responds with a `ReserveStatus` object; the reserve was known to the exchange.
- :http:statuscode:`401 Unauthorized`:
- The *Account-Request-Signature* is invalid.
- This response comes with a standard `ErrorDetail` response.
:http:statuscode:`403 Forbidden`:
- The provided timestamp is not close to the current time.
+ The *TALER_SIGNATURE_RESERVE_STATUS_REQUEST* signature is invalid.
+ This response comes with a standard `ErrorDetail` response. Alternatively, the provided timestamp is not close to the current time.
:http:statuscode:`404 Not found`:
The reserve key does not belong to a reserve known to the exchange.
@@ -1118,13 +1300,9 @@ exchange.
// Balance left in the reserve.
balance: Amount;
- // True if the owner of the account currently satisfies
- // the required KYC checks.
- kyc_passed: boolean;
-
- // True if the reserve history includes a merge of a purse
- // and thus the owner must pass KYC checks before withdrawing.
- kyc_required: boolean;
+ // If set, gives the maximum age group that the client is required to set
+ // during withdrawal.
+ maximum_age_group: number;
// Transaction history for this reserve.
// May be partial (!).
@@ -1141,9 +1319,60 @@ exchange.
| AccountSetupTransaction
| ReserveHistoryTransaction
| ReserveWithdrawTransaction
+ | ReserveAgeWithdrawTransaction
| ReserveCreditTransaction
| ReserveClosingTransaction
- | ReserveRecoupTransaction;
+ | ReserveOpenRequestTransaction
+ | ReserveCloseRequestTransaction
+ | PurseMergeTransaction;
+
+ .. ts:def:: PurseMergeTransaction
+
+ interface PurseMergeTransaction {
+ type: "MERGE";
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKey;
+
+ // Minimum age required for all coins deposited into the purse.
+ min_age: Integer;
+
+ // Number that identifies who created the purse
+ // and how it was paid for.
+ flags: Integer;
+
+ // Purse public key.
+ purse_pub: EddsaPublicKey;
+
+ // EdDSA signature of the account/reserve affirming the merge
+ // over a `TALER_AccountMergeSignaturePS`.
+ // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE``
+ reserve_sig: EddsaSignature;
+
+ // Client-side timestamp of when the merge request was made.
+ merge_timestamp: Timestamp;
+
+ // Indicative time by which the purse should expire
+ // if it has not been merged into an account. At this
+ // point, all of the deposits made should be
+ // auto-refunded.
+ purse_expiration: Timestamp;
+
+ // Purse fee the reserve owner paid for the purse creation.
+ purse_fee: Amount;
+
+ // Total amount merged into the reserve.
+ // (excludes fees).
+ amount: Amount;
+
+ // True if the purse was actually merged.
+ // If false, only the purse_fee has an impact
+ // on the reserve balance!
+ merged: boolean;
+ }
.. ts:def:: ReserveHistoryTransaction
@@ -1151,7 +1380,7 @@ exchange.
type: "HISTORY";
// Fee agreed to by the reserve owner.
- history_fee: Amount;
+ amount: Amount;
// Time when the request was made.
request_timestamp: Timestamp;
@@ -1183,7 +1412,7 @@ exchange.
// Signature created with the reserve's private key.
// Must be of purpose ``TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST`` over
- // a `TALER_AccountSetupRequestSignaturePS`.
+ // a ``TALER_AccountSetupRequestSignaturePS``.
reserve_sig: EddsaSignature;
}
@@ -1252,6 +1481,26 @@ exchange.
withdraw_fee: Amount;
}
+ .. ts:def:: ReserveAgeWithdrawTransaction
+
+ interface ReserveAgeWithdrawTransaction {
+ type: "AGEWITHDRAW";
+
+ // Total Amount withdrawn.
+ amount: Amount;
+
+ // Commitment of all ``n*kappa`` coins.
+ age_restricted_coins_commitment: HashCode;
+
+ // Signature over a `TALER_AgeWithdrawRequestPS`
+ // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``
+ // created with the reserve's private key.
+ reserve_sig: EddsaSignature;
+
+ // Fee that is charged for withdraw.
+ withdraw_fee: Amount;
+ }
+
.. ts:def:: ReserveCreditTransaction
@@ -1285,7 +1534,7 @@ exchange.
closing_fee: Amount;
// Wire transfer subject.
- wtid: string;
+ wtid: Base32;
// ``payto://`` URI of the wire account into which the funds were returned to.
receiver_account_details: string;
@@ -1303,27 +1552,64 @@ exchange.
}
- .. ts:def:: ReserveRecoupTransaction
+ .. ts:def:: ReserveOpenRequestTransaction
- interface ReserveRecoupTransaction {
- type: "RECOUP";
+ interface ReserveOpenRequestTransaction {
+ type: "OPEN";
- // Amount paid back.
- amount: Amount;
+ // Open fee paid from the reserve.
+ open_fee: Amount;
// This is a signature over
- // a struct `TALER_RecoupConfirmationPS` with purpose
- // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP``.
- exchange_sig: EddsaSignature;
+ // a struct `TALER_ReserveOpenPS` with purpose
+ // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN``.
+ reserve_sig: EddsaSignature;
- // Public key used to create 'exchange_sig'.
- exchange_pub: EddsaPublicKey;
+ // Timestamp of the open request.
+ request_timestamp: Timestamp;
- // Time when the funds were paid back into the reserve.
- timestamp: Timestamp;
+ // Requested expiration.
+ requested_expiration: Timestamp;
- // Public key of the coin that was paid back.
- coin_pub: CoinPublicKey;
+ // Requested number of free open purses.
+ requested_min_purses: Integer;
+
+ }
+
+ .. ts:def:: ReserveCloseRequestTransaction
+
+ interface ReserveCloseRequestTransaction {
+ type: "CLOSE";
+
+ // This is a signature over
+ // a struct `TALER_ReserveClosePS` with purpose
+ // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE``.
+ reserve_sig: EddsaSignature;
+
+ // Target account ``payto://``, optional.
+ h_payto?: string;
+
+ // Timestamp of the close request.
+ request_timestamp: Timestamp;
+ }
+
+ .. ts:def:: ReserveCreditTransaction
+
+ interface ReserveCreditTransaction {
+ type: "CREDIT";
+
+ // Amount deposited.
+ amount: Amount;
+
+ // Sender account ``payto://`` URL.
+ sender_account_url: string;
+
+ // Opaque identifier internal to the exchange that
+ // uniquely identifies the wire transfer that credited the reserve.
+ wire_reference: Integer;
+
+ // Timestamp of the incoming wire transfer.
+ timestamp: Timestamp;
}
@@ -1340,11 +1626,9 @@ exchange.
:http:statuscode:`200 OK`:
The exchange responds with a `ReserveStatus` object; the reserve was known to the exchange.
- :http:statuscode:`401 Unauthorized`:
- The *Account-Request-Signature* is invalid.
- This response comes with a standard `ErrorDetail` response.
:http:statuscode:`403 Forbidden`:
- The provided timestamp is not close to the current time.
+ The *TALER_SIGNATURE_RESERVE_HISTORY_REQUEST* is invalid.
+ This response comes with a standard `ErrorDetail` response. Alternatively, the provided timestamp is not close to the current time.
:http:statuscode:`404 Not found`:
The reserve key does not belong to a reserve known to the exchange.
:http:statuscode:`412 Precondition failed`:
@@ -1369,6 +1653,138 @@ exchange.
}
+.. _delete-reserve:
+
+.. http:DELETE:: /reserves/$RESERVE_PUB
+
+ Forcefully closes a reserve.
+ The request header must contain an *Account-Request-Signature*.
+ Note: this endpoint is not currently implemented!
+
+ **Request:**
+
+ *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to delete the account. The purpose used MUST be ``TALER_SIGNATURE_RESERVE_CLOSE``.
+
+ :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange
+ will delete the account even if there is a balance remaining.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange provides details
+ about the account deletion.
+ The response will include a `ReserveClosedResponse` object.
+ :http:statuscode:`403 Forbidden`:
+ The *Account-Request-Signature* is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The account is unknown to the exchange.
+ :http:statuscode:`409 Conflict`:
+ The account is still has digital cash in it, the associated
+ wire method is ``void`` and the *force* option was not provided.
+ This response comes with a standard `ErrorDetail` response.
+
+ **Details:**
+
+ .. ts:def:: ReserveClosedResponse
+
+ interface ReserveClosedResponse {
+
+ // Final balance of the account.
+ closing_amount: Amount;
+
+ // Current time of the exchange, used as part of
+ // what the exchange signs over.
+ close_time: Timestamp;
+
+ // Hash of the wire account into which the remaining
+ // balance will be transferred. Note: may be the
+ // hash over ``payto://void/`, in which case the
+ // balance is forfeit to the profit of the exchange.
+ h_wire: HashCode;
+
+ // This is a signature over a
+ // struct ``TALER_AccountDeleteConfirmationPS`` with purpose
+ // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``.
+ exchange_sig: EddsaSignature;
+
+ }
+
+
+
+Withdraw
+~~~~~~~~
+
+.. http:post:: /csr-withdraw
+
+ Obtain exchange-side input values in preparation for a
+ withdraw step for certain denomination cipher types,
+ specifically at this point for Clause-Schnorr blind
+ signatures.
+
+ **Request:** The request body must be a `WithdrawPrepareRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `WithdrawPrepareResponse`. Note that repeating exactly the same request
+ will again yield the same response (assuming none of the denomination is expired).
+ :http:statuscode:`404 Not found`:
+ The denomination key is not known to the exchange.
+ :http:statuscode:`410 Gone`:
+ The requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked. The response is a
+ `DenominationExpiredMessage`. Clients must evaluate
+ the error code provided to understand which of the
+ cases this is and handle it accordingly.
+
+ **Details:**
+
+ .. ts:def:: WithdrawPrepareRequest
+
+ interface WithdrawPrepareRequest {
+
+ // Nonce to be used by the exchange to derive
+ // its private inputs from. Must not have ever
+ // been used before.
+ nonce: CSNonce;
+
+ // Hash of the public key of the denomination the
+ // request relates to.
+ denom_pub_hash: HashCode;
+
+ }
+
+ .. ts:def:: WithdrawPrepareResponse
+
+ type WithdrawPrepareResponse
+ | ExchangeWithdrawValue;
+
+ .. ts:def:: ExchangeWithdrawValue
+
+ type ExchangeWithdrawValue =
+ | ExchangeRsaWithdrawValue
+ | ExchangeCsWithdrawValue;
+
+ .. ts:def:: ExchangeRsaWithdrawValue
+
+ interface ExchangeRsaWithdrawValue {
+ cipher: "RSA";
+ }
+
+ .. ts:def:: ExchangeCsWithdrawValue
+
+ interface ExchangeCsWithdrawValue {
+ cipher: "CS";
+
+ // CSR R0 value
+ r_pub_0: CsRPublic;
+
+ // CSR R1 value
+ r_pub_1: CsRPublic;
+ }
+
+
.. http:post:: /reserves/$RESERVE_PUB/withdraw
Withdraw a coin of the specified denomination. Note that the client should
@@ -1386,27 +1802,9 @@ exchange.
will again yield the same response, so if the network goes down during the
transaction or before the client can commit the coin signature to disk, the
coin is not lost.
- :http:statuscode:`202 Accepted`:
- This reserve has received funds from a purse or the amount withdrawn
- exceeds another legal threshold and thus the reserve must
- be upgraded to an account (with KYC) before the withdraw can
- complete. Note that this response does NOT affirm that the
- withdraw will ultimately complete with the requested amount.
- The user should be redirected to the provided location to perform
- the required KYC checks to open the account before withdrawing.
- Afterwards, the request should be repeated.
- The response will be an `KycNeededRedirect` object.
-
- Implementation note: internally, we need to
- distinguish between upgrading the reserve to an
- account (due to P2P payment) and identifying the
- owner of the origin bank account (due to exceeding
- the withdraw amount threshold), as we need to create
- a different payto://-URI for the KYC check depending
- on the case.
-
:http:statuscode:`403 Forbidden`:
The signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
The denomination key or the reserve are not known to the exchange. If the
denomination key is unknown, this suggests a bug in the wallet as the
@@ -1426,6 +1824,25 @@ exchange.
`DenominationExpiredMessage`. Clients must evaluate
the error code provided to understand which of the
cases this is and handle it accordingly.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
+ be upgraded to an account (with KYC) before the withdraw can
+ complete. Note that this response does NOT affirm that the
+ withdraw will ultimately complete with the requested amount.
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account before withdrawing.
+ Afterwards, the request should be repeated.
+ The response will be an `KycNeededRedirect` object.
+
+ Implementation note: internally, we need to
+ distinguish between upgrading the reserve to an
+ account (due to P2P payment) and identifying the
+ owner of the origin bank account (due to exceeding
+ the withdraw amount threshold), as we need to create
+ a different payto://-URI for the KYC check depending
+ on the case.
+
**Details:**
@@ -1481,19 +1898,54 @@ exchange.
.. ts:def:: WithdrawResponse
interface WithdrawResponse {
- // The blinded RSA signature over the 'coin_ev', affirms the coin's
+ // The blinded signature over the 'coin_ev', affirms the coin's
// validity after unblinding.
- ev_sig: BlindedRsaSignature;
+ ev_sig: BlindedDenominationSignature;
+
+ }
+
+ .. ts:def:: BlindedDenominationSignature
+
+ type BlindedDenominationSignature =
+ | RsaBlindedDenominationSignature
+ | CSBlindedDenominationSignature;
+
+ .. ts:def:: RsaBlindedDenominationSignature
+
+ interface RsaBlindedDenominationSignature {
+ cipher: "RSA";
+
+ // (blinded) RSA signature
+ blinded_rsa_signature: BlindedRsaSignature;
+ }
+
+ .. ts:def:: CSBlindedDenominationSignature
+
+ interface CSBlindedDenominationSignature {
+ type: "CS";
+
+ // Signer chosen bit value, 0 or 1, used
+ // in Clause Blind Schnorr to make the
+ // ROS problem harder.
+ b: Integer;
+
+ // Blinded scalar calculated from c_b.
+ s: Cs25519Scalar;
}
.. ts:def:: KycNeededRedirect
interface KycNeededRedirect {
- // Payment target that the merchant should
+
+ // Hash of the payto:// account URI that identifies
+ // the account which is being KYCed.
+ h_payto: PaytoHash;
+
+ // Legitimization target that the merchant should
// use to check for its KYC status using
- // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
- payment_target_uuid: Integer;
+ // the ``/kyc-check/$REQUIREMENT_ROW/...`` endpoint.
+ requirement_row: Integer;
}
@@ -1510,80 +1962,323 @@ exchange.
balance: Amount;
// History of the reserve's activity, in the same format
- // as returned by ``/reserve/status``.
+ // as returned by ``/reserve/$RID/history``.
history: TransactionHistoryItem[]
}
-.. _delete-reserve:
-.. http:DELETE:: /reserves/$RESERVE_PUB
- Forcefully closes a reserve.
- The request header must contain an *Account-Request-Signature*.
- **Request:**
+Batch Withdraw
+~~~~~~~~~~~~~~
- *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to delete the account. The purpose used MUST be ``TALER_SIGNATURE_RESERVE_CLOSE``.
- :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange
- will delete the account even if there is a balance remaining.
+.. http:post:: /reserves/$RESERVE_PUB/batch-withdraw
+
+ Withdraw multiple coins from the same reserve. Note that the client should
+ commit all of the request details, including the private key of the coins and
+ the blinding factors, to disk *before* issuing this request, so that it can
+ recover the information if necessary in case of transient failures, like
+ power outage, network outage, etc.
+
+ **Request:** The request body must be a `BatchWithdrawRequest` object.
**Response:**
:http:statuscode:`200 OK`:
- The operation succeeded, the exchange provides details
- about the account deletion.
- The response will include a `ReserveClosedResponse` object.
- :http:statuscode:`401 Unauthorized`:
- The *Account-Request-Signature* is invalid.
+ The request was successful, and the response is a `BatchWithdrawResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
+ :http:statuscode:`403 Forbidden`:
+ A signature is invalid.
This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
- The account is unknown to the exchange.
+ A denomination key or the reserve are not known to the exchange. If the
+ denomination key is unknown, this suggests a bug in the wallet as the
+ wallet should have used current denomination keys from ``/keys``.
+ In this case, the response will be a `DenominationUnknownMessage`.
+ If the reserve is unknown, the wallet should not report a hard error yet, but
+ instead simply wait for up to a day, as the wire transaction might simply
+ not yet have completed and might be known to the exchange in the near future.
+ In this case, the wallet should repeat the exact same request later again
+ using exactly the same blinded coin.
:http:statuscode:`409 Conflict`:
- The account is still has digital cash in it, the associated
- wire method is ``void`` and the *force* option was not provided.
- This response comes with a standard `ErrorDetail` response.
+ The balance of the reserve is not sufficient to withdraw the coins of the
+ indicated denominations. The response is `WithdrawError` object.
+ :http:statuscode:`410 Gone`:
+ A requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked.
+ The response is a `DenominationExpiredMessage`. Clients must evaluate the
+ error code provided to understand which of the cases this is and handle it
+ accordingly.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
+ be upgraded to an account (with KYC) before the withdraw can
+ complete. Note that this response does NOT affirm that the
+ withdraw will ultimately complete with the requested amount.
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account before withdrawing.
+ Afterwards, the request should be repeated.
+ The response will be an `KycNeededRedirect` object.
+
+ Implementation note: internally, we need to
+ distinguish between upgrading the reserve to an
+ account (due to P2P payment) and identifying the
+ owner of the origin bank account (due to exceeding
+ the withdraw amount threshold), as we need to create
+ a different payto://-URI for the KYC check depending
+ on the case.
+
**Details:**
- .. ts:def:: ReserveClosedResponse
+ .. ts:def:: BatchWithdrawRequest
- interface ReserveClosedResponse {
+ interface BatchWithdrawRequest {
+ // Array of requests for the individual coins to withdraw.
+ planchets: WithdrawRequest[];
- // Final balance of the account.
- closing_amount: Amount;
+ }
- // Current time of the exchange, used as part of
- // what the exchange signs over.
- close_time: Timestamp;
- // Hash of the wire account into which the remaining
- // balance will be transferred. Note: may be the
- // hash over ``payto://void/`, in which case the
- // balance is forfeit to the profit of the exchange.
- h_wire: HashCode;
+ .. ts:def:: BatchWithdrawResponse
- // This is a signature over a
- // struct ``TALER_AccountDeleteConfirmationPS`` with purpose
- // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``.
+ interface BatchWithdrawResponse {
+ // Array of blinded signatures, in the same order as was
+ // given in the request.
+ ev_sigs: WithdrawResponse[];
+
+ }
+
+Withdraw with Age Restriction
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the reserve was marked with a maximum age group, the client has to perform a
+cut&choose protocol with the exchange. It first calls
+``/reserves/$RESERVE_PUB/age-withdraw`` and commits to ``n*kappa`` coins. On
+success, the exchange answers this request with an noreveal-index. The client
+then has to call ``/age-withdraw/$ACH/reveal`` to reveal all ``n*(kappa - 1)``
+coins along with their age commitments to proof that they were appropriate.
+If so, the exchange will blindly sign ``n`` undisclosed coins from the request.
+
+
+.. http:POST:: /reserves/$RESERVE_PUB/age-withdraw
+
+ Withdraw multiple coins *with age restriction* from the same reserve.
+ Note that the client should commit all of the request details, including the
+ private key of the coins and the blinding factors, to disk *before* issuing
+ this request, so that it can recover the information if necessary in case of
+ transient failures, like power outage, network outage, etc.
+
+ **Request:** The request body must be a `AgeWithdrawRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `AgeWithdrawResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
+ :http:statuscode:`403 Forbidden`:
+ A signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`409 Conflict`:
+ The balance of the reserve is not sufficient to withdraw the coins of the
+ given amount. The response is a `WithdrawError` object.
+ :http:statuscode:`410 Gone`:
+ A requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked.
+ The response is a `DenominationExpiredMessage`. Clients must evaluate the
+ error code provided to understand which of the cases this is and handle it
+ accordingly.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
+ be upgraded to an account (with KYC) before the withdraw can
+ complete. Note that this response does NOT affirm that the
+ withdraw will ultimately complete with the requested amount.
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account before withdrawing.
+ Afterwards, the request should be repeated.
+ The response will be an `KycNeededRedirect` object.
+
+ .. ts:def:: AgeWithdrawRequest
+
+ interface AgeWithdrawRequest {
+ // Commitment to the coins with age restriction. This is the SHA512
+ // hash value $ACH over all n*kappa `BlindedCoinHash` values of all
+ // coins and their age commitments. It is alter used as part of the URL
+ // in the subsequent call to /age-withdraw/$ACH/reveal.
+ age_restricted_coins_commitment: HashCode;
+
+ // The total amount that the client wants to withdraw from the reserve
+ // and must be at most the balance of the reserve. The balance of the
+ // reserve will be immediatley reduced by that amount.
+ // In the subsequent call to /age-withdraw/$ACH/reveal, the client has to
+ // provide the list of denominations (with support for age restriction)
+ // that the coins shall be signed with. The sum of the values of those
+ // denominations MUST equal this amount.
+ amount: Amount;
+
+ // The maximum age group to commit to. MUST be the same as the maximum
+ // age group in the reserve.
+ max_age_group: number;
+
+ // Signature of `TALER_AgeWithdrawRequestPS` created with
+ // the `reserves's private key <reserve-priv>`
+ // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``.
+ reserve_sig: EddsaSignature;
+ }
+
+ .. ts:def:: AgeWithdrawResponse
+
+ interface AgeWithdrawResponse {
+ // index of the commitments that the client doesn't
+ // have to disclose
+ noreveal_index: Integer;
+
+ // Signature of `TALER_AgeWithdrawRequestPS` whereby
+ // the exchange confirms the ``noreveal_index``.
exchange_sig: EddsaSignature;
+ // `Public EdDSA key <sign-key-pub>` of the exchange that was used to
+ // generate the signature. Should match one of the exchange's signing
+ // keys from ``/keys``. Again given explicitly as the client might
+ // otherwise be confused by clock skew as to which signing key was used.
+ exchange_pub: EddsaPublicKey;
}
+.. http:POST:: /age-withdraw/$ACH/reveal
+
+ The client has previously committed to multiple coins with age restriction
+ in a call to ``/reserve/$RESERVE_PUB/age-withdraw`` and got a
+ `AgeWithdrawResponse` from the exchange. By calling this
+ endpoint, the client has to reveal each coin and their ``kappa - 1``
+ age commitments, except for the age commitments with index
+ ``noreveal_index``. The hash of all commitments from the former withdraw
+ request is given as the ``$ACH`` value in the URL to this endpoint.
+
+
+ **Request:** The request body must be a `AgeWithdrawRevealRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `BlindedSignaturesResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
+ :http:statuscode:`404 Not found`:
+ The provided commitment $ACH is unknown.
+ :http:statuscode:`409 Conflict`:
+ The reveal operation failed and the response is an `WithdrawError` object.
+ The error codes indicate one of two cases:
+
+ 1. An age commitment for one of the coins did not fulfill the required
+ maximum age requirement of the corresponding reserve. Error code:
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE``.
+ 2. The sum of all denominations in the request is not equal to the amount
+ that was given in the previous commitment via the call to
+ /reserves/$RESERVE_PUB/age-withdraw. Error code:
+ ``TALER_EC_EXCHANGE_GENERIC_MISMATCH_OF_AMOUNT_AND_DENOMINATIONS``.
+
+
+ .. ts:def:: AgeWithdrawRevealRequest
+
+ interface AgeWithdrawRevealRequest {
+ // The public key of the reserve that was used for the initial commitment
+ // request. Needed for optimized database lookup.
+ reserve_pub: EddsaPublicKey;
+
+ // Array of ``n`` hash codes of denomination public keys to order.
+ // These denominations MUST support age restriction as defined in the
+ // output to /keys.
+ // The sum of all denomination's values MUST equal the original amount
+ // of the previous commitment.
+ denoms_h: HashCode[];
+
+ // Array of ``n`` entries with blinded coins, which are the non-desclosed
+ // coins in the previous commitment. They match the respective entries
+ // in ``denoms_h``.
+ coin_evs: CoinEnvelope[];
+
+ // Array of ``n`` arrays of ``kappa - 1`` disclosed coin private keys,
+ // from which the associated age commitments are also derived.
+ disclosed_coins: DisclosedAgeRestrictedCoin[][];
+
+ }
+
+ .. ts:def:: DisclosedAgeRestrictedCoin
+
+ interface DisclosedAgeRestrictedCoin {
+ // A coin's private key. The associated blinding and age commitment for
+ // this coin MUST be derived from this private key as follows:
+ //
+ // Calculate the blinding beta as
+ // beta := HKDF(coin_priv, "blinding")
+ //
+ // If the denominations are for Clause-Schnorr-Signatures, calculate the
+ // nonce as
+ // nonce := HKDF(coin_priv, "cs-nonce")
+ //
+ // Let m ∈ {1,...,M} be the maximum age group as defined in the reserve
+ // that the wallet can commit to.
+ //
+ // For age group $AG ∈ {1,...m}, set
+ // seed = HDKF(coin_priv, "age-commitment", $AG)
+ // p[$AG] = Edx25519_generate_private(seed)
+ // and calculate the corresponding Edx25519PublicKey as
+ // q[$AG] = Edx25519_public_from_private(p[$AG])
+ //
+ // For age groups $AG ∈ {m,...,M}, set
+ // f[$AG] = HDKF(coin_priv, "age-factor", $AG)
+ // and calculate the corresponding Edx25519PublicKey as
+ // q[$AG] = Edx25519_derive_public(`PublishedAgeRestrictionBaseKey`, f[$AG])
+ //
+ // Finally, with coin_priv and age commitment (q[]), the exchange
+ // will calculate the coin's public key coin_pub and use the
+ // TALER_CoinPubHashP(coin_pub, age_commitment_hash(q))
+ // during the verification of the original age-withdraw-commitment.
+ coin_priv: EddsaPrivateKey;
+
+ }
+
+ .. ts:def:: PublishedAgeRestrictionBaseKey
+
+ // The value for ``PublishedAgeRestrictionBaseKey`` is a randomly chosen
+ // `Edx25519PublicKey` for which the private key is not known to the clients. It is
+ // used during the age-withdraw protocol so that clients can proof that they
+ // derived all public keys to age groups higher than their allowed maximum
+ // from this particular value.
+ const PublishedAgeRestrictionBaseKey =
+ new Edx25519PublicKey("DZJRF6HXN520505XDAWM8NMH36QV9J3VH77265WQ09EBQ76QSKCG");
+
+
+
.. _deposit-par:
-------
Deposit
-------
-Deposit operations are requested by a merchant during a transaction. For the
-deposit operation, the merchant has to obtain the deposit permission for a coin
-from their customer who owns the coin. When depositing a coin, the merchant is
-credited an amount specified in the deposit permission, possibly a fraction of
-the total coin's value, minus the deposit fee as specified by the coin's
-denomination.
+Deposit operations are requested f.e. by a merchant during a transaction or a
+bidder during an auction.
+
+For the deposit operation during purchase, the merchant has to obtain the
+deposit permission for a coin from their customer who owns the coin. When
+depositing a coin, the merchant is credited an amount specified in the deposit
+permission, possibly a fraction of the total coin's value, minus the deposit
+fee as specified by the coin's denomination.
+
+For auctions, a bidder performs an deposit operation and provides all relevant
+information for the auction policy (such as timeout and public key as bidder)
+and can use the ``exchange_sig`` field from the `DepositSuccess` message as a
+proof to the seller for the escrow of sufficient fund.
+
.. _deposit:
@@ -1606,8 +2301,9 @@ denomination.
:http:statuscode:`200 OK`:
The operation succeeded, the exchange confirms that no double-spending took
place. The response will include a `DepositSuccess` object.
- :http:statuscode:`401 Unauthorized`:
+ :http:statuscode:`403 Forbidden`:
One of the signatures is invalid.
+ This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
Either the denomination key is not recognized (expired or invalid),
or the wire type is not recognized.
@@ -1618,7 +2314,8 @@ denomination.
residual value, or because the same public key of the coin has been
previously used with a different denomination. Which case it is
can be decided by looking at the error code
- (``TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS`` or ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
The fields of the response are the same in both cases.
The request should not be repeated again with this coin.
In this case, the response is a `DepositDoubleSpendError`.
@@ -1639,16 +2336,12 @@ denomination.
contribution: Amount;
// The merchant's account details.
+ // In case of an auction policy, it refers to the seller.
+ merchant_payto_uri: string;
+
// The salt is used to hide the ``payto_uri`` from customers
- // that learn the ``h_wire`` of the merchant.
- wire: {
- payto_uri: string;
- salt: HashCode;
- };
-
- // SHA-512 hash of the merchant's payment details from ``wire``. Although
- // strictly speaking redundant, this helps detect inconsistencies.
- h_wire: HashCode;
+ // when computing the ``h_wire`` of the merchant.
+ wire_salt: WireSalt;
// SHA-512 hash of the contract of the merchant with the customer. Further
// details are never disclosed to the exchange.
@@ -1657,29 +2350,217 @@ denomination.
// Hash of denomination RSA key with which the coin is signed.
denom_pub_hash: HashCode;
+ // IFF the corresponding denomination has support for
+ // age restriction enabled, this field MUST contain the SHA256
+ // value of the age commitment that MUST have been provided during the
+ // purchase.
+ age_commitment_hash?: AgeCommitmentHash;
+
// Exchange's unblinded RSA signature of the coin.
- ub_sig: RsaSignature;
+ ub_sig: DenominationSignature;
// Timestamp when the contract was finalized.
timestamp: Timestamp;
// Indicative time by which the exchange undertakes to transfer the funds to
- // the merchant, in case of successful payment.
+ // the merchant, in case of successful payment. A wire transfer deadline of 'never'
+ // is not allowed.
wire_transfer_deadline: Timestamp;
// EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the
// merchant for refund requests.
+ //
+ // THIS FIELD WILL BE DEPRICATED, once the refund mechanism becomes a
+ // policy via extension.
merchant_pub: EddsaPublicKey;
// Date until which the merchant can issue a refund to the customer via the
// exchange, to be omitted if refunds are not allowed.
+ //
+ // THIS FIELD WILL BE DEPRICATED, once the refund mechanism becomes a
+ // policy via extension.
refund_deadline?: Timestamp;
+ // CAVEAT: THIS IS WORK IN PROGRESS
+ // (Optional) policy for the deposit.
+ // This might be a refund, auction or escrow policy.
+ //
+ // Note that support for policies is an optional feature of the exchange.
+ // Optional features are so called "extensions" in Taler. The exchange
+ // provides the list of supported extensions, including policies, in the
+ // `ExtensionsManifestsResponse` response to the ``/keys`` endpoint.
+ policy?: DepositPolicy;
+
// Signature over `TALER_DepositRequestPS`, made by the customer with the
// `coin's private key <coin-priv>`.
coin_sig: EddsaSignature;
}
+ .. ts:def:: DenominationSignature
+
+ type DenominationSignature =
+ | RsaDenominationSignature
+ | CSDenominationSignature;
+
+ .. ts:def:: RsaDenominationSignature
+
+ interface RsaDenominationSignature {
+ cipher: "RSA";
+
+ // RSA signature
+ rsa_signature: RsaSignature;
+ }
+
+ .. ts:def:: CSDenominationSignature
+
+ interface CSDenominationSignature {
+ type: "CS";
+
+ // R value component of the signature.
+ cs_signature_r: Cs25519Point;
+
+ // s value component of the signature.
+ cs_signature_s: Cs25519Scalar:
+
+ }
+
+ .. ts:def:: DepositPolicy
+
+ type DepositPolicy =
+ | PolicyMerchantRefund
+ | PolicyBrandtVickreyAuction
+ | PolicyEscrowedPayment;
+
+ .. ts:def:: PolicyMerchantRefund
+
+ // CAVEAT: THIS IS STILL WORK IN PROGRESS.
+ // This policy is optional and might not be supported by the exchange.
+ // If it does, the exchange MUST show support for this policy in the
+ // ``extensions`` field in the response to ``/keys``.
+ interface PolicyMerchantRefund {
+ type: "merchant_refund";
+
+ // EdDSA `public key of the merchant <merchant-pub>`, so that the client
+ // can identify the merchant for refund requests.
+ merchant_pub: EddsaPublicKey;
+
+ // Date until which the merchant can issue a refund to the customer via
+ // the ``/extensions/policy_refund``-endpoint of the exchange.
+ deadline: Timestamp;
+ }
+
+ .. ts:def:: PolicyBrandtVickreyAuction
+
+ // CAVEAT: THIS IS STILL WORK IN PROGRESS.
+ // This policy is optional and might not be supported by the exchange.
+ // If it does, the exchange MUST show support for this policy in the
+ // ``extensions`` field in the response to ``/keys``.
+ interface PolicyBrandtVickreyAuction {
+ type: "brandt_vickrey_auction";
+
+ // Public key of this bidder.
+ //
+ // The bidder uses this key to sign the auction information and
+ // the messages it sends to the seller during the auction.
+ bidder_pub: EddsaPublicKey;
+
+ // Hash of the auction terms
+ //
+ // The hash should be taken over a normalized JSON object of type
+ // `BrandtVickreyAuction`.
+ h_auction: HashCode;
+
+ // The amount that this bidder commits to for this auction
+ //
+ // This amount can be larger than the contribution of a single coin.
+ // The bidder can increase funding of this auction policy by using
+ // sufficiently many coins during the deposit operation (single or batch)
+ // with the same policy.
+ commitment: Amount;
+
+ // Date until the auction must have been successfully executed and
+ // a valid transcript provided to the
+ // ``/extensions/policy_brandt_vickrey_auction``-endpoint of the
+ // exchange.
+ //
+ // [If the auction has not been executed by then] OR [has been executed
+ // before then, but this bidder did not win], the coin's value doesn't
+ // change and the owner can refresh the coin.
+ //
+ // If this bidder won the auction, the winning price/amount from the
+ // outcome will be substracted from the coin and transfered to the
+ // merchant's ``payout_uri`` from the deposit request (minus a potential
+ // auction fee). For any remaining value, the bidder can refresh the
+ // coin to retrieve change.
+ deadline: Timestamp;
+ }
+
+ .. ts:def:: BrandtVickreyAuction
+
+ // CAVEAT: THIS IS STILL WORK IN PROGRESS.
+ // This structure defines an auction of Brandt-Vickory kind.
+ // It is used for the `PolicyBrandtVickreyAuction`.
+ interface BrandtVickreyAuction {
+ // Start date of the auction
+ time_start: Timestamp;
+
+ // Maximum duration per round. There are four rounds in an auction of
+ // Brandt-Vickrey kind.
+ time_round: RelativeTime;
+
+ // This integer m refers to the (m+1)-type of the Brandt-Vickrey-auction.
+ // - Type 0 refers to an auction with one highest-price winner,
+ // - Type 1 refers to an auction with one winner, paying the second
+ // highest price,
+ // - Type 2 refers to an auction with two winners, paying
+ // the third-highest price,
+ // - etc.
+ auction_type: number;
+
+ // The vector of prices for the Brandt-Vickrey auction. The values MUST
+ // be in strictly increasing order.
+ prices: Amount[];
+
+ // The type of outcome of the auction.
+ // In case the auction is declared public, each bidder can calculate the
+ // winning price. This field is not relevant for the replay of a
+ // transcript, as the transcript must be provided by the seller who sees
+ // the winner(s) and winning price of the auction.
+ outcome_public: boolean;
+
+ // The public key of the seller.
+ pubkey: EddsaPublicKey;
+
+ // The seller's account details.
+ payto_uri: string;
+ }
+
+
+ .. ts:def:: PolicyEscrowedPayment
+
+ // CAVEAT: THIS IS STILL WORK IN PROGRESS
+ // This policy is optional and might not be supported by the exchange.
+ // If it does, the exchange MUST show support for this policy in the
+ // ``extensions`` field in the response to ``/keys``.
+ interface PolicyEscrowedPayment {
+ type: "escrowed_payment";
+
+ // Public key of this trustor, the owner of the coins.
+ //
+ // To claim the deposit, the merchant must provide the valid signature
+ // of the ``h_contract_terms`` field from the deposit, signed by _this_
+ // key, to the ``/extensions/policy_escrow``-endpoint of the exchange,
+ // after the date specified in ``not_before`` and before the date
+ // specified in ``not_after``.
+ trustor_pub: EddsaPublicKey;
+
+ // Latest date by which the deposit must be claimed. If the deposit
+ // has not been claimed by that date, the deposited coins can be
+ // refreshed by the (still) owner.
+ deadline: Timestamp;
+ }
+
+
The deposit operation succeeds if the coin is valid for making a deposit and
has enough residual value that has not already been deposited or melted.
@@ -1697,11 +2578,6 @@ denomination.
// URL, or if the base URL has changed since the deposit.
transaction_base_url?: string;
- // Payment target that the merchant should
- // use to check for its KYC status using
- // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
- payment_target_uuid: Integer;
-
// Timestamp when the deposit was received by the exchange.
exchange_timestamp: Timestamp;
@@ -1741,7 +2617,8 @@ denomination.
| CoinOldCoinRecoupTransaction
| CoinRecoupRefreshTransaction
| CoinPurseDepositTransaction
- | CoinPurseRefundTransaction;
+ | CoinPurseRefundTransaction
+ | CoinReserveOpenDepositTransaction;
.. ts:def:: CoinDepositTransaction
@@ -1947,16 +2824,16 @@ denomination.
exchange_pub: EddsaPublicKey;
// Blinding factor of the revoked new coin.
- new_coin_blinding_secret: RsaBlindingKeySecret;
+ new_coin_blinding_secret: DenominationBlindingKeySecret;
// Blinded public key of the revoked new coin.
- new_coin_ev: RsaBlindingKeySecret;
+ new_coin_ev: DenominationBlindingKeySecret;
}
.. ts:def:: CoinPurseDepositTransaction
interface CoinPurseDepositTransaction {
- type: "PURSE_DEPOSIT";
+ type: "PURSE-DEPOSIT";
// The total amount of the coin's value absorbed
// by this transaction.
@@ -1990,7 +2867,7 @@ denomination.
.. ts:def:: CoinPurseRefundTransaction
interface CoinPurseRefundTransaction {
- type: "PURSE_REFUND";
+ type: "PURSE-REFUND";
// The total amount of the coin's value restored
// by this transaction.
@@ -2003,11 +2880,6 @@ denomination.
// fee will be waived.
refund_fee: Amount;
- // Share of the purse fee charged to this coin.
- // The sum of all purse fee shares will match the
- // total purse fee.
- purse_fee_share: Amount;
-
// Public key of the purse that expired.
purse_pub: EddsaPublicKey;
@@ -2016,11 +2888,184 @@ denomination.
// of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND``.
exchange_sig: EddsaSignature;
- // Public key used to sign 'exchange_sig'.
+ // Public key used to sign 'exchange_sig'.
exchange_pub: EddsaPublicKey;
}
+ .. ts:def:: CoinReserveOpenDepositTransaction
+
+ interface CoinReserveOpenDepositTransaction {
+ type: "RESERVE-OPEN-DEPOSIT";
+
+ // The total amount of the coin's value absorbed
+ // by this transaction.
+ // Note that this means the amount given includes
+ // the deposit fee.
+ coin_contribution: Amount;
+
+ // Signature of the reserve open operation being paid for.
+ reserve_sig: EddsaSignature;
+
+ // Signature by the coin over a
+ // `TALER_ReserveOpenDepositSignaturePS` of
+ // purpose ``TALER_SIGNATURE_RESERVE_OPEN_DEPOSIT``.
+ coin_sig: EddsaSignature;
+
+ }
+
+
+.. http:POST:: /batch-deposit
+
+ Deposit multiple coins and ask the exchange to transfer the given :ref:`amount`
+ into the merchant's bank account. This API is used by the merchant to redeem
+ the digital coins.
+
+ **Request:**
+
+ The request body must be a `BatchDepositRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange confirms that no double-spending took
+ place. The response will include a `BatchDepositSuccess` object.
+ :http:statuscode:`403 Forbidden`:
+ One of the signatures is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ Either one of the denomination keys is not recognized (expired or invalid),
+ or the wire type is not recognized.
+ If a denomination key is unknown, the response will be
+ a `DenominationUnknownMessage`.
+ :http:statuscode:`409 Conflict`:
+ The deposit operation has either failed because a coin has insufficient
+ residual value, or because the same public key of a coin has been
+ previously used with a different denomination. Which case it is
+ can be decided by looking at the error code
+ (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ The fields of the response are the same in both cases.
+ The request should not be repeated again with this coin.
+ In this case, the response is a `DepositDoubleSpendError` with
+ an additional ``coin_pub`` field specifying the public key of the
+ coin that was double-spent.
+ :http:statuscode:`410 Gone`:
+ The requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked. The response is a
+ `DenominationExpiredMessage`. Clients must evaluate
+ the error code provided to understand which of the
+ cases this is and handle it accordingly.
+
+ **Details:**
+
+ .. ts:def:: BatchDepositRequest
+
+ interface BatchDepositRequest {
+
+ // The merchant's account details.
+ merchant_payto_uri: string;
+
+ // The salt is used to hide the ``payto_uri`` from customers
+ // when computing the ``h_wire`` of the merchant.
+ wire_salt: WireSalt;
+
+ // SHA-512 hash of the contract of the merchant with the customer. Further
+ // details are never disclosed to the exchange.
+ h_contract_terms: HashCode;
+
+ // The list of coins that are going to be deposited with this Request.
+ coins: BatchDepositRequestCoin[];
+
+ // Timestamp when the contract was finalized.
+ timestamp: Timestamp;
+
+ // Indicative time by which the exchange undertakes to transfer the funds to
+ // the merchant, in case of successful payment. A wire transfer deadline of 'never'
+ // is not allowed.
+ wire_transfer_deadline: Timestamp;
+
+ // EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the
+ // merchant for refund requests.
+ merchant_pub: EddsaPublicKey;
+
+ // Date until which the merchant can issue a refund to the customer via the
+ // exchange, to be omitted if refunds are not allowed.
+ //
+ // THIS FIELD WILL BE DEPRICATED, once the refund mechanism becomes a
+ // policy via extension.
+ refund_deadline?: Timestamp;
+
+ // CAVEAT: THIS IS WORK IN PROGRESS
+ // (Optional) policy for the batch-deposit.
+ // This might be a refund, auction or escrow policy.
+ policy?: DepositPolicy;
+ }
+
+ .. ts:def:: BatchDepositRequestCoin
+
+ interface BatchDepositRequestCoin {
+ // EdDSA public key of the coin being deposited.
+ coin_pub: EddsaPublicKey;
+
+ // Hash of denomination RSA key with which the coin is signed.
+ denom_pub_hash: HashCode;
+
+ // Exchange's unblinded RSA signature of the coin.
+ ub_sig: DenominationSignature;
+
+ // Amount to be deposited, can be a fraction of the
+ // coin's total value.
+ contribution: Amount;
+
+ // Signature over `TALER_DepositRequestPS`, made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+ }
+
+ The deposit operation succeeds if the coin is valid for making a deposit and
+ has enough residual value that has not already been deposited or melted.
+
+ .. ts:def:: BatchDepositSuccess
+
+ interface BatchDepositSuccess {
+ // Optional base URL of the exchange for looking up wire transfers
+ // associated with this transaction. If not given,
+ // the base URL is the same as the one used for this request.
+ // Can be used if the base URL for ``/transactions/`` differs from that
+ // for ``/coins/``, i.e. for load balancing. Clients SHOULD
+ // respect the ``transaction_base_url`` if provided. Any HTTP server
+ // belonging to an exchange MUST generate a 307 or 308 redirection
+ // to the correct base URL should a client uses the wrong base
+ // URL, or if the base URL has changed since the deposit.
+ transaction_base_url?: string;
+
+ // Timestamp when the deposit was received by the exchange.
+ exchange_timestamp: Timestamp;
+
+ // `Public EdDSA key of the exchange <sign-key-pub>` that was used to
+ // generate the signature.
+ // Should match one of the exchange's signing keys from ``/keys``. It is given
+ // explicitly as the client might otherwise be confused by clock skew as to
+ // which signing key was used.
+ exchange_pub: EddsaPublicKey;
+
+ // Array of deposit confirmation signatures from the exchange
+ // Entries must be in the same order the coins were given
+ // in the batch deposit request.
+ exchange_sigs: DepositConfirmationSignature[];
+ }
+
+ .. ts:def:: DepositConfirmationSignature
+
+ interface DepositConfirmationSignature {
+ // The EdDSA signature of `TALER_DepositConfirmationPS` using a current
+ // `signing key of the exchange <sign-key-priv>` affirming the successful
+ // deposit and that the exchange will transfer the funds after the refund
+ // deadline, or as soon as possible if the refund deadline is zero.
+ exchange_sig: EddsaSignature;
+ }
+
----------
Refreshing
@@ -2038,6 +3083,73 @@ using the ``/refresh/link`` request. While ``/refresh/link`` must be implemente
the exchange to achieve taxability, wallets do not really ever need that part of
the API during normal operation.
+
+.. http:post:: /csr-melt
+
+ Obtain exchange-side input values in preparation for a
+ melt step for certain denomination cipher types,
+ specifically at this point for Clause-Schnorr blind
+ signatures.
+
+ **Request:** The request body must be a `MeltPrepareRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `MeltPrepareResponse`. Note that repeating exactly the same request
+ will again yield the same response (assuming none of the denomination is expired).
+ :http:statuscode:`404 Not found`:
+ A denomination key is not known to the exchange.
+ :http:statuscode:`410 Gone`:
+ A requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked. The response is a
+ `DenominationExpiredMessage`. Clients must evaluate
+ the error code provided to understand which of the
+ cases this is and handle it accordingly.
+
+ **Details:**
+
+ .. ts:def:: MeltPrepareRequest
+
+ interface WithdrawPrepareRequest {
+
+ // Master seed for the Clause-schnorr R-value
+ // creation.
+ // Must not have been used in any prior request.
+ rms: RefreshMasterSeed;
+
+ // Array of denominations and coin offsets for
+ // each of the fresh coins with a CS-cipher
+ // denomination.
+ nks: MeltPrepareDenomNonce[];
+
+ }
+
+ .. ts:def:: MeltPrepareDenomNonce
+
+ interface MeltPrepareDenomNonce {
+
+ // Offset of this coin in the list of
+ // fresh coins. May not match the array offset
+ // as the fresh coins may include non-CS
+ // denominations as well.
+ coin_offset: Integer;
+
+ // Hash of the public key of the denomination the
+ // request relates to. Must be a CS denomination type.
+ denom_pub_hash: HashCode;
+ }
+
+
+ .. ts:def:: MeltPrepareResponse
+
+ interface MeltPrepareResponse {
+ // Responses for each request, in the same
+ // order that was used in the request.
+ ewvs: ExchangeWithdrawValue[];
+ }
+
+
.. _refresh:
.. http:post:: /coins/$COIN_PUB/melt
@@ -2065,7 +3177,8 @@ the API during normal operation.
residual value, or because the same public key of the coin has been
previously used with a different denomination. Which case it is
can be decided by looking at the error code
- (``TALER_EC_EXCHANGE_MELT_INSUFFICIENT_FUNDS`` or ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
The response is `MeltForbiddenResponse` in both cases.
:http:statuscode:`410 Gone`:
The requested denomination key is not yet or no longer valid.
@@ -2085,7 +3198,7 @@ the API during normal operation.
denom_pub_hash: HashCode;
// Signature over the `coin public key <eddsa-coin-pub>` by the denomination.
- denom_sig: RsaSignature;
+ denom_sig: DenominationSignature;
// Signature by the `coin <coin-priv>` over the melt commitment.
confirm_sig: EddsaSignature;
@@ -2098,6 +3211,17 @@ the API during normal operation.
// See also ``TALER_refresh_get_commitment()``.
rc: TALER_RefreshCommitmentP;
+ // Master seed for the Clause-schnorr R-value
+ // creation. Must match the /csr-melt request.
+ // Must not have been used in any prior melt request.
+ // Must be present if one of the fresh coin's
+ // denominations is of type Clause-Schnorr.
+ rms?: RefreshMasterSeed;
+
+ // IFF the denomination has age restriction support, the client MUST
+ // provide the SHA256 hash of the age commitment of the coin.
+ // MUST be omitted otherwise.
+ age_commitment_hash?: AgeCommitmentHash;
}
For details about the HKDF used to derive the new coin private keys and
@@ -2148,18 +3272,6 @@ the API during normal operation.
// Detailed error code.
code: Integer;
- // Public key of a melted coin that had insufficient funds.
- coin_pub: EddsaPublicKey;
-
- // Original total value of the coin.
- original_value: Amount;
-
- // Remaining value of the coin.
- residual_value: Amount;
-
- // Amount of the coin's value that was to be melted.
- requested_value: Amount;
-
// The transaction list of the respective coin that failed to have sufficient funds left.
// Note that only the transaction history for one bogus coin is given,
// even if multiple coins would have failed the check.
@@ -2232,6 +3344,13 @@ the API during normal operation.
// Signs over a `TALER_CoinLinkSignaturePS`.
link_sigs: EddsaSignature[];
+ // IFF the corresponding denomination has support for age restriction,
+ // the client MUST provide the original age commitment, i. e. the
+ // vector of public keys.
+ // The size of the vector MUST be the number of age groups as defined by the
+ // Exchange in the field ``.age_groups`` of the extension ``age_restriction``.
+ old_age_commitment?: Edx25519PublicKey[];
+
}
@@ -2239,7 +3358,7 @@ the API during normal operation.
interface RevealResponse {
// List of the exchange's blinded RSA signatures on the new coins.
- ev_sigs : Array<{ ev_sig: BlindedRsaSignature }>;
+ ev_sigs : Array<{ ev_sig: BlindedDenominationSignature }>;
}
@@ -2267,9 +3386,7 @@ the API during normal operation.
**Response:**
:http:statuscode:`200 OK`:
- All commitments were revealed successfully. The exchange returns an array,
- typically consisting of only one element, in which each each element contains
- information about a melting session that the coin was used in.
+ All commitments were revealed successfully. The exchange returns an array (typically consisting of only one element), in which each each element of the array contains a `LinkResponse` entry with information about a melting session that the coin was used in.
:http:statuscode:`404 Not found`:
The exchange has no linkage data for the given public key, as the coin has not
yet been involved in a refresh operation.
@@ -2295,11 +3412,19 @@ the API during normal operation.
denom_pub: RsaPublicKey;
// Exchange's blinded signature over the fresh coin.
- ev_sig: BlindedRsaSignature;
+ ev_sig: BlindedDenominationSignature;
// Blinded coin.
coin_ev : CoinEnvelope;
+ // Values contributed by the exchange during the
+ // withdraw operation (see /csr-melt).
+ ewv: ExchangeWithdrawValue;
+
+ // Offset of this coin in the refresh operation.
+ // Input needed to derive the private key.
+ coin_idx: Integer;
+
// Signature made by the old coin over the refresh request.
// Signs over a `TALER_CoinLinkSignaturePS`.
link_sig: EddsaSignature;
@@ -2329,11 +3454,10 @@ in using this API.
exchange. The exchange MUST return a 307 or 308 redirection to the correct
base URL if this is the case.
- Depending whether ``$COIN_PUB`` is a withdrawn coin or a refreshed coin,
- the remaining amount on the coin will be credited either on the reserve or
- the old coin that ``$COIN_PUB`` was withdrawn/refreshed from.
+ The remaining amount on the coin will be credited to the reserve
+ that ``$COIN_PUB`` was withdrawn from.
- Note that the original withdrawal/refresh fees will **not** be recouped.
+ Note that the original withdrawal fees will **not** be recouped.
**Request:** The request body must be a `RecoupRequest` object.
@@ -2341,13 +3465,14 @@ in using this API.
**Response:**
:http:statuscode:`200 OK`:
- The request was successful, and the response is a `RecoupConfirmation`.
+ The request was successful, and the response is a `RecoupWithdrawalConfirmation`.
Note that repeating exactly the same request
will again yield the same response, so if the network goes down during the
transaction or before the client can commit the coin signature to disk, the
coin is not lost.
- :http:statuscode:`401 Unauthorized`:
+ :http:statuscode:`403 Forbidden`:
The coin's signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
The denomination key is unknown, or the blinded
coin is not known to have been withdrawn.
@@ -2358,8 +3483,7 @@ in using this API.
residual value, or because the same public key of the coin has been
previously used with a different denomination. Which case it is
can be decided by looking at the error code
- (``TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO`` or
- ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ (usually ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS``).
The response is a `DepositDoubleSpendError`.
:http:statuscode:`410 Gone`:
The requested denomination key is not yet or no longer valid.
@@ -2373,49 +3497,120 @@ in using this API.
.. ts:def:: RecoupRequest
interface RecoupRequest {
- // Hash of denomination public key (RSA), specifying the type of coin the client
+ // Hash of denomination public key, specifying the type of coin the client
// would like the exchange to pay back.
denom_pub_hash: HashCode;
// Signature over the `coin public key <eddsa-coin-pub>` by the denomination.
- denom_sig: RsaSignature;
+ denom_sig: DenominationSignature;
- // Coin's blinding factor.
- coin_blind_key_secret: RsaBlindingKeySecret;
+ // Exchange-contributed values during the refresh
+ // operation (see /csr-withdraw).
+ ewv: ExchangeWithdrawValue;
// Signature of `TALER_RecoupRequestPS` created with
// the `coin's private key <coin-priv>`.
coin_sig: EddsaSignature;
- // Was the coin refreshed (and thus the recoup should go to the old coin)?
- // While this information is technically redundant, it helps the exchange
- // to respond faster.
- // *Optional* (for backwards compatibility); if absent, ``false`` is assumed.
- refreshed?: boolean;
- }
-
+ // Coin's blinding factor.
+ coin_blind_key_secret: DenominationBlindingKeySecret;
- .. ts:def:: RecoupConfirmation
+ // Nonce that was used by the exchange to derive
+ // its private inputs from during withdraw. Only
+ // present if the cipher of the revoked denomination
+ // is of type Clause-Schnorr (CS).
+ cs_nonce?: CSNonce;
+ }
- type RecoupConfirmation = | RecoupRefreshConfirmation
- | RecoupWithdrawalConfirmation;
.. ts:def:: RecoupWithdrawalConfirmation
interface RecoupWithdrawalConfirmation {
- // Tag to distinguish the `RecoupConfirmation` response type.
- refreshed: false;
-
// Public key of the reserve that will receive the recoup.
reserve_pub: EddsaPublicKey;
}
+
+.. http:post:: /coins/$COIN_PUB/recoup-refresh
+
+ Demand that a coin be refunded via wire transfer to the original owner.
+
+ The base URL for ``/coins/``-requests may differ from the main base URL of the
+ exchange. The exchange MUST return a 307 or 308 redirection to the correct
+ base URL if this is the case.
+
+ The remaining amount on the coin will be credited to
+ the old coin that ``$COIN_PUB`` was refreshed from.
+
+ Note that the original refresh fees will **not** be recouped.
+
+
+ **Request:** The request body must be a `RecoupRefreshRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `RecoupRefreshConfirmation`.
+ Note that repeating exactly the same request
+ will again yield the same response, so if the network goes down during the
+ transaction or before the client can commit the coin signature to disk, the
+ coin is not lost.
+ :http:statuscode:`403 Forbidden`:
+ The coin's signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The denomination key is unknown, or the blinded
+ coin is not known to have been withdrawn.
+ If the denomination key is unknown, the response will be
+ a `DenominationUnknownMessage`.
+ :http:statuscode:`409 Conflict`:
+ The operation is not allowed as the coin has insufficient
+ residual value, or because the same public key of the coin has been
+ previously used with a different denomination. Which case it is
+ can be decided by looking at the error code
+ (usually ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_BALANCE``).
+ The response is a `DepositDoubleSpendError`.
+ :http:statuscode:`410 Gone`:
+ The requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was not yet revoked. The response is a
+ `DenominationExpiredMessage`. Clients must evaluate
+ the error code provided to understand which of the
+ cases this is and handle it accordingly.
+
+ **Details:**
+
+ .. ts:def:: RecoupRefreshRequest
+
+ interface RecoupRefreshRequest {
+ // Hash of denomination public key, specifying the type of coin the client
+ // would like the exchange to pay back.
+ denom_pub_hash: HashCode;
+
+ // Signature over the `coin public key <eddsa-coin-pub>` by the denomination.
+ denom_sig: DenominationSignature;
+
+ // Exchange-contributed values during the refresh
+ // operation (see /csr-melt).
+ ewv: ExchangeWithdrawValue;
+
+ // Signature of `TALER_RecoupRequestPS` created with
+ // the `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+
+ // Coin's blinding factor.
+ coin_blind_key_secret: DenominationBlindingKeySecret;
+
+ // Nonce that was used by the exchange to derive
+ // its private inputs from during withdraw. Only
+ // present if the cipher of the revoked denomination
+ // is of type Clause-Schnorr (CS).
+ cs_nonce?: CSNonce;
+ }
+
+
.. ts:def:: RecoupRefreshConfirmation
interface RecoupRefreshConfirmation {
- // Tag to distinguish the `RecoupConfirmation` response type.
- refreshed: true;
-
// Public key of the old coin that will receive the recoup.
old_coin_pub: EddsaPublicKey;
}
@@ -2480,8 +3675,8 @@ typically also view the balance.)
// Public key of the merchant (identical for all deposits).
merchant_pub: EddsaPublicKey;
- // Hash of the wire details (identical for all deposits).
- h_wire: HashCode;
+ // Hash of the payto:// account URI (identical for all deposits).
+ h_payto: PaytoHash;
// Time of the execution of the wire transfer by the exchange.
execution_time: Timestamp;
@@ -2539,8 +3734,9 @@ typically also view the balance.)
executed. Hence the exchange does not yet have a wire transfer identifier. The
merchant should come back later and ask again.
The response body is a `TrackTransactionAcceptedResponse`.
- :http:statuscode:`401 Unauthorized`:
+ :http:statuscode:`403 Forbidden`:
A signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
The deposit operation is unknown to the exchange.
@@ -2550,11 +3746,6 @@ typically also view the balance.)
interface TrackTransactionResponse {
- // Payment target that the merchant should
- // use to check for its KYC status using
- // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
- payment_target_uuid: Integer;
-
// Raw wire transfer identifier of the deposit.
wtid: Base32;
@@ -2583,12 +3774,22 @@ typically also view the balance.)
interface TrackTransactionAcceptedResponse {
- // Payment target that the merchant should
+ // Legitimization target that the merchant should
// use to check for its KYC status using
- // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
- payment_target_uuid: Integer;
+ // the ``/kyc-check/$REQUIREMENT_ROW/...`` endpoint.
+ // Optional, not present if the deposit has not
+ // yet been aggregated to the point that a KYC
+ // need has been evaluated.
+ requirement_row?: Integer;
+
+ // True if the KYC check for the merchant has been
+ // satisfied. False does not mean that KYC
+ // is strictly needed, unless also a
+ // legitimization_uuid is provided.
+ kyc_ok: boolean;
// Time by which the exchange currently thinks the deposit will be executed.
+ // Actual execution may be later if the KYC check is not satisfied by then.
execution_time: Timestamp;
}
@@ -2609,7 +3810,7 @@ Refunds
:http:statuscode:`200 OK`:
The operation succeeded, the exchange confirms that the coin can now be refreshed. The response will include a `RefundSuccess` object.
- :http:statuscode:`401 Unauthorized`:
+ :http:statuscode:`403 Forbidden`:
Merchant signature is invalid.
This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
@@ -2698,32 +3899,23 @@ Refunds
Wallet-to-wallet transfers
--------------------------
- .. note::
-
- This is a draft API that is not yet implemented.
-
+.. http:GET:: /purses/$PURSE_PUB/merge
+.. http:GET:: /purses/$PURSE_PUB/deposit
-.. http:GET:: /purses/$PURSE_PUB
-
- Obtain information about a purse. The request header must
- contain a *Purse-Request-Signature*. Endpoint used by
- the party that did not create the purse.
+ Obtain information about a purse. Depending on the suffix,
+ the long-polling (if any) will wait for either a merge or
+ a deposit event.
**Request:**
- *Purse-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$PURSE_PRIV``, affirming its authorization to download the purse status. The purpose used MUST be ``TALER_SIGNATURE_PURSE_STATUS_REQUEST``.
-
- :query merge_timeout_ms=NUMBER: *Optional.* If specified,
+ :query timeout_ms=NUMBER: *Optional.* If specified,
the exchange
- will wait up to ``timeout_ms`` milliseconds for completion
+ will wait up to ``NUMBER`` milliseconds for completion
of a merge operation before sending the HTTP response.
:query deposit_timeout_ms=NUMBER: *Optional.* If specified,
the exchange
- will wait up to ``timeout_ms`` milliseconds for completion
+ will wait up to ``NUMBER`` milliseconds for completion
of a deposit operation before sending the HTTP response.
- :query contract=BOOLEAN: *Optional.* If 'false' is specified,
- the exchange will not return the encrypted contract, saving
- bandwidth for clients that already know it.
**Response:**
@@ -2731,11 +3923,10 @@ Wallet-to-wallet transfers
The operation succeeded, the exchange provides details
about the purse.
The response will include a `PurseStatus` object.
- :http:statuscode:`401 Unauthorized`:
- The *Purse-Request-Signature* is invalid.
- This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
The purse is unknown to the exchange.
+ :http:statuscode:`410 Gone`:
+ The purse expired before the deposit or merge was completed.
**Details:**
@@ -2748,26 +3939,18 @@ Wallet-to-wallet transfers
// exceeds 'merge_value_after_fees', and a
// 'merge_request' exists for the purse, then the
// purse will (have been) merged with the account.
- total_deposit_amount: Amount;
-
- // Indicative time by which the purse expires
- // if it has not been merged into an account. At this
- // point, all of the deposits made will be auto-refunded.
- purse_expiration: Timestamp;
-
- // Desired total amount to be merged into the reserve.
- // (excludes fees).
- merge_value_after_fees: Amount;
-
- // Indicative time at which the exchange is answering the
- // status request. Used as part of 'exchange_sig'.
- status_timestamp: Timestamp;
+ balance: Amount;
- // Deposit fees charged so far to all deposited coins.
- deposit_fees: Amount;
+ // Time of the merge, missing if "never".
+ merge_timestamp?: Timestamp;
- // SHA-512 hash of the contact of the purse.
- h_contract_terms: HashCode;
+ // Time of the deposits being complete, missing if "never".
+ // Note that this time may not be "stable": once sufficient
+ // deposits have been made, is "now" before the purse
+ // expiration, and otherwise set to the purse expiration.
+ // However, this should also not be relied upon. The key
+ // property is that it is either "never" or in the past.
+ deposit_timestamp?: Timestamp;
// EdDSA signature of the exchange over a
// `TALER_PurseStatusResponseSignaturePS`
@@ -2778,26 +3961,17 @@ Wallet-to-wallet transfers
// EdDSA public key exchange used for 'exchange_sig'.
exchange_pub: EddsaPublicKey;
- // AES-GCM Encrypted contract terms using encryption
- // key derived from DH of 'contract_pub' and the 'purse_pub'.
- // Optional, may be omitted if not desired by the client.
- e_contract_terms?: string;
-
- // If a merge request was received, information about the
- // merge request. Omitted if the purse has not yet received
- // a merge request.
- merge_request?: MergeRequest;
-
}
-.. http:POST:: /purses/$PURSE_PUB/deposit
- Deposit money into a purse. Endpoint used by the buyer.
+.. http:POST:: /purses/$PURSE_PUB/create
+
+ Create a purse by depositing money into it. First step of a PUSH payment.
**Request:**
- The request body must be a `PurseRequest` object.
+ The request body must be a `PurseCreate` object.
**Response:**
@@ -2805,138 +3979,133 @@ Wallet-to-wallet transfers
The operation succeeded, the exchange confirms that all
coins were deposited into the purse.
The response will include a `PurseDepositSuccess` object.
- :http:statuscode:`202 Accepted`:
- The payment was accepted, but insufficient to reach the
- specified purse balance. If an encrypted contract was
- provided, it will have been stored in the database.
- The client should make further
- purse deposits before the expiration deadline.
- The response will include a `PurseDepositAccepted` object.
- :http:statuscode:`401 Unauthorized`:
- A coin signature is invalid. The response will
- include a `PurseDepositSignatureErrorDetail`
:http:statuscode:`403 Forbidden`:
- The server is denying the operation as a purse with a
- different contract or total amount already exists.
- This response comes with a standard `PurseConflict` response.
- :http:statuscode:`404 Not found`:
- FIXME: when exactly does this happen?
+ A coin, denomination or contract signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not Found`:
+ The denomination of one of the coins is unknown to the exchange.
:http:statuscode:`409 Conflict`:
The deposit operation has either failed because a coin has insufficient
residual value, or because the same public key of the coin has been
- previously used with a different denomination. Which case it is
+ previously used with a different denomination, or because a purse with
+ the same public key but different meta data was created previously.
+ Which case it is
can be decided by looking at the error code
- (``TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS`` or
- ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
- The fields of the response are the same in both cases.
- The request should not be repeated again with this coin.
- In this case, the response is a `PurseDepositDoubleSpendError`.
- If the value of all successful coins is below the purse fee,
- the exchange may not setup the purse at all. The encrypted
- contract will not have been associated with the purse if this
- status code is returned. However, all coins that were not
- double-spent will have been deposited into the purse.
+ (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or
+ ``TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA`` or
+ ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA`` or
+ ``TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA``).
+ The specific fields of the response depend on the error code
+ and include the signatures (and what was signed over) proving the
+ conflict.
:http:statuscode:`425 Too Early`:
This response type is used if the given purse expiration time
is too far in the future (at least from the perspective
of the exchange). Thus, retrying at a later time may
- succeed. The client should look at the ``Date:`` header of the response to see if a minor time difference is to blame and possibly adjust the request accordingly.
+ succeed. The client should look at the ``Date:`` header
+ of the response to see if a minor time difference is to
+ blame and possibly adjust the request accordingly.
+ (Note: this status code is not yet used.)
**Details:**
- .. ts:def:: PurseRequest
+ .. ts:def:: PurseCreate
- interface PurseRequest {
+ interface PurseCreate {
+
+ // Total value of the purse, excluding fees.
+ amount: Amount;
+
+ // Minimum age required for all coins deposited into the purse.
+ min_age: Integer;
+
+ // Optional encrypted contract, in case the buyer is
+ // proposing the contract and thus establishing the
+ // purse with the payment.
+ econtract?: EncryptedContract;
+
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKey;
// EdDSA signature of the purse over a
// `TALER_PurseRequestSignaturePS`
- // of purpose ``TALER_SIGNATURE_PURSE_REQUEST``
+ // of purpose ``TALER_SIGNATURE_WALLET_PURSE_CREATE``
// confirming the key
// invariants associated with the purse.
// (amount, h_contract_terms, expiration).
purse_sig: EddsaSignature;
- // Total amount to be paid into the purse.
- // Clients may make several requests, i.e. if a
- // first request failed with a double-spending error.
- // The exchange will confirm the creation of the
- // purse once the amount given here is reached.
- merge_value_after_fees: Amount;
-
// SHA-512 hash of the contact of the purse.
h_contract_terms: HashCode;
- // Client-side timestamp of when the payment was made.
- payment_timestamp: Timestamp;
+ // Array of coins being deposited into the purse.
+ // Maximum length is 128.
+ deposits: PurseDeposit[];
// Indicative time by which the purse should expire
// if it has not been merged into an account. At this
// point, all of the deposits made will be auto-refunded.
purse_expiration: Timestamp;
- // Optional encrypted contract, in case the buyer is
- // proposing the contract and thus establishing the
- // purse with the payment.
- contract?: EncryptedContract;
-
- // Array of coins being deposited into the purse.
- // Maximum length is 128.
- deposits: PurseDeposit[];
}
- .. ts:def:: EncryptedContract
+ .. ts:def:: EncryptedContract
interface EncryptedContract {
- // ECDH contract_public key used to encrypt the contract.
- // Optional as the contract terms may already be known
- // to the exchange or the other wallet from a different
- // interaction.
- contract_pub: TALER_EcdhEphemeralPublicKeyP;
+ // Encrypted contract.
+ econtract: string;
+
+ // Signature over the (encrypted) contract.
+ econtract_sig: EddsaSignature;
+
+ // Ephemeral public key for the DH operation to decrypt the encrypted contract.
+ contract_pub: EddsaPublicKey;
- // AES-GCM Encrypted contract terms using encryption
- // key derived from DH of ``contract_pub`` and the ``purse_pub``.
- // Optional as the contract terms may already be known
- // to the exchange or the other wallet from a different
- // interaction.
- e_contract_terms: string;
}
.. ts:def:: PurseDeposit
interface PurseDeposit {
- // Public key of the coin being deposited into the purse.
- coin_pub: EddsaPublicKey;
-
// Amount to be deposited, can be a fraction of the
// coin's total value.
- contribution: Amount;
+ amount: Amount;
// Hash of denomination RSA key with which the coin is signed.
denom_pub_hash: HashCode;
// Exchange's unblinded RSA signature of the coin.
- ub_sig: RsaSignature;
+ ub_sig: DenominationSignature;
+
+ // Age commitment for the coin, if the denomination is age-restricted.
+ age_commitment?: AgeCommitment;
+
+ // Attestation for the minimum age, if the denomination is age-restricted.
+ attest?: Attestation;
// Signature over `TALER_PurseDepositSignaturePS`
- // of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT``
+ // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT``
// made by the customer with the
// `coin's private key <coin-priv>`.
coin_sig: EddsaSignature;
+ // Public key of the coin being deposited into the purse.
+ coin_pub: EddsaPublicKey;
+
}
.. ts:def:: PurseDepositSuccess
interface PurseDepositSuccess {
- // Total amount paid into the purse.
- total_purse_amount: Amount;
+ // Total amount deposited into the purse so far (without fees).
+ total_deposited: Amount;
- // Total deposit fees charged.
- total_deposit_fees: Amount;
+ // Time at the exchange.
+ exchange_timestamp: Timestamp;
// EdDSA signature of the exchange affirming the payment,
// of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT_CONFIRMED``
@@ -2950,39 +4119,27 @@ Wallet-to-wallet transfers
}
- .. ts:def:: PurseDepositAccepted
-
- interface PurseDepositAccepted {
-
- // Total amount paid so far into the purse, in this
- // and previous requests.
- total_amount_deposited: Amount;
-
- // Total amount contributed by the current request.
- total_amount_contributed: Amount;
-
- }
-
.. ts:def:: PurseConflict
- // Union discriminated by the "type" field.
+ // Union discriminated by the "code" field.
type PurseConflict =
- | PurseMergeConflict
- | PurseRequestConflict;
+ | DepositDoubleSpendError
+ | PurseCreateConflict
+ | PurseDepositConflict
+ | PurseContractConflict;
- .. ts:def:: PurseMergeConflict
+ .. ts:def:: PurseCreateConflict
- interface PurseMergeConflict {
- type: "MERGE";
-
- // SHA-512 hash of the contact of the purse.
- h_contract_terms: HashCode;
+ interface PurseCreateConflict {
+ // Must be equal to TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA
+ code: Integer;
- // Hash of the wire details of the reserve.
- h_wire: HashCode;
+ // Total amount to be merged into the reserve.
+ // (excludes fees).
+ amount: Amount;
- // Reserve merging the purse.
- reserve_pub: EddsaPublicKey;
+ // Minimum age required for all coins deposited into the purse.
+ min_age: Integer;
// Indicative time by which the purse should expire
// if it has not been merged into an account. At this
@@ -2990,136 +4147,125 @@ Wallet-to-wallet transfers
// auto-refunded.
purse_expiration: Timestamp;
- // When was the merge request generated.
- merge_timestamp: Timestamp;
-
- // Total amount to be merged into the reserve.
- // (excludes fees).
- merge_value_after_fees: Amount;
-
// EdDSA signature of the purse over
// `TALER_PurseMergeSignaturePS` of
- // purpose ``TALER_SIGNATURE_PURSE_MERGE``
+ // purpose ``TALER_SIGNATURE_WALLET_PURSE_MERGE``
// confirming that the
// above details hold for this purse.
purse_sig: EddsaSignature;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKey;
}
- .. ts:def:: PurseRequestConflict
+ .. ts:def:: PurseDepositConflict
- interface PurseRequestConflict {
- type: "REQUEST";
+ interface PurseDepositConflict {
+ // Must be equal to TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA
+ code: Integer;
- // SHA-512 hash of the contact of the purse.
- h_contract_terms: HashCode;
+ // Public key of the coin being deposited into the purse.
+ coin_pub: EddsaPublicKey;
- // Indicative time by which the purse should expire
- // if it has not been merged into an account. At this
- // point, all of the deposits made should be
- // auto-refunded.
- purse_expiration: Timestamp;
+ // Signature over `TALER_PurseDepositSignaturePS`
+ // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT``
+ // made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
- // Total amount to be paid into the purse as per
- // the previous request.
- total_purse_amount: Amount;
+ // Target exchange URL for the purse. Not present for the
+ // same exchange.
+ partner_url?: string;
- // EdDSA signature of the purse over
- // `TALER_PurseRequestSignaturePS` of
- // purpose ``TALER_SIGNATURE_PURSE_REQUEST``
- // confirming that the
- // above details hold for this purse.
- purse_sig: EddsaSignature;
+ // Amount to be contributed to the purse by this coin.
+ amount: Amount;
}
- .. ts:def:: PurseDepositDoubleSpendError
+ .. ts:def:: PurseContractConflict
- interface DepositDoubleSpendError {
- // Taler error code.
- code: number;
+ interface PurseContractConflict {
+ // Must be equal to TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA
+ code: Integer;
+
+ // Hash of the encrypted contract.
+ h_econtract: HashCode;
- // Human-readable description of the error, i.e. "insufficient funds".
- hint?: string;
+ // Signature over the contract.
+ econtract_sig: EddsaSignature;
- // Total amount contributed by the current request.
- // Note that some coins may have been successfully
- // deposited into the purse, so the total amount
- // from these coins is listed here.
- total_amount_contributed: Amount;
+ // Ephemeral public key for the DH operation to decrypt the contract.
+ contract_pub: EddsaPublicKey;
- // Public keys of coins that could not be deposited
- // into the purse, mapped to the coin's histories.
- coin_map: EddsaPublicKey -> CoinSpendHistoryItem[];
}
- .. ts:def:: PurseDepositSignatureErrorDetail
- interface PurseDepositSignatureErrorDetail {
- // Taler error code, summarizing the problem.
- // Note that for problems about specific
- // coins, the 'coin_error_map' should be consulted.
- // The 'coin_error_map' will be empty if the
- // 'purse_sig' was invalid. In this case,
- // the coins will not even have been checked by
- // the exchange.
- code: number;
+.. http:delete:: /purses/$PURSE_PUB
- // Human-readable description of the error, i.e. "invalid siganture".
- hint?: string;
+ Delete a purse that is unmerged and not yet expired. Refunds any money that
+ is already in the purse.
- // Total amount contributed by the current request.
- // Note that some coins may have been successfully
- // deposited into the purse, so the total amount
- // from these coins is listed here.
- total_amount_contributed: Amount;
+ **Request:**
- // Public keys of coins that could not be deposited
- // into the purse, mapped to the coin's histories.
- coin_error_map: EddsaPublicKey -> ErrorDetail[];
- }
+ The request body must be empty, as recommended for HTTP delete in general.
+ To authorize the request, the header must contain a
+ ``Taler-Purse-Signature: $PURSE_SIG`` where ``$PURSE_SIG`` is the Crockford base32-encoded
+ EdDSA signature of purpose TALER_SIGNATURE_WALLET_PURSE_DELETE.
+
+ **Response:**
+
+ :http:statuscode:`204 No Content`:
+ The operation succeeded, the exchange confirms that the purse
+ was deleted.
+ :http:statuscode:`403 Forbidden`:
+ The signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not Found`:
+ The purse is not known. Might have already been deleted previously.
+ :http:statuscode:`409 Conflict`:
+ It is too late to delete the purse, its fate (merge or expiration)
+ was already decided.
.. http:POST:: /purses/$PURSE_PUB/merge
Merge purse with account, adding the value of the purse into
- the account. Endpoint to be used by the seller.
+ the account. Endpoint to be used by the receiver of a PUSH payment.
**Request:**
The request body must be a `MergeRequest` object.
- :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
- wait up to ``timeout_ms`` milliseconds to receive payment before
- reporting on the completion of merge operation. Basically
- forstalls returning a 202 response for up to timeout milliseconds
- to possibly return a 200 response instead.
-
**Response:**
:http:statuscode:`200 OK`:
The operation succeeded, the exchange confirms that the
funds were merged into the account.
The response will include a `MergeSuccess` object.
- :http:statuscode:`202 Accepted`:
- The operation succeeded, the exchange confirms that the
- merge request is valid. Alas, the purse was still not
- funded and thus the actual merge is delayed.
- The response will include a `MergeAccepted` object.
- :http:statuscode:`401 Unauthorized`:
- Account signature is invalid.
+ :http:statuscode:`402 Payment Required`:
+ The purse is not yet full and more money needs to be deposited
+ before the merge can be made.
+ :http:statuscode:`403 Forbidden`:
+ The signature of the merge request or the reserve was invalid.
This response comes with a standard `ErrorDetail` response.
:http:statuscode:`404 Not found`:
- The refund operation failed as we could not find the purse.
+ The merge operation failed as we could not find the purse
+ or the partner exchange.
This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`409 Conflict`:
+ The purse was already merged into a different reserve.
+ The response will include a `MergeConflict` object.
:http:statuscode:`410 Gone`:
The purse has already expired and thus can no longer be merged.
This response comes with a standard `ErrorDetail` response.
- :http:statuscode:`429 Too Many Requests`:
- This account is not at this exchange, has not yet passed the
- KYC checks, or it has exceeded the number of open purses.
- The client must include payment to create another purse or
- wait until existing purses have expired.
+ :http:statuscode:`451 Unavailable For Legal Reasons`:
+ This account has not yet passed the KYC checks.
+ The client must pass KYC checks before proceeding with the merge.
+ The response will be an `KycNeededRedirect` object.
**Details:**
@@ -3139,13 +4285,146 @@ Wallet-to-wallet transfers
// EdDSA signature of the purse private key affirming the merge
// over a `TALER_PurseMergeSignaturePS`.
// Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``.
- purse_sig: EddsaSignature;
+ merge_sig: EddsaSignature;
+
+ // Client-side timestamp of when the merge request was made.
+ merge_timestamp: Timestamp;
+
+ }
+
+ .. ts:def:: MergeSuccess
+
+ interface MergeSuccess {
+
+ // Amount merged (excluding deposit fees).
+ merge_amount: Amount;
+
+ // Time at which the merge came into effect.
+ // Maximum of the "payment_timestamp" and the
+ // "merge_timestamp".
+ exchange_timestamp: Timestamp;
+
+ // EdDSA signature of the exchange affirming the merge of
+ // purpose ``TALER_SIGNATURE_PURSE_MERGE_SUCCESS``
+ // over `TALER_PurseMergeSuccessSignaturePS`.
+ // Signs over the above and the account public key.
+ exchange_sig: EddsaSignature;
+
+ // public key used to create the signature.
+ exchange_pub: EddsaPublicKey;
+
+ }
+
+ .. ts:def:: MergeConflict
+
+ interface MergeConflict {
+
+ // Client-side timestamp of when the merge request was made.
+ merge_timestamp: Timestamp;
+
+ // EdDSA signature of the purse private key affirming the merge
+ // over a `TALER_PurseMergeSignaturePS`.
+ // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``.
+ merge_sig: EddsaSignature;
+
+ // Base URL of the exchange receiving the payment, only present
+ // if the exchange hosting the reserve is not this exchange.
+ partner_url?: string;
+
+ // Public key of the reserve that the purse was merged into.
+ reserve_pub: EddsaPublicKey;
+ }
+
+
+
+.. http:POST:: /reserves/$RESERVE_PUB/purse
+
+ Create purse for an account. First step of a PULL payment.
+
+ **Request:**
+
+ The request body must be a `ReservePurseRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange confirms that the
+ purse was allocated.
+ The response will include a `PurseDepositSuccess` object.
+ :http:statuscode:`402 Payment Required`:
+ The account needs to contain more funding to create more purses.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`403 Forbidden`:
+ Account or contract signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The purse creation operation failed as we could not find the reserve.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`409 Conflict`:
+ The purse creation failed because a purse with
+ the same public key but different meta data was
+ created previously. Which specific conflict it is
+ can be decided by looking at the error code
+ (``TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA`` or
+ ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA`` or
+ ``TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA``).
+ The specific fields of the response depend on the error code
+ and include the signatures (and what was signed over) proving the
+ conflict.
+ The response will be a `PurseConflict` response
+ (but not a `DepositDoubleSpendError`).
+ :http:statuscode:`451 Unavailable For Legal Reasons`:
+ This account has not yet passed the KYC checks.
+ The client must pass KYC checks before proceeding with the merge.
+ The response will be an `KycNeededRedirect` object.
+
+ **Details:**
+
+ .. ts:def:: ReservePurseRequest
+
+ interface ReservePurseRequest {
// Minimum amount that must be credited to the reserve, that is
// the total value of the purse minus the deposit fees.
// If the deposit fees are lower, the contribution to the
// reserve can be higher!
- merge_value_after_fees: Amount;
+ purse_value: Amount;
+
+ // Minimum age required for all coins deposited into the purse.
+ min_age: Integer;
+
+ // Purse fee the reserve owner is willing to pay
+ // for the purse creation. Optional, if not present
+ // the purse is to be created from the purse quota
+ // of the reserve.
+ purse_fee: Amount;
+
+ // Optional encrypted contract, in case the buyer is
+ // proposing the contract and thus establishing the
+ // purse with the payment.
+ econtract?: EncryptedContract;
+
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKey;
+
+ // EdDSA signature of the purse private key affirming the merge
+ // over a `TALER_PurseMergeSignaturePS`.
+ // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``.
+ merge_sig: EddsaSignature;
+
+ // EdDSA signature of the account/reserve affirming the merge.
+ // Must be of purpose ``TALER_SIGNATURE_WALLET_ACCOUNT_MERGE``
+ reserve_sig: EddsaSignature;
+
+ // Purse public key.
+ purse_pub: EddsaPublicKey;
+
+ // EdDSA signature of the purse over
+ // `TALER_PurseRequestSignaturePS` of
+ // purpose ``TALER_SIGNATURE_PURSE_REQUEST``
+ // confirming that the
+ // above details hold for this purse.
+ purse_sig: EddsaSignature;
// SHA-512 hash of the contact of the purse.
h_contract_terms: HashCode;
@@ -3157,32 +4436,110 @@ Wallet-to-wallet transfers
// if it has not been paid.
purse_expiration: Timestamp;
- // Optional encrypted contract, in case the seller is
- // proposing the contract and thus establishing the
- // purse with the payment.
- contract?: EncryptedContract;
+ }
+
+.. http:POST:: /purses/$PURSE_PUB/deposit
+
+ Deposit money into a purse. Used by the buyer for a PULL payment.
+
+ **Request:**
+
+ The request body must be a `PurseDeposits` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange confirms that all
+ coins were deposited into the purse.
+ The response will include a `PurseDepositSuccess` object.
+ :http:statuscode:`403 Forbidden`:
+ A coin or denomination signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The purse is unknown.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`409 Conflict`:
+ The deposit operation has either failed because a coin has insufficient
+ residual value, or because the same public key of the coin has been
+ previously used with a different denomination. Which case it is
+ can be decided by looking at the error code
+ (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or
+ ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA``).
+ This response comes with a standard `PurseConflict` response
+ (alas some cases are impossible).
+ :http:statuscode:`410 Gone`:
+ The purse has expired.
+
+
+ **Details:**
+
+ .. ts:def:: PurseDeposits
+
+ interface PurseDeposits {
+
+ // Array of coins to deposit into the purse.
+ deposits: PurseDeposit[];
}
- .. ts:def:: MergeSuccess
+ .. ts:def:: PurseDeposit
- interface MergeSuccess {
+ interface PurseDeposit {
- // Amount merged (excluding deposit fees).
- merge_amount: Amount;
+ // Amount to be deposited, can be a fraction of the
+ // coin's total value.
+ amount: Amount;
+
+ // Hash of denomination RSA key with which the coin is signed.
+ denom_pub_hash: HashCode;
+
+ // Exchange's unblinded RSA signature of the coin.
+ ub_sig: DenominationSignature;
+
+ // Age commitment for the coin, if the denomination is age-restricted.
+ age_commitment?: AgeCommitment;
+
+ // Attestation for the minimum age, if the denomination is age-restricted.
+ attest?: Attestation;
+
+ // Signature over `TALER_PurseDepositSignaturePS`
+ // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT``
+ // made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+
+ // Public key of the coin being deposited into the purse.
+ coin_pub: EddsaPublicKey;
+
+ }
+
+ .. ts:def:: PurseDepositSuccess
+
+ interface PurseDepositSuccess {
+
+ // Total amount paid into the purse.
+ total_deposited: Amount;
+
+ // Total amount expected in the purse.
+ purse_value_after_fees: Amount;
+
+ // Time at which the deposit came into effect.
+ exchange_timestamp: Timestamp;
+
+ // Indicative time by which the purse should expire
+ // if it has not been merged into an account. At this
+ // point, all of the deposits made will be auto-refunded.
+ purse_expiration: Timestamp;
// SHA-512 hash of the contact of the purse.
h_contract_terms: HashCode;
- // Time at which the merge came into effect.
- // Maximum of the "payment_timestamp" and the
- // "merge_timestamp".
- contract_time: Timestamp;
-
- // EdDSA signature of the exchange affirming the merge of
- // purpose ``TALER_SIGNATURE_PURSE_MERGE_SUCCESS``
- // over `TALER_PurseMergeSuccessSignaturePS`.
- // Signs over the above and the account public key.
+ // EdDSA signature of the exchange affirming the payment,
+ // of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT_CONFIRMED``
+ // over a `TALER_PurseDepositConfirmedSignaturePS`.
+ // Signs over the above and the purse public key and
+ // the hash of the contract terms.
exchange_sig: EddsaSignature;
// public key used to create the signature.
@@ -3190,17 +4547,17 @@ Wallet-to-wallet transfers
}
- .. ts:def:: MergeAccepted
-
- interface MergeAccepted {
+ .. ts:def:: AgeCommitment
- // The number of remaining purses that can still be opened
- // under the given account.
- remaining_purses: Integer;
-
- }
+ // AgeCommitment is an array of public keys, one for each age group of the
+ // age-restricted denomination.
+ type AgeCommitment = Edx25519PublicKey[];
+ .. ts:def:: Attestation
+ // An attestation for a minimum age is an Edx25519 signature of the age
+ // with purpose ``TALER_SIGNATURE_WALLET_AGE_ATTESTATION``.
+ type Attestation = string;
.. _exchange_wads:
@@ -3305,22 +4662,77 @@ wallet-to-wallet payments. Only another exchange should access this endpoint.
KYC status updates
------------------
- .. note::
+.. http:POST:: /kyc-wallet
- This is a draft API that is not yet implemented.
+ Setup KYC identification for a wallet. Returns the KYC UUID.
+
+ **Request:**
+
+ The request body must be a `WalletKycRequest` object.
+ **Response:**
-.. http:GET:: /kyc-check/$PAYMENT_TARGET_UUID
+ :http:statuscode:`204 No Content`:
+ KYC is disabled at this exchange, or the balance
+ is below the threshold that requires KYC, or this
+ wallet already satisfied the KYC check for the
+ given balance.
+ :http:statuscode:`403 Forbidden`:
+ The provided signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ The wallet must undergo a KYC check. A KYC ID was created.
+ The response will be a `WalletKycUuid` object.
+
+ **Details:**
+
+ .. ts:def:: WalletKycRequest
+
+ interface WalletKycRequest {
+
+ // Balance threshold (not necessarily exact balance)
+ // to be crossed by the wallet that (may) trigger
+ // additional KYC requirements.
+ balance: Amount;
+
+ // EdDSA signature of the wallet affirming the
+ // request, must be of purpose
+ // ``TALER_SIGNATURE_WALLET_ACCOUNT_SETUP``
+ reserve_sig: EddsaSignature;
+
+ // long-term wallet reserve-account
+ // public key used to create the signature.
+ reserve_pub: EddsaPublicKey;
+ }
+
+ .. ts:def:: WalletKycUuid
+
+ interface WalletKycUuid {
+
+ // UUID that the wallet should use when initiating
+ // the KYC check.
+ requirement_row: number;
+
+ // Hash of the payto:// account URI for the wallet.
+ h_payto: PaytoHash;
+
+ }
+
+
+.. http:GET:: /kyc-check/$REQUIREMENT_ROW/$H_PAYTO/$USERTYPE
Check or update KYC status of a particular payment target.
Returns the current KYC status of the account and, if
negative, returns the URL where the KYC process can be
- initiated using OAuth.
+ initiated. The ``$REQUIREMENT_ROW`` must have been
+ returned previously from an exchange API endpoint that
+ determined that KYC was needed. The ``$H_PATYO`` must be
+ the hash of the payto:// URI of the payment target.
+ The ``$USERTYPE`` states whether the entity to perform
+ the KYC is an "individual" or "business".
**Request:**
- :query h_payto=HASH: Specifies the hash of the payto:// URI of the payment
- target. Weak security check used to authorize the status request.
:query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
wait up to ``timeout_ms`` milliseconds if the payment target
is currently not legitimized. Ignored if the payment target
@@ -3332,7 +4744,7 @@ KYC status updates
:http:statuscode:`200 Ok`:
The KYC operation succeeded, the exchange confirms that the
- payment target is now authorized to transact.
+ payment target already authorized to transact.
The response will be an `AccountKycStatus` object.
:http:statuscode:`202 Accepted`:
The user should be redirected to the provided location to perform
@@ -3342,7 +4754,10 @@ KYC status updates
:http:statuscode:`204 No content`:
The exchange is not configured to perform KYC and thus
generally all accounts are simply considered legitimate.
- :http:statuscode:`401 Unauthorized`:
+ :http:statuscode:`402 Payment Required`:
+ The client must pay the KYC fee for the KYC process.
+ **This is currently not implemented, see #7365.**
+ :http:statuscode:`403 Forbidden`:
The provided hash does not match the payment target.
:http:statuscode:`404 Not found`:
The payment target is unknown.
@@ -3351,7 +4766,11 @@ KYC status updates
.. ts:def:: AccountKycStatus
- interface AccountKycStatus {
+ interface AccountKycStatus {
+
+ // Details about the KYC check that the user
+ // passed.
+ kyc_details: KycDetails;
// Current time of the exchange, used as part of
// what the exchange signs over.
@@ -3360,7 +4779,7 @@ KYC status updates
// EdDSA signature of the exchange affirming the account
// is KYC'ed, must be of purpose
// ``TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS``
- // and over `TALER_AccountSetupStatusSignaturePS`.
+ // and over ``TALER_AccountSetupStatusSignaturePS``.
exchange_sig: EddsaSignature;
// public key used to create the signature.
@@ -3369,7 +4788,7 @@ KYC status updates
.. ts:def:: AccountKycRedirect
- interface AccountKycRedirect {
+ interface AccountKycRedirect {
// URL that the user should open in a browser to
// proceed with the KYC process.
@@ -3377,20 +4796,34 @@ KYC status updates
}
+ .. ts:def:: KycDetails
+
+ // Object that specifies which KYC checks are satisfied.
+ interface KycDetails {
+
+ // Keys are the names of the check(s).
+ // The values are for now always empty objects.
-.. http:GET:: /kyc-proof/$PAYMENT_TARGET_UUID
+ }
+
+.. http:GET:: /kyc-proof/$H_PAYTO/$PROVIDER_SECTION
Update KYC status of a particular payment target. Provides
- the token to the exchange that allows it to verify that the
- user has completed the KYC process. The HTTP method must be
- a GET due to requirements from the OAuth 2.0 protocol.
+ information to the exchange that allows it to verify that the
+ user has completed the KYC process. The details depend on
+ the logic, which is selected by the $PROVIDER_SECTION.
**Request:**
- :query code=CODE: OAuth 2.0 code argument.
+ Details on the request depend on the specific KYC logic
+ that was used.
+
+ If $LOGIC is "oauth2.0", the query parameters are:
+
+ :query code=CODE: OAuth 2.0 code argument.
:query state=CODE: OAuth 2.0 state argument.
- ..note::
+ .. note::
Depending on the OAuth variant used, additional
query parameters may need to be passed here.
@@ -3403,12 +4836,341 @@ KYC status updates
The browser is redirected to a human-readable
page configured by the exchange operator.
:http:statuscode:`401 Unauthorized`:
- The provided OAuth 2.0 authentication token is invalid.
+ The provided authorization token is invalid.
:http:statuscode:`404 Not found`:
The payment target is unknown.
:http:statuscode:`502 Bad Gateway`:
- The exchange received an invalid reply from the OAuth-based
+ The exchange received an invalid reply from the
legitimization service.
:http:statuscode:`504 Gateway Timeout`:
- The exchange did not receive a reply from the OAuth legitimization
+ The exchange did not receive a reply from the legitimization
service within a reasonable time period.
+
+
+.. http:GET:: /kyc-webhook/$PROVIDER_SECTION/*
+.. http:POST:: /kyc-webhook/$PROVIDER_SECTION/*
+.. http:GET:: /kyc-webhook/$LOGIC/*
+.. http:POST:: /kyc-webhook/$LOGIC/*
+
+ Update KYC status of a particular payment target. Provides
+ information to the KYC logic of the exchange that allows
+ it to verify that the user has completed the KYC process.
+ May be a GET or a POST request, depending on $LOGIC or
+ $PROVIDER_SECTION.
+
+ **Request:**
+
+ Details on the request depend on the specific KYC logic
+ that was used.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The webhook succeeded.
+ :http:statuscode:`404 Not found`:
+ The specified logic is unknown.
+
+
+---------------
+Reserve control
+---------------
+
+This section describes the reserve control API which can be used to (1)
+prevent a reserve from expiring (which is useful if the reserve is used for
+tipping), to (2) pay an annual fee to allow a number of purses to be created
+for the respective reserve without paying a purse fee each time, to (3) obtain
+KYC information associated with a reserve to prove the identity of the person
+sending an invoice to the payer, and to (4) close a reserve before it would
+naturally expire and possibly (5) wire the funds to a designated account.
+
+ .. note::
+
+ This section is about a proposed API. It is not implemented. See also DD 31.
+
+.. http:post:: /reserves/$RESERVE_PUB/open
+
+ Request keeping a reserve open for tipping or invoicing.
+
+ **Request:**
+
+ The request body must be a `ReserveOpenRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The exchange responds with a `ReserveOpenResponse` object.
+ :http:statuscode:`402 Payment Required`:
+ The exchange responds with a `ReserveOpenFailure` object when
+ the payment offered is insufficient for the requested operation.
+ :http:statuscode:`403 Forbidden`:
+ The *TALER_SIGNATURE_WALLET_RESERVE_OPEN* signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The reserve key does not belong to a reserve known to the exchange.
+ :http:statuscode:`409 Conflict`:
+ The balance of the reserve or of a coin was insufficient.
+ Which case it is can be decided by looking at the error code
+ (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or
+ ``TALER_EC_EXCHANGE_OPEN_INSUFFICIENT_FUNDS``).
+ The specific fields of the response depend on the error code
+ and include the signatures (and what was signed over) proving the
+ conflict.
+ The response is `WithdrawError` object or a `DepositDoubleSpendError`
+ depending on the error type.
+ :http:statuscode:`451 Unavailable For Legal Reasons`:
+ This account has not yet passed the KYC checks.
+ The client must pass KYC checks before the reserve can be opened.
+ The response will be an `KycNeededRedirect` object.
+
+ **Details:**
+
+ .. ts:def:: ReserveOpenRequest
+
+ interface ReserveOpenRequest {
+ // Signature of purpose
+ // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN`` over
+ // a `TALER_ReserveOpenRequestSignaturePS`.
+ reserve_sig: EddsaSignature;
+
+ // Array of payments made towards the cost of the
+ // operation.
+ payments: OpenPaymentDetail[];
+
+ // Amount to be paid from the reserve for this
+ // operation.
+ reserve_payment: Amount;
+
+ // Time when the client made the request.
+ // Timestamp must be reasonably close to the time of
+ // the exchange, otherwise the exchange may reject
+ // the request (with a status code of 400).
+ request_timestamp: Timestamp;
+
+ // Desired new expiration time for the reserve.
+ // If the reserve would expire before this time,
+ // the exchange will charge account fees (and
+ // possibly KYC fees) until the expiration time
+ // exceeds this timestamp. Note that the exchange
+ // will refuse requests (with a status code of 400)
+ // if the time is so far in the future that the
+ // fees are not yet known (see /keys).
+ reserve_expiration: Timestamp;
+
+ // Desired open purse limit. Can be used to pay the
+ // annual account fee more than once to get a larger
+ // purse limit.
+ purse_limit: Integer;
+
+ }
+
+ .. ts:def:: ReserveOpenResponse
+
+ interface ReserveOpenResponse {
+ // Transaction cost for extending the expiration time.
+ // Excludes KYC fees.
+ open_cost: Amount;
+
+ // Current expiration time for the reserve.
+ reserve_expiration: Timestamp;
+ }
+
+ .. ts:def:: ReserveOpenFailure
+
+ interface ReserveOpenFailure {
+ // Transaction cost that should have been paid
+ // to extending the reserve as requested.
+ // Excludes KYC fees.
+ open_cost: Amount;
+
+ // Remaining expiration time for the reserve.
+ reserve_expiration: Timestamp;
+ }
+
+ .. ts:def:: OpenPaymentDetail
+
+ interface OpenPaymentDetail {
+
+ // Contribution of this coin to the overall amount.
+ // Can be a fraciton of the coin's total value.
+ amount: Amount;
+
+ // Hash of denomination RSA key with which the coin is signed.
+ denom_pub_hash: HashCode;
+
+ // Exchange's unblinded RSA signature of the coin.
+ ub_sig: DenominationSignature;
+
+ // Age commitment for the coin, if the denomination is age-restricted.
+ age_commitment?: AgeCommitment;
+
+ // Signature over `TALER_ReserveOpenDepositSignaturePS`
+ // of purpose ``TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT``
+ // made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+
+ // Public key of the coin being used to pay for
+ // opening the reserve.
+ coin_pub: EddsaPublicKey;
+
+ }
+
+
+.. http:get:: /reserves-attest/$RESERVE_PUB
+
+ Request list of available KYC attributes about the owner of a reserve.
+ Used as a preliminary step to find out which subsets of attributes the
+ exchange could provide signatures over.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The exchange responds with a `ReserveKycAttributes` object.
+ :http:statuscode:`404 Not found`:
+ The reserve key does not belong to a reserve known to the exchange.
+ :http:statuscode:`409 Conflict`:
+ The exchange does not have the requested KYC information.
+
+ **Details:**
+
+ .. ts:def:: ReserveKycAttributes
+
+ interface ReserveKycAttributes {
+ // Array of KYC attributes available. The attribute names
+ // listed are expected to be from the respective GANA
+ // registry.
+ details: string[];
+ }
+
+
+.. http:post:: /reserves-attest/$RESERVE_PUB
+
+ Request signed KYC information about the owner of a reserve.
+ This can be used by a reserve owner to include a proof
+ of their identity in invoices.
+
+ **Request:**
+
+ The request body must be a `ReserveAttestRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The exchange responds with a `ReserveAttestResponse` object.
+ :http:statuscode:`403 Forbidden`:
+ The *TALER_SIGNATURE_WALLET_KYC_DETAILS* signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The reserve key does not belong to a reserve known to the exchange.
+ :http:statuscode:`409 Conflict`:
+ The exchange does not have the requested KYC information.
+
+ **Details:**
+
+ .. ts:def:: ReserveAttestRequest
+
+ interface ReserveAttestRequest {
+ // Signature of purpose
+ // ``TALER_SIGNATURE_WALLET_ATTEST_DETAILS`` over
+ // a `TALER_WalletReserveAttestRequestSignaturePS`.
+ reserve_sig: EddsaSignature;
+
+ // Client's time for the request.
+ request_timestamp: Timestamp;
+
+ // Array of KYC attributes requested.
+ details: string[];
+ }
+
+ .. ts:def:: ReserveAttestResponse
+
+ interface ReserveAttestResponse {
+ // Signature of purpose
+ // ``TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS`` over
+ // a `TALER_ExchangeReserveAttestDetailsSignaturePS`.
+ exchange_sig: EddsaSignature;
+
+ // Exchange public key used to create the
+ // signature.
+ exchange_pub: EddsaPublicKey;
+
+ // Time when the exchange created the signature.
+ exchange_timestamp: Timestamp;
+
+ // Expiration time for the provided information.
+ expiration_time: Timestamp;
+
+ // KYC details (key-value pairs) as requested.
+ // The keys will match the elements of the
+ // ``details`` array from the request.
+ attributes: Object;
+ }
+
+
+.. http:post:: /reserves/$RESERVE_PUB/close
+
+ Force immediate closure of a reserve. Does not actually
+ delete the reserve or the KYC data, but merely forces
+ the reserve's current balance to be wired back to the
+ account where it originated from, or another account of
+ the user's choosing if they performed the required KYC
+ check and designated another target account.
+
+ **Request:**
+
+ The request body must be a `ReserveCloseRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The exchange responds with a `ReserveCloseResponse` object.
+ :http:statuscode:`403 Forbidden`:
+ The *TALER_SIGNATURE_WALLET_RESERVE_CLOSE* signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The reserve key does not belong to a reserve known to the exchange.
+ :http:statuscode:`409 Conflict`:
+ No target account was given, and the exchange does not know an
+ origin account for this reserve.
+ :http:statuscode:`451 Unavailable For Legal Reasons`:
+ This account has not yet passed the KYC checks, hence wiring
+ funds to a non-origin account is not allowed.
+ The client must pass KYC checks before the reserve can be opened.
+ The response will be an `KycNeededRedirect` object.
+
+ **Details:**
+
+ .. ts:def:: ReserveCloseRequest
+
+ interface ReserveCloseRequest {
+ // Signature of purpose
+ // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE`` over
+ // a `TALER_ReserveCloseRequestSignaturePS`.
+ reserve_sig: EddsaSignature;
+
+ // Time when the client made the request.
+ // Timestamp must be reasonably close to the time of
+ // the exchange, otherwise the exchange may reject
+ // the request (with a status code of 400).
+ request_timestamp: Timestamp;
+
+ // payto://-URI of the account the reserve balance is to be
+ // wired to. Must be of the form: 'payto://$METHOD' for a
+ // wire method supported by this exchange (if the
+ // method is not supported, this is a bad request (400)).
+ // If not given, the reserve's origin account
+ // will be used. If no origin account is known for the
+ // reserve and not given, this is a conflict (409).
+ payto_uri?: string;
+
+ }
+
+ .. ts:def:: ReserveCloseResponse
+
+ interface ReserveCloseResponse {
+
+ // Actual amount that will be wired (excludes closing fee).
+ wire_amount: Amount;
+
+ }
diff --git a/core/api-mailbox.rst b/core/api-mailbox.rst
new file mode 100644
index 00000000..33db482d
--- /dev/null
+++ b/core/api-mailbox.rst
@@ -0,0 +1,212 @@
+..
+ This file is part of GNU TALER.
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Christian Grothoff
+
+
+=======================
+The Mailbox RESTful API
+=======================
+
+This is a proposed API for the GNU Taler Mailbox service which allows Taler
+wallets to securely send push and pull payment requests to other wallets
+without having to interact with the respective messaging service.
+
+The API specified here follows the :ref:`general conventions <http-common>`
+for all details not specified in the individual requests.
+The `glossary <https://docs.taler.net/glossary.html#glossary>`_
+defines all specific terms used in this section.
+
+.. include:: tos.rst
+
+-------------------------
+Configuration information
+-------------------------
+
+.. http:get:: /config
+
+ Return the protocol version and currency supported by this service.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The body is a `VersionResponse`.
+
+ **Details:**
+
+ .. ts:def:: VersionResponse
+
+ interface VersionResponse {
+ // libtool-style representation of the Merchant protocol version, see
+ // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+ // The format is "current:revision:age".
+ version: string;
+
+ // Name of the protocol.
+ name: "taler-mailbox";
+
+ // Fee per message.
+ message_fee: Amount;
+
+ // How long will the service store a message
+ // before giving up on delivery?
+ delivery_period: RelativeTime;
+
+ }
+
+----------------
+Sending messages
+----------------
+
+.. http:post:: /$H_MAILBOX
+
+ Puts a message into ``$H_MAILBOX``.
+ ``$H_MAILBOX`` is the SHA512 of an EdDSA public key.
+
+ **Request**
+
+ The body of the request must be an `IdentityMessage`.
+
+ **Response**
+
+ :http:statuscode:`204 No Content`
+ Message was stored and will be delivered.
+ :http:statuscode:`402 Payment Required`
+ Client needs to make a Taler payment before proceeding. See
+ standard Taler payment procedure.
+ :http:statuscode:`403 Forbidden`
+ The specified ``order_id`` does not permit sending
+ of this message. Possible reaons include the order
+ being for a different message, unpaid or
+ malformed.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`429 Too Many Requests`:
+ The system is currently experiencing a too high request
+ load and is unable to accept the message for delivery.
+ The response format is given by `MailboxRateLimitedResponse`_.
+
+ **Details:**
+
+ .. _IdentityMessage:
+ .. ts:def:: IdentityMessage
+
+ interface IdentityMessage {
+ // Public DH key used to encrypt the body. Must be fresh
+ // and only used once (ephemeral).
+ ephemeral_key: EcdhePublicKey;
+
+ // Encrypted message. Must be exactly 256-32 bytes long.
+ body: string;
+
+ // Order ID, if the client recently paid for this message.
+ order_id?: string;
+ }
+
+ .. _MailboxRateLimitedResponse:
+ .. ts:def:: MailboxRateLimitedResponse
+
+ interface MailboxRateLimitedResponse {
+
+ // Taler error code, TALER_EC_MAILBOX_DELIVERY_RATE_LIMITED.
+ code: number;
+
+ // When the client should retry.
+ retry_delay: RelativeTime;
+
+ // The human readable error message.
+ hint: string;
+
+ }
+
+------------------
+Receiving messages
+------------------
+
+.. http:get:: /$H_MAILBOX
+
+ Endpoint that returns unread messages in ``$H_MAILBOX``.
+ The number of messages returned by the service can be limited.
+ If the request is simply repeated, the same messages will be
+ returned again (or possibly more if additional messages arrived
+ and the total number is below the service's current internal limit).
+ To receive additional messages, the client generally has to first
+ explicitly DELETE already downloaded messages from the mailbox.
+
+ **Request:**
+
+ :query timeout_ms=NUMBER: *Optional.* If specified,
+ the Mailbox service will wait up to ``NUMBER``
+ milliseconds for the arrival of new messages
+ before sending the HTTP response. Note that if the
+ mailbox is non-empty, the service will always return
+ immediately with the messages in the mailbox, and not
+ wait for additional messages to arrive.
+
+ **Response**
+
+ :http:statuscode:`200 Ok`:
+ Messages are returned in binary format, 256 bytes per message,
+ starting with the ephemeral key and followed by
+ the encrypted body. Messages are not encapsulated in JSON!
+ :http:statuscode:`204 No Content`:
+ The mailbox is empty.
+ :http:statuscode:`429 Too Many Requests`:
+ The system is currently experiencing a too high request
+ load and is unable to accept the message for delivery.
+ The response format is given by `MailboxRateLimitedResponse`_.
+
+.. http:delete:: /$ADDRESS
+
+ Requests the deletion of already received messages from the
+ mailbox. Here, ``$ADDRESS`` must be the EdDSA public key
+ of the mailbox (not the hash!).
+
+ **Request**
+
+ The body of the request must be a ``MessageDeletionRequest``.
+
+ **Details:**
+
+ .. _MessageDeletionRequest:
+ .. ts:def:: MessageDeletionRequest
+
+ interface MessageDeletionRequest {
+
+ // Number of messages to delete. (Starting from the beginning
+ // of the latest GET response).
+ count: Integer;
+
+ // SHA-512 hash over all messages to delete.
+ checksum: HashCode;
+
+ // Signature by the mailbox's private key affirming
+ // the deletion of the messages, of purpuse
+ // ``TALER_SIGNATURE_WALLET_MAILBOX_DELETE_MESSAGES``.
+ wallet_sig: EddsaSignature;
+
+ }
+
+ **Response**
+
+ :http:statuscode:`204 No Content`:
+ Deletion complete.
+ :http:statuscode:`403 Forbidden`:
+ The ``wallet_sig`` is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The checksum does not match the messages currently at the
+ head of the mailbox, or ``count`` is larger
+ than the number of messages currently in the mailbox.
+ This response comes with a standard `ErrorDetail` response.
diff --git a/core/api-merchant.rst b/core/api-merchant.rst
index 519a4324..f686b302 100644
--- a/core/api-merchant.rst
+++ b/core/api-merchant.rst
@@ -16,6 +16,7 @@
@author Marcello Stanisci
@author Florian Dold
@author Christian Grothoff
+ @author Priscilla Huang
.. _merchant-api:
@@ -25,6 +26,7 @@ Merchant Backend API
.. contents:: Table of Contents
+
-----------------------
Base URLs and Instances
-----------------------
@@ -38,7 +40,7 @@ instance is used when no explicit instance is specified. Despite its name,
this instance must be created after the installation. In case *no* default
instance is found - or its credentials got lost -, the administrator can use
the default instance's rights by resorting on the ``--auth`` command line option,
-or by restarting the service by providing a environment variable called
+or by restarting the service by providing an environment variable called
``TALER_MERCHANT_TOKEN``.
Each instance (default and others) has a base URL. The resources under
@@ -118,7 +120,7 @@ such as the implemented version of the protocol and the currency used.
**Response:**
:http:statuscode:`200 OK`:
- The exchange accepted all of the coins. The body is a `VersionResponse`.
+ The body is a `VersionResponse`.
.. ts:def:: VersionResponse
@@ -245,18 +247,22 @@ Making the payment
The merchant backend could not find the order or the instance and thus cannot process the payment.
:http:statuscode:`406 Not acceptable`:
The payment is insufficient (sum is below the required total amount).
+ TODO: Should probably change to a different status code in the future as 406 is technically wrong.
:http:statuscode:`408 Request timeout`:
The backend took too long to process the request. Likely the merchant's connection
to the exchange timed out. Try again.
:http:statuscode:`409 Conflict`:
- The exchange rejected the payment because a coin was already spent.
- The response will include the ``coin_pub`` for which the payment failed,
- in addition to the response from the exchange to the ``/deposit`` request.
+ The exchange rejected the payment because a coin was already spent, or
+ the merchant rejected the payment because the order was already fully paid
+ (and then return signatures with refunds). If a coin was already spent,
+ the response will include the ``exchange_url`` for which the payment failed,
+ in addition to the response from the exchange to the ``/batch-deposit`` request.
:http:statuscode:`410 Gone`:
The offer has expired and is no longer available.
:http:statuscode:`412 Precondition failed`:
The given exchange is not acceptable for this merchant, as it is not in the
list of accepted exchanges and not audited by an approved auditor.
+ TODO: Status code may be changed to 409 in the future as 412 is technically wrong.
:http:statuscode:`502 Bad gateway`:
The merchant's interaction with the exchange failed in some way.
The client might want to try again later.
@@ -381,6 +387,9 @@ Querying payment status
// Amount that was refunded in total.
refund_amount: Amount;
+
+ // Amount that already taken by the wallet.
+ refund_taken: Amount;
}
.. ts:def:: StatusGotoResponse
@@ -848,7 +857,7 @@ Instance management
-------------------
Instances allow one merchant backend to be shared by multiple merchants.
-Every backend must have at least one instance, typcially the "default"
+Every backend must have at least one instance, typically the "default"
instance setup before it can be used to manage inventory or process payments.
@@ -894,6 +903,15 @@ Setting up instances
// Merchant name corresponding to this instance.
name: string;
+ // Merchant email for customer contact.
+ email?: string;
+
+ // Merchant public website.
+ website?: string;
+
+ // Merchant logo.
+ logo?: ImageDataUrl;
+
// Authentication settings for this instance
auth: InstanceAuthConfigurationMessage;
@@ -998,6 +1016,15 @@ Setting up instances
// Merchant name corresponding to this instance.
name: string;
+ // Merchant email for customer contact.
+ email?: string;
+
+ // Merchant public website.
+ website?: string;
+
+ // Merchant logo.
+ logo?: ImageDataUrl;
+
// The merchant's physical address (to be put into contracts).
address: Location;
@@ -1063,6 +1090,12 @@ Inspecting instances
// Merchant name corresponding to this instance.
name: string;
+ // Merchant public website.
+ website?: string;
+
+ // Merchant logo.
+ logo?: ImageDataUrl;
+
// Merchant instance this response is about ($INSTANCE).
id: string;
@@ -1103,6 +1136,15 @@ Inspecting instances
// Merchant name corresponding to this instance.
name: string;
+ // Merchant email for customer contact.
+ email?: string;
+
+ // Merchant public website.
+ website?: string;
+
+ // Merchant logo.
+ logo?: ImageDataUrl;
+
// Public key of the merchant/instance, in Crockford Base32 encoding.
merchant_pub: EddsaPublicKey;
@@ -1274,14 +1316,14 @@ KYC status checks
.. ts:def:: ExchangeKycTimeout
- interface ExchangeKycTimeout {
+ interface ExchangeKycTimeout {
- // Base URL of the exchange this is about.
- exchange_url: string;
+ // Base URL of the exchange this is about.
+ exchange_url: string;
- // Numeric `error code <error-codes>` indicating errors the exchange
- // returned, or TALER_EC_INVALID for none.
- exchange_code: number;
+ // Numeric `error code <error-codes>` indicating errors the exchange
+ // returned, or TALER_EC_INVALID for none.
+ exchange_code: number;
// HTTP status code returned by the exchange when we asked for
// information about the KYC status.
@@ -1363,6 +1405,9 @@ Adding products to the inventory
// Identifies when we expect the next restocking to happen.
next_restock?: Timestamp;
+ // Minimum age buyer must have (in years). Default is 0.
+ minimum_age?: Integer;
+
}
@@ -1435,6 +1480,9 @@ Adding products to the inventory
// Identifies when we expect the next restocking to happen.
next_restock?: Timestamp;
+ // Minimum age buyer must have (in years). Default is 0.
+ minimum_age?: Integer;
+
}
Inspecting inventory
@@ -1526,6 +1574,9 @@ Inspecting inventory
// Identifies when we expect the next restocking to happen.
next_restock?: Timestamp;
+ // Minimum age buyer must have (in years).
+ minimum_age?: Integer;
+
}
@@ -1799,8 +1850,8 @@ Inspecting orders
:query paid: *Optional*. If set to yes, only return paid orders, if no only unpaid orders. Do not give (or use "all") to see all orders regardless of payment status.
:query refunded: *Optional*. If set to yes, only return refunded orders, if no only unrefunded orders. Do not give (or use "all") to see all orders regardless of refund status.
:query wired: *Optional*. If set to yes, only return wired orders, if no only orders with missing wire transfers. Do not give (or use "all") to see all orders regardless of wire transfer status.
- :query delta: *Optional*. takes value of the form ``N (-N)``, so that at most ``N`` values strictly older (younger) than ``start`` and ``date_ms`` are returned. Defaults to ``-20`` to return the last 20 entries (before ``start`` and/or ``date_ms``).
- :query date_ms: *Optional.* Non-negative date in milliseconds after the UNIX Epoc, see ``delta`` for its interpretation. If not specified, we default to the oldest or most recent entry, depending on ``delta``.
+ :query delta: *Optional*. takes value of the form ``N (-N)``, so that at most ``N`` values strictly older (younger) than ``start`` and ``date_s`` are returned. Defaults to ``-20`` to return the last 20 entries (before ``start`` and/or ``date_s``).
+ :query date_s: *Optional.* Non-negative date in seconds after the UNIX Epoc, see ``delta`` for its interpretation. If not specified, we default to the oldest or most recent entry, depending on ``delta``.
:query start: *Optional*. Row number threshold, see ``delta`` for its interpretation. Defaults to ``INT64_MAX``, namely the biggest row id possible in the database.
:query timeout_ms: *Optional*. Timeout in milliseconds to wait for additional orders if the answer would otherwise be negative (long polling). Only useful if delta is positive. Note that the merchant MAY still return a response that contains fewer than ``delta`` orders.
@@ -1985,6 +2036,9 @@ Inspecting orders
// Reason given for the refund.
reason: string;
+ // Set to true if a refund is still available for the wallet for this payment.
+ pending: boolean;
+
// When was the refund approved.
timestamp: Timestamp;
@@ -2492,7 +2546,7 @@ once we got a reply from the exchange.
.. http:delete:: [/instances/$INSTANCE]/private/transfers/$TID
- Here, the TID ist the 'transfer_serial_id' of the transfer
+ Here, the TID is the 'transfer_serial_id' of the transfer
to delete.
**Response:**
@@ -2901,7 +2955,7 @@ Checking tip status
.. ts:def:: Tip
- interface Tip {
+ interface Tip {
// ID of the tip in the backend database.
row_id: number;
@@ -2915,6 +2969,434 @@ Checking tip status
+--------
+Template
+--------
+
+The template is a backend feature that is used to create an order. We use the template to have some static information that cannot be changed by the customer.
+The template is private and allows us to be flexible. The private template allows for greater security and allows us to have a minimum information we need for each store.
+
+
+
+Adding templates
+----------------
+
+.. http:post:: [/instances/$INSTANCE]/private/templates
+
+ This is used to create a template.
+
+ **Request:**
+
+ The request must be a `TemplateAddDetails`.
+
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The creation of the template is successful.
+ :http:statuscode:`404 Not found`:
+ The merchant instance is unknown or it is not in our data.
+
+
+ .. ts:def:: TemplateAddDetails
+
+ interface TemplateAddDetails {
+
+ // Template ID to use.
+ template_id: string;
+
+ // Human-readable description for the template.
+ template_description: string;
+
+ // A base64-encoded image selected by the merchant.
+ // This parameter is optional.
+ // We are not sure about it.
+ image?: ImageDataUrl;
+
+ // Additional information in a separate template.
+ template_contract: TemplateContractDetails;
+ }
+
+
+ .. ts:def:: TemplateContractDetails
+
+ interface TemplateContractDetails {
+
+ // Human-readable summary for the template.
+ summary?: string;
+
+ // The price is imposed by the merchant and cannot be changed by the customer.
+ // This parameter is optional.
+ amount?: Amount;
+
+ // Minimum age buyer must have (in years). Default is 0.
+ minimum_age: Integer;
+
+ // The time the customer need to pay before his order will be deleted.
+ // It is deleted if the customer did not pay and if the duration is over.
+ pay_duration: RelativeTime;
+
+ }
+
+
+
+Editing templates
+-----------------
+
+
+.. http:patch:: [/instances/$INSTANCE]/private/templates/$TEMPLATE_ID
+
+ This is used to update a template. It is useful when we need to change information in the template or when we have mistake some information.
+
+ **Request:**
+
+ The request must be a `TemplatePatchDetails`.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The template has successfully modified.
+ :http:statuscode:`404 Not found`:
+ The template(ID) is unknown to the backend.
+ :http:statuscode:`409 Conflict`:
+ The provided information is inconsistent with the current state of the template. Changes made is the same with
+ another store.
+
+
+ .. ts:def:: TemplatePatchDetails
+
+ interface TemplatePatchDetails {
+
+ // Human-readable description for the template.
+ template_description: string;
+
+ // A base64-encoded image selected by the merchant.
+ // This parameter is optional.
+ // We are not sure about it.
+ image?: ImageDataUrl;
+
+ // Additional information in a separate template.
+ template_contract: TemplateContractDetails;
+
+ }
+
+
+
+Inspecting template
+-------------------
+
+.. http:get:: [/instances/$INSTANCE]/private/templates
+
+ This is used to return the list of all the templates.
+
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The backend has successfully returned all the templates. Returns a `TemplateSummaryResponse`.
+ :http:statuscode:`404 Not found`:
+ The backend has does not know about the instance.
+
+ .. ts:def:: TemplateSummaryResponse
+
+ interface TemplateSummaryResponse {
+
+ // List of templates that are present in our backend.
+ templates_list: TemplateEntry[];
+ }
+
+ The `TemplatesEntry` object describes a template. It has the following structure:
+
+ .. ts:def:: TemplateEntry
+
+ interface TemplateEntry {
+
+ // Template identifier, as found in the template.
+ template_id: string;
+
+ // Human-readable description for the template.
+ template_description: string;
+
+ }
+
+.. http:get:: [/instances/$INSTANCE]/private/templates/$TEMPLATE_ID
+
+ This is used to obtain detailed information about a specific template.
+
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The backend has successfully returned the detailed information about a specific template.
+ Returns a `TemplateDetails`.
+ :http:statuscode:`404 Not found`:
+ The template(ID) is unknown to the backend.
+
+
+ .. ts:def:: TemplateDetails
+
+ interface TemplateDetails {
+
+ // Human-readable description for the template.
+ template_description: string;
+
+ // A base64-encoded image selected by the merchant.
+ // This parameter is optional.
+ // We are not sure about it.
+ image?: ImageDataUrl;
+
+ // Additional information in a separate template.
+ template_contract: TemplateContractDetails;
+ }
+
+
+
+Removing template
+-----------------
+
+.. http:delete:: [/instances/$INSTANCE]/private/templates/$TEMPLATE_ID
+
+ This is used to delete information about a template. If we no longer use it.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The backend has successfully deleted the template.
+ :http:statuscode:`404 Not found`:
+ The backend does not know the instance or the template.
+
+
+
+Using template
+----------------
+
+.. http:post:: [/instances/$INSTANCES]/templates/$TEMPLATE_ID
+
+ This using template can be modified by everyone and will be used to create order.
+
+
+ **Request:**
+
+ The request must be a `UsingTemplateDetails` and we accept JSON application and URL encoded.
+
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The using template is successful. Returns a `UsingTemplateResponse`.
+ :http:statuscode:`302 Found`:
+ The client should go to the indicated location. Only returned if the content type was HTML. The target site may allow the client to pay his order.
+ :http:statuscode:`404 Not found`:
+ The merchant instance is unknown or the template is unknown.
+
+
+ .. ts:def:: UsingTemplateDetails
+
+ interface UsingTemplateDetails {
+
+ // Summary of the template
+ summary?: string;
+
+ // The amount entered by the customer.
+ amount?: Amount;
+ }
+
+ .. ts:def:: UsingTemplateResponse
+
+ interface UsingTemplateResponse {
+
+ // After enter the request. The user will be pay with a taler URL.
+ taler_url: string;
+ }
+
+
+--------
+Webhooks
+--------
+
+The webhook is a backend feature that is used to send a confirmation to the merchant. It can be send with a SMS, email or with another method. It will confirm that the
+customer paid the merchant. It will show the date and the price the customer paid.
+
+
+
+Adding webhooks
+---------------
+
+.. http:post:: [/instances/$INSTANCES]/private/webhooks
+
+ This is used to create a webhook.
+
+ **Request:**
+
+ The request must be a `WebhookAddDetails`.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The creation of the webhook is successsful.
+
+ :http:statuscode:`404 Not found`:
+ The merchant instance is unknowm or it not in our data.
+
+ .. ts:def:: WebhookAddDetails
+
+ interface WebhookAddDetails {
+
+ // Webhook ID to use.
+ webhook_id: string;
+
+ // The event of the webhook: why the webhook is used.
+ event_type: string;
+
+ // URL of the webhook where the customer will be redirected.
+ url: string;
+
+ // Method used by the webhook
+ http_method: string;
+
+ // Header template of the webhook
+ header_template?: string;
+
+ // Body template by the webhook
+ body_template?: string;
+
+ }
+
+
+Editing webhooks
+----------------
+
+.. http:patch:: [/instances/$INSTANCES]/private/webhooks/$WEBHOOK_ID
+
+ This is used to update a webhook.
+
+ **Request:**
+
+ The request must be a `WebhookPatchDetails`.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The webhook has successfully modified.
+ :http:statuscode:`404 Not found`:
+ The webhook(ID) is unknown to the backend.
+ :http:statuscode:`409 Conflict`:
+ The provided information is inconsistent with the current state of the webhook. Changes made is the same with another store.
+
+ .. ts:def:: WebhookPatchDetails
+
+ interface WebhookPatchDetails {
+
+ // The event of the webhook: why the webhook is used.
+ event_type: string;
+
+ // URL of the webhook where the customer will be redirected.
+ url: string;
+
+ // Method used by the webhook
+ http_method: string;
+
+ // Header template of the webhook
+ header_template?: string;
+
+ // Body template by the webhook
+ body_template?: string;
+
+ }
+
+
+
+Inspecting webhook
+------------------
+
+.. http:get:: [/instances/$INSTANCES]/private/webhooks
+
+ This is used to return the list of all the webhooks.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The backend has successfully returned all the webhooks. Returns a `WebhookSummaryResponse`.
+
+ :http:statuscode:`404 Not found`:
+ The backend has does not know about the instance.
+
+ .. ts:def:: WebhookSummaryResponse
+
+ interface WebhookSummaryResponse {
+
+ // List of webhooks that are present in our backend.
+ webhooks_list: WebhookEntry[];
+
+ }
+
+ The WebhookEntry object describes a webhook. It has the following structure:
+
+ .. ts:def:: WebhookEntry
+
+ interface WebhookEntry {
+
+ // Webhook identifier, as found in the webhook.
+ webhook_id: string;
+
+ // The event of the webhook: why the webhook is used.
+ event_type: string;
+
+ }
+
+
+.. http:get:: [/instances/$INSTANCES]/private/webhooks/$WEBHOOK_ID
+
+ This is used to obtain detailed information about apecific template.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The backend has successfully returned the detailed information about a specific webhook. Returns a `WebhookDetails`.
+
+ :http:statuscode:`404 Not found`:
+ The webhook(ID) is unknown to the backend.
+
+
+ .. ts:def:: WebhookDetails
+
+ interface WebhookDetails {
+
+ // The event of the webhook: why the webhook is used.
+ event_type: string;
+
+ // URL of the webhook where the customer will be redirected.
+ url: string;
+
+ // Method used by the webhook
+ http_method: string;
+
+ // Header template of the webhook
+ header_template?: string;
+
+ // Body template by the webhook
+ body_template?: string;
+
+ }
+
+
+Removing webhook
+-----------------
+
+.. http:delete:: [/instances/$INSTANCES]/private/webhooks/$WEBHOOK_ID
+
+ This is used to delete information about a webhook.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The backend has successfully deleted the webhook.
+
+ :http:statuscode:`404 Not found`:
+ The webhook(ID) or the instance is unknown to the backend.
+
+
+
------------------
The Contract Terms
------------------
@@ -3069,7 +3551,7 @@ The contract terms must have the following structure:
extra?: any;
}
-The wallet must select a exchange that either the merchant accepts directly by
+The wallet must select an exchange that either the merchant accepts directly by
listing it in the exchanges array, or for which the merchant accepts an auditor
that audits that exchange by listing it in the auditors array.
@@ -3124,6 +3606,15 @@ It has the following structure:
name: string;
// Label for a location with the business address of the merchant.
+ email?: string;
+
+ // Label for a location with the business address of the merchant.
+ website?: string;
+
+ // An optional base64-encoded product image.
+ logo?: ImageDataUrl;
+
+ // Label for a location with the business address of the merchant.
address?: Location;
// Label for a location that denotes the jurisdiction for disputes.
diff --git a/core/api-sync.rst b/core/api-sync.rst
index 759a4c3f..51e429e4 100644
--- a/core/api-sync.rst
+++ b/core/api-sync.rst
@@ -103,13 +103,15 @@ user's location profiles by linking client IP addresses and client
keys.
---------------------------
-Receiving Terms of Service
---------------------------
+.. include:: tos.rst
+
+-----------------------
+Receiving Configuration
+-----------------------
.. http:get:: /config
- Obtain the terms of service provided by the storage service.
+ Obtain the key configuration settings of the storage service.
**Response:**
@@ -137,9 +139,12 @@ Receiving Terms of Service
}
-
.. _sync:
+----------------------
+Recovering Backup Data
+----------------------
+
.. http:get:: /backups/${ACCOUNT-KEY}
Download latest version of the backup.
@@ -200,6 +205,10 @@ Receiving Terms of Service
signature validation.
+---------------------
+Uploading Backup Data
+---------------------
+
.. http:post:: /backups/${ACCOUNT-KEY}
Upload a new version of the account's database, or download the
diff --git a/core/api-taldir.rst b/core/api-taldir.rst
new file mode 100644
index 00000000..88fe5ce1
--- /dev/null
+++ b/core/api-taldir.rst
@@ -0,0 +1,254 @@
+..
+ This file is part of GNU TALER.
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Christian Grothoff
+ @author Martin Schanzenbach
+
+
+======================
+The TalDir RESTful API
+======================
+
+This is a proposed API for the TalDir service which allows Taler wallets to
+securely associate an inbox service (URL and public key) with the address of a
+messaging service used by the wallet's user. Wallets can also lookup the
+inbox of other users. This will enable wallets to make wallet-to-wallet
+payments to distant wallets where the target user is only identified by their
+address in a messaging service. Examples for messaging services include E-mail
+and SMS.
+
+The API specified here follows the :ref:`general conventions <http-common>`
+for all details not specified in the individual requests.
+The `glossary <https://docs.taler.net/glossary.html#glossary>`_
+defines all specific terms used in this section.
+
+
+.. include:: tos.rst
+
+
+-------------------------
+Configuration information
+-------------------------
+
+.. http:get:: /config
+
+ Return the protocol version and currency supported by this service.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The body is a `VersionResponse`.
+
+ .. ts:def:: VersionResponse
+
+ interface VersionResponse {
+ // libtool-style representation of the Merchant protocol version, see
+ // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+ // The format is "current:revision:age".
+ version: string;
+
+ // Name of the protocol.
+ name: "taler-directory";
+
+ // Supported registration methods
+ methods: Method[];
+
+ // fee for one month of registration
+ monthly_fee: Amount;
+
+ }
+
+ interface Method {
+ // Name of the method, e.g. "email" or "sms".
+ name: string;
+
+ // per challenge fee
+ challenge_fee: Amount;
+
+ }
+
+--------------------
+Address registration
+--------------------
+
+.. http:post:: /register/$METHOD
+
+ Endpoint to register, extend or modify the registration for an address in
+ the directory.
+ Here, $METHOD is the type of address to register, e.g. "email", or "phone".
+ Supported methods are listed in the VersionResponse.
+ Note that duration should be given as a multiple of a month in microseconds.
+ If the duration is not a multiple of a month it will be rounded to the
+ nearest multiple. Halfway values will be rounded away from zero.
+ The cost calculation and resulting registration validity will be adjusted
+ automatically.
+ In order to only modify the data, the duration may be set to 0.
+ When the call is made with unmodified data and a duration of 0, the
+ endpoint will return how long this registration is currently paid for.
+
+ **Request**
+
+ .. ts:def:: IdentityMessage
+
+ interface IdentityMessage {
+ // Address, in $METHOD-specific format
+ address: string;
+
+ // Public key of the user to register. As string in Crockfor base32 encoding.
+ public_key: EddsaPublicKey;
+
+ // (HTTPS) endpoint URL for the inbox service.
+ inbox_url: string;
+
+ // For how long should the registration last/be extended.
+ duration: RelativeTime;
+
+ }
+
+ **Response**
+
+ :http:statuscode:`200 Ok`
+ Registration already exists for this address for the specified duration.
+ Returns for how long this registration is paid for.
+ The response format is given by `AlreadyPaidResponse`_.
+ :http:statuscode:`202 Accepted`
+ Registration was initiated, the client should check for receiving
+ a challenge at the address where registration was attempted.
+ :http:statuscode:`402 Payment Required`
+ Client needs to make a Taler payment before proceeding. See
+ standard Taler payment procedure.
+ :http:statuscode:`404 Not found`
+ The TalDir service does not support the specified method.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`429 Too Many Requests`:
+ The client exceeded the number of allowed attempts for initiating
+ a challenge for this address in the given timeframe.
+ The response format is given by `RateLimitedResponse`_.
+
+ .. _RateLimitedResponse:
+ .. ts:def:: RateLimitedResponse
+
+ interface RateLimitedResponse {
+
+ // Taler error code, TALER_EC_TALDIR_REGISTER_RATE_LIMITED.
+ code: number;
+
+ // At what frequency are new registrations allowed.
+ request_frequency: RelativeTime;
+
+ // The human readable error message.
+ hint: string;
+
+ }
+
+ .. _AlreadyPaidResponse:
+ .. ts:def:: AlreadyPaidResponse
+
+ interface AlreadyPaidResponse {
+
+ // The remaining duration for which this registration is still paid for
+ valid_for: RelativeTime;
+
+ }
+
+
+.. http:get:: /register/$H_ADDRESS/$PINTAN
+
+ Endpoint that generates an HTML Web site with a QR code and
+ ``taler://taldir/$H_ADDRESS/$PINTAN-wallet`` link for completing the
+ registration. Useful to open the registration challenge in a browser (say if
+ it was received on a different device than where the wallet is running).
+ Does NOT complete the registration, as some providers automatically click on
+ all links in messages. Yes, we do not like them doing so either, but ``GET``
+ is a "safe" method according to the HTTP standard, so technically this is
+ allowed.
+
+ Opening the link will lead the **wallet** to do the POST call below. If the
+ Taler wallet can somehow intercept the URL (say for SMS, if it has the right
+ permissions) it can skip this request and directly do the POST, as all of
+ the required new information is already encoded in the URL.
+
+ Note that the wallet must be involved before the POST is made, as the
+ wallet's public key from the registration must be hashed with the ``$PINTAN``
+ to protect the user against phishing. Otherwise, someone else might attempt
+ a concurrent registration of a different public key, and the user might
+ accidentally authorize the registration of the public key of a different
+ wallet.
+
+.. http:post:: /$H_ADDRESS
+
+ This request is the last step of a registration, proving to the TalDir that
+ the user of the wallet is indeed able to receive messages at the specified
+ address. ``$H_ADDRESS`` is the SHA-512 hash of the address to be registered in
+ Crockford base32 encoding.
+
+ **Request**
+
+ .. ts:def:: IdentityConfirmation
+
+ interface IdentityConfirmation {
+ // The solution is the SHA-512 hash of the challenge ($PINTAN) value
+ // chosen by TalDir (encoded as string just as given in the URL, but
+ // excluding the 0-termination) concatenated with the binary 32-byte
+ // value representing the wallet's EdDSA public key.
+ // The hash is provided as string in Crockford base32 encoding.
+ solution: HashCode;
+
+ }
+
+ **Response**
+
+ :http:statuscode:`204 No Content`:
+ Registration complete.
+ :http:statuscode:`403 Forbidden`:
+ The ``solution`` is invalid. Retrying immediately is allowed.
+ :http:statuscode:`404 Not found`:
+ The address is unknown (original registration attempt may have expired).
+ :http:statuscode:`429 Too Many Requests`:
+ The client exceeded the number of allowed attempts for solving
+ a challenge for this address in the given timeframe.
+
+--------------
+Address lookup
+--------------
+
+.. http:get:: /$H_ADDRESS
+
+ Lookup the public key (and mailbox service base URL) associated with
+ an address in the TalDir. Here, ``$H_ADDRESS`` is the SHA-512 hash of
+ a (presumably) registered address in Crockford base32 encoding.
+
+ **Response**
+
+ Standard HTTP cache control headers are used to specify how long the
+ registration is still expected to be valid.
+
+ :http:statuscode:`200 Ok`:
+ Registration information returned, of type `MailboxDetailResponse`
+ :http:statuscode:`404 Not found`:
+ The address is unknown (original registration may have expired).
+
+ .. _MailboxDetailResponse:
+ .. ts:def:: MailboxDetailResponse
+
+ interface MailboxDetailResponse {
+
+ // Registered public key of the user. As string in Crockford base32 encoding.
+ public_key: EddsaPublicKey;
+
+ // (HTTPS) endpoint URL for the inbox service.
+ inbox_url: string;
+
+ }
diff --git a/core/api-wire.rst b/core/api-wire.rst
index 63373f68..34c0a0fc 100644
--- a/core/api-wire.rst
+++ b/core/api-wire.rst
@@ -327,6 +327,8 @@ Wire Transfer Test APIs
Endpoints in this section are only used for integration tests and never
exposed by bank gateways in production.
+.. _twg-admin-add-incoming:
+
.. http:post:: ${BASE_URL}/admin/add-incoming
Simulate a transfer from a customer to the exchange. This API is *not*
diff --git a/core/index.rst b/core/index.rst
index 6dd762fd..f44bc247 100644
--- a/core/index.rst
+++ b/core/index.rst
@@ -35,6 +35,8 @@ interfaces between the core components of Taler.
api-merchant
api-auditor
api-sync
+ api-taldir
+ api-mailbox
api-wire
api-bank-merchant
api-bank-integration
diff --git a/core/taler-uri.rst b/core/taler-uri.rst
index dd45611e..7b897ba1 100644
--- a/core/taler-uri.rst
+++ b/core/taler-uri.rst
@@ -68,7 +68,7 @@ The action ``withdraw`` is used to trigger a bank-integrated withdrawal operatio
taler://withdraw/{bank_host}{/bank_prefix_path*}/{withdrawal_uid}{#ssid}
-* ``bank_host`` is the hostname of the merchant.
+* ``bank_host`` is the hostname of the bank.
* ``bank_prefix_path`` is an optional list of path components that identifies the path prefix of the bank integration API base URL.
* ``withdrawal_uid`` is the unique ID of the withdrawal operation.
* ``ssid`` is the optional WLAN SSID that the bank (typically in an ATM scenario) can offer the wallet
@@ -121,11 +121,25 @@ a merchant and ask the user to accept/decline it.
* ``merchant_host`` is the hostname of the merchant.
* ``merchant_prefix_path`` is an optional list of path components that identifies the path prefix of the merchant base URL.
* ``tip_id`` uniquely identifies the tip.
-* ``insecure`` is an optional query parameter. When "1", the ``merchant_host`` is contacted via HTTP.
- When absent or "0", the ``merchant_host`` is contacted via HTTPS.
* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for Internet connectivity.
+---------------------
+Peer-to-peer payments
+---------------------
+
+A pay-push URI instructs the wallet to accept a P2P push payment.
+
+.. code:: none
+
+ taler://pay-push/{exchange_host}{/exchange_prefix_path*}/{contract_priv}
+
+* ``exchange_host`` is the hostname of the exchange.
+* ``exchange_prefix_path`` is an optional list of path components that identifies the path prefix of the exchange base URL.
+* ``contract_priv`` is the private key of the peer push payment contract stored at the exchange
+
+FIXME: pay-pull URIs missing!
+
----------------
Adding exchanges
----------------
diff --git a/core/tos.rst b/core/tos.rst
new file mode 100644
index 00000000..0cdb8986
--- /dev/null
+++ b/core/tos.rst
@@ -0,0 +1,42 @@
+--------------------
+Terms of service API
+--------------------
+
+These APIs allow clients to obtain the terms of service
+and the privacy policy of a service.
+
+
+.. http:get:: /terms
+
+ Get the terms of service of the service.
+ The endpoint will consider the "Accept" and "Accept-Language" and
+ "Accept-Encoding" headers when generating a response. Specifically,
+ it will try to find a response with an acceptable mime-type, then
+ pick the version in the most preferred language of the user, and
+ finally apply compression if that is allowed by the client and
+ deemed beneficial.
+
+ The endpoint will set an "Etag", and subsequent requests of the same client
+ should provide the tag in an "If-None-Match" header to detect if the terms
+ of service have changed. If not, a "304 Not Modified" response will be
+ returned. Note that the "304 Not Modified" will also be returned if the
+ client changed the "Accept-Language" or "Accept-Encoding" header. Thus, if
+ the client would like to download the resource in a different language or
+ format, the "If-None-Match" header must be omitted.
+
+ If the "Etag" is missing, the client should not cache the response and
+ instead prompt the user again at the next opportunity. This is usually only
+ the case if the terms of service were not configured correctly.
+
+ When returning a full response (not a "304 Not Modified"), the server
+ should also include a "Acceptable-Languages" header which includes
+ a space-separated list of the languages in which the terms of service
+ are available in. Clients can use this to generate a language switcher
+ for users that may not have expressed a proper language preference.
+
+
+.. http:get:: /privacy
+
+ Get the privacy policy of the service. Behaves the same way as
+ The "/terms" endpoint, except that it returns the privacy policy
+ instead of the terms of service.
diff --git a/core/wireformats.rst b/core/wireformats.rst
index d4ffe5f2..591f1ce8 100644
--- a/core/wireformats.rst
+++ b/core/wireformats.rst
@@ -56,7 +56,7 @@ follow the ``payto://`` specification for SEPA:
.. code-block:: none
- payto://sepa/IBAN
+ payto://iban/IBAN
.. [#sepa] SEPA - Single Euro Payments Area:
http://www.ecb.europa.eu/paym/sepa/html/index.en.html
diff --git a/demo-deployment.rst b/demo-deployment.rst
new file mode 100644
index 00000000..2f0b5a04
--- /dev/null
+++ b/demo-deployment.rst
@@ -0,0 +1,10 @@
+This document explains how to install Taler+LibEuFin
+on gv.taler.net, for demo.taler.net.
+
+1. Login as the ``demo`` user on ``gv.taler.net``.
+2. Pull the latest ``deployment.git`` code.
+3. Navigate to the ``deployment.git/docker/demo`` directory.
+4. The README file is the reference about building and running the services.
+
+Nginx is already configured to reach the services as exported by
+Docker Compose.
diff --git a/design-documents/001-new-browser-integration.rst b/design-documents/001-new-browser-integration.rst
index 8dbe2a43..80a08859 100644
--- a/design-documents/001-new-browser-integration.rst
+++ b/design-documents/001-new-browser-integration.rst
@@ -1,5 +1,5 @@
-Design Doc 001: New Browser Integration
-#######################################
+DD1: New Browser Integration
+############################
.. warning::
diff --git a/design-documents/002-wallet-exchange-management.rst b/design-documents/002-wallet-exchange-management.rst
index 9d10045a..297c23f8 100644
--- a/design-documents/002-wallet-exchange-management.rst
+++ b/design-documents/002-wallet-exchange-management.rst
@@ -1,5 +1,5 @@
-Design Doc 002: Wallet Exchange Management
-##########################################
+DD2: Wallet Exchange Management
+###############################
.. note::
@@ -40,7 +40,7 @@ audited by a trusted auditor.
An exchange might only be known the wallet temporarily. For example,
the wallet UI may allow the user to review the fee structure of an
exchange before the wallet is permanently added to the wallet.
-Once a an exchange is either (a) marked as trusted or (b) used for a
+Once an exchange is either (a) marked as trusted or (b) used for a
withdrawal operation, it is marked as permanent.
Exchanges that are not permanent will be automatically be removed
diff --git a/design-documents/003-tos-rendering.rst b/design-documents/003-tos-rendering.rst
index 3011775c..9b19ee02 100644
--- a/design-documents/003-tos-rendering.rst
+++ b/design-documents/003-tos-rendering.rst
@@ -1,5 +1,5 @@
-Design Doc 003: ToS rendering
-#############################
+DD3: ToS rendering
+##################
Summary
=======
diff --git a/design-documents/004-wallet-withdrawal-flow.rst b/design-documents/004-wallet-withdrawal-flow.rst
index 28e9c16b..a385959b 100644
--- a/design-documents/004-wallet-withdrawal-flow.rst
+++ b/design-documents/004-wallet-withdrawal-flow.rst
@@ -1,5 +1,5 @@
-Design Doc 004: Wallet Withdrawal Flow
-######################################
+DD4: Wallet Withdrawal Flow
+###########################
Summary
=======
diff --git a/design-documents/005-wallet-backup-sync.rst b/design-documents/005-wallet-backup-sync.rst
index 20fce37b..7592242c 100644
--- a/design-documents/005-wallet-backup-sync.rst
+++ b/design-documents/005-wallet-backup-sync.rst
@@ -1,5 +1,5 @@
-Design Doc 005: Wallet Backup and Sync
-######################################
+DD5: Wallet Backup and Sync
+###########################
.. warning::
@@ -56,13 +56,13 @@ Entities that are **not** synchronized are:
* withdrawal operations before they have been accepted by the user
Entities that **could** be synchronized (to be decided):
-
+
* private keys of other sync accounts
* coin planchets
* tips before the corresponding coins have been withdrawn
* refresh sessions (not only the "meta data" about the operation,
but everything)
-
+
Garbage collection
------------------
diff --git a/design-documents/006-extensions.rst b/design-documents/006-extensions.rst
index e663b2f5..9dde065d 100644
--- a/design-documents/006-extensions.rst
+++ b/design-documents/006-extensions.rst
@@ -1,5 +1,5 @@
-Design Document 006: Extensions for GNU Taler
-#############################################
+DD6: Extensions for GNU Taler
+#############################
Summary
=======
@@ -18,6 +18,7 @@ of 2021 and 2022:
* Peer-to-peer payments
* Anonymous age-restriction
* Escrow service for anonymous auctions
+* A general escrow service
We call a feature an *extension* when it is *optional* for either the
exchange, wallet or merchant to enable and support it. (However, enabling
@@ -31,9 +32,6 @@ participants.
Requirements
============
-TODO. Not sure if we have any requirements - other than particular
-ideas/designs for extensions?
-
Proposed Solution
=================
@@ -41,70 +39,151 @@ Proposed Solution
Exchange
^^^^^^^^
-The exchange will add two new REQUIRED fields in response to ``/keys``:
+The exchange will add two new *optional* fields in response to ``/keys``:
-#. The (but maybe empty) field ``extensions`` which contains a dictionary of
+#. The field ``extensions`` which contains a dictionary of
extension-names and their configuration, see below.
-#. The field ``extensions_sig`` that contains the EdDSA signature of the SHA256-hash
- of the normalized JSON-string of the ``extenstions`` object.
-
-The names of extensions MUST be unique and SHOULD include a version information
-in Taler's `protocol version ranges notation`_ as suffix starting with letter
-'``v``', f.e.: ``age_restriction.v1`` or ``p2p.v1:2:3``.
+#. The field ``extensions_sig`` that contains the EdDSA signature of the
+ SHA256-hash of the normalized JSON-string of the ``extensions`` object.
-.. _protocol version ranges notation: https://docs.taler.net/core/api-common.html#protocol-version-ranges
+The necessary changes to ``ExtensionsManifestsResponse`` are highlighted here:
-The necessary changes to ``ExchangeKeysResponse`` are highlighted here:
+.. ts:def:: ExtensionsManifestsResponse
-.. ts:def:: ExchangeKeysResponse
-
- interface ExchangeKeysResponse {
+ interface ExtensionsManifestsResponse {
//...
- // Required (but maybe emtpy) field with a dictionary of (name, object)
- // pairs defining the supported extensions.
- // The name MUST be unique and SHOULD include version information in Taler's
- // protocol version ranges notation as suffix, starting with letter 'v',
- // f.e.: "age_restriction.v1" or "p2p.v1:2:3".
- extensions: { name: Extension };
+ // Optional field with a dictionary of (name, object) pairs defining the
+ // supported and enabled extensions.
+ // The name MUST be non-empty and unique.
+ extensions?: { name: ExtensionManifest };
- // Signature by the exchange master key of the SHA-512 hash of the
- // normalized JSON-object of field ``extenstions``.
+ // Signature by the exchange master key of the SHA-256 hash of the
+ // normalized JSON-object of field ``extensions``, if it was set.
// The signature MUST have purpose ``TALER_SIGNATURE_MASTER_EXTENSIONS``.
- extensions_sig: EddsaSignature;
+ extensions_sig?: EddsaSignature;
//...
}
-The definition of ``Extension`` object itself is mostly up to the particular
-feature. However, it MUST contain the following fields:
+Extension names
+---------------
+
+The names of extensions MUST be unique. The full name MUST be registered with
+GANA_ along with a full description of the extension.
+
+.. _GANA: https://git.gnunet.org/gana.git
+
+(In the rare situation that the exchange might have to provide *multiple*
+versions of the "same" feature in parallel, multiple unique names MUST be used,
+f.e. ``age_restriction`` an ``age_restriction.v2``.)
+
+ExtensionManifest object
+---------------------------
+
+The definition of ``ExtensionManifest`` object itself is mostly up to the
+particular feature. **However**, it MUST have
-* ``description`` ― a short description of the feature itself. Can be used by wallets to display information about the feature to the customer.
+#. the boolean field ``critical`` that has the same semantics as as "critical"
+ has for extensions in X.509_: if true, the client must "understand" the
+ extension before proceeding, if "false" clients can safely skip extensions
+ they do not understand.
-* ``required`` ― a boolean that indicates if this feature MUST be supported by the wallets and/or merchants in order to use this exchange.
+#. the field ``version`` of type `LibtoolVersion` which contains the version
+ information of the extension in Taler's `protocol version ranges notation`_.
-.. ts:def:: Extension
+.. _X.509: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2
- interface Extension {
- // Short description of the feature.
- description: string;
+.. _`protocol version ranges notation`: https://docs.taler.net/core/api-common.html#protocol-version-ranges
- // Set to ``true`` if this extension MUST be supported by wallets and/or
- // merchants.
- required: boolean;
+.. ts:def:: ExtensionManifest
- // Additional fields defined by the feature itself
- ...
+ interface ExtensionManifest {
+ // The criticality of the extension MUST be provided. It has the same
+ // semantics as "critical" has for extensions in X.509:
+ // - if "true", the client must "understand" the extension before
+ // proceeding,
+ // - if "false", clients can safely skip extensions they do not
+ // understand.
+ // (see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2)
+ critical: boolean;
+ // The version information MUST be provided in Taler's protocol version
+ // ranges notation, see
+ // https://docs.taler.net/core/api-common.html#protocol-version-ranges
+ version: LibtoolVersion;
+
+ // Optional configuration object, defined by the feature itself
+ config?: object;
}
-**TODO**:
+Configuration
+-------------
+
+Extensions are *disabled* per default and must *explicetly* be enabled in the
+the TALER configuration manually. The configurations of all enabled extensions
+are signed with the master key and uploaded to the exchange with the tool
+``taler-exchange-offline``.
+
+Each extension has its own section in the configuration, starting with the
+prefix ``exchange-extension-``, like ``[exchange-extension-age_restriction]``.
+The field ``ENABLED = YES|NO`` is used to enable or disable the corresponding
+extension. If the extension has its own configuration parameters, they MAY be
+optional, in which case the ``taler-exchange-offline`` tool MUST fill them with
+safe default values.
+
+The ``taler-exchange-offline`` tool MUST offer the subcommand ``extensions``
+for showing and signing extensions. For this purpose, the following
+sub-subcommands MUST be available:
+
+* ``extensions show``: List all available extensions, their versions,
+ criticality and whether they are enabled.
+* ``extensions sign``: Sign the configuration of all enabled extensions with
+ the master key and prepare a JSON-object for the ``upload`` command.
+
+When extensions are offered and enabled by an exchange, the ``extensions``
+object MUST be signed by the exchange's master signing key. Whenever
+extensions are enabled or disabled, the offline tool MUST sign the SHA256 hash
+of the normalized JSON-string of the ``extensions`` object, if it is not empty.
+
+In order to do so, the ``taler-exchange-offline`` tool MUST
+
+#. have the complete list of all available optional features/extensions and
+ their versions builtin and
+
+#. understand them (including the version). For example, the extension for
+ age-restriction will require the exchange to perform particular steps when
+ this extension is enabled (i.e. signing denominations with support with age
+ restriction *together* with the string of age groups).
+
+#. reject a configuration that refers to any extension that the tool does not
+ know or understand.
+
+Similarly, the exchange MUST reject a signed configuration with extensions it
+does not know or understand.
+
+Examples
+--------
+
+A configuration for age-restriction in the taler configuration would look like
+this:
+
+.. code:: none
+
+ [exchange-extension-age_restriction]
+ ENABLED = true
+ # default:
+ AGE_GROUPS = "8:10:12:14:16:18:21"
+
+
+ [exchange-extension-policy_brandt_vickery_auction]
+ ENABLED = true
+ REPLAY_PROGRAM = "/usr/local/bin/taler-exchange-auction_replay"
-* Add examples for age-restriction and p2p.
Merchant
^^^^^^^^
@@ -113,6 +192,16 @@ TODO:
* Needs to express support for particular extensions, too. F.e. age-restriction.
+Extension Plugins
+==================
+
+TODO:
+
+* describe ``struct TALER_Extension``
+* describe the plugin loading mechanism for extensions
+* describe the various handlers
+
+
Alternatives
============
@@ -132,4 +221,3 @@ Discussion / Q&A
The initial ideas presented here are based on discussions between Özgür Kesim
and Christian Grothoff.
-
diff --git a/design-documents/007-payment.rst b/design-documents/007-payment.rst
index 115ac3f8..2750c816 100644
--- a/design-documents/007-payment.rst
+++ b/design-documents/007-payment.rst
@@ -1,5 +1,5 @@
-Design Doc 007: Specification of the Payment Flow
-#################################################
+DD7: Specification of the Payment Flow
+######################################
Summary
=======
diff --git a/design-documents/008-fees.rst b/design-documents/008-fees.rst
index 47c2af8a..b99190c4 100644
--- a/design-documents/008-fees.rst
+++ b/design-documents/008-fees.rst
@@ -1,5 +1,5 @@
-Design Doc 008: Fee Structure Metrics
-#####################################
+DD8: Fee Structure Metrics
+##########################
.. note::
diff --git a/design-documents/009-backup.rst b/design-documents/009-backup.rst
index 04ae628c..1cf9bb23 100644
--- a/design-documents/009-backup.rst
+++ b/design-documents/009-backup.rst
@@ -1,5 +1,5 @@
-Design Doc 009: Wallet Backup
-#############################
+DD9: Wallet Backup
+##################
Summary
=======
diff --git a/design-documents/010-exchange-helpers.rst b/design-documents/010-exchange-helpers.rst
index e67fca4e..fe9de063 100644
--- a/design-documents/010-exchange-helpers.rst
+++ b/design-documents/010-exchange-helpers.rst
@@ -1,5 +1,5 @@
-Design Doc 010: Exchange crypto helper design
-#############################################
+DD10: Exchange crypto helper design
+###################################
Summary
=======
@@ -13,7 +13,7 @@ Motivation
We want to provide an additional layer of protection for the private online
signing keys used by the exchange. The exchange is network-facing, includes an
-HTTP server, Postgres interaction, JSON parser and quite a bit of other logic
+HTTP server, PostgreSQL interaction, JSON parser and quite a bit of other logic
which may all be theoretically vulnerable to remote exploitation. Thus, it
would be good from a security perspective to protect the private online
signing keys via an additional layer of protection.
@@ -89,7 +89,7 @@ Exchange design considerations:
exchange. This simplifies the exchange, and we already needed the
exchange operator to start four processes to operate an exchange.
So this number simply increases to six (not even counting the
- Postgres database and a reverse HTTP proxy for TLS termination).
+ PostgreSQL database and a reverse HTTP proxy for TLS termination).
* Each exchange thread will create its own connection to the helpers, and will
block while waiting on the helper to create a signature. This keeps the
exchange logic simple and similar to the existing in-line signing calls.
@@ -108,7 +108,7 @@ New exchange endpoints:
sign based on that file, and then upload the resulting signature back to
the exchange. For this, master signatures will be POSTed to
the exchange to the ``/keys`` endpoint.
- The exchange will keep those signatures in the Postgres database.
+ The exchange will keep those signatures in the PostgreSQL database.
* A new endpoint (``/auditors``) will also allow adding/removing auditors
(POST, DELETE) using requests signed with the offline master private key.
Once an auditor has been added, the respective auditor signatures on exchange
diff --git a/design-documents/011-auditor-db-sync.rst b/design-documents/011-auditor-db-sync.rst
index fb2e3bea..8a460ed8 100644
--- a/design-documents/011-auditor-db-sync.rst
+++ b/design-documents/011-auditor-db-sync.rst
@@ -1,5 +1,5 @@
-Design Doc 011: Auditor-Exchange Database Synchronization
-#########################################################
+DD11: Auditor-Exchange Database Synchronization
+###############################################
Summary
=======
@@ -62,7 +62,7 @@ Proposed Solution
=================
* Use "common" incremental database replication (whichever is
- approproate for the exchange database setup, synchronous
+ appropriate for the exchange database setup, synchronous
or asynchronous) to make a 1:1 copy of the exchange database
at the auditor. This should work for any full-featured
modern database. This "ingress" copy cannot be trusted, as constraint
@@ -89,9 +89,9 @@ Proposed Solution
* The auditor's "ingress" database should be well isolated from
the rest of the auditor's system and database
(different user accounts). The reason is that we should not
- assume that the Postgres replication code is battle-tested with
+ assume that the PostgreSQL replication code is battle-tested with
malicious parties in mind.
-* The canonical Postgres synchronization between exchange and the
+* The canonical PostgreSQL synchronization between exchange and the
auditor's "ingress" database must use transport security.
The above solution does not gracefully handle mutable tables on which
@@ -148,10 +148,10 @@ A good order for replicating the tables should be:
Alternatives
============
-* Copy the Postgres WAL, filter it for "illegal" operations
+* Copy the PostgreSQL WAL, filter it for "illegal" operations
and then apply it at the auditor end. Disadvantages: WAL
filtering is not a common operation (format documented?),
- this would be highly Postgres-specific, and would require
+ this would be highly PostgreSQL-specific, and would require
complex work to write the filter. Also unsure how one
could later recover gracefully from transient errors
(say where the exchange recified a bogus DELETE).
diff --git a/design-documents/012-fee-schedule-metrics.rst b/design-documents/012-fee-schedule-metrics.rst
index 8eb36da7..3ab7f565 100644
--- a/design-documents/012-fee-schedule-metrics.rst
+++ b/design-documents/012-fee-schedule-metrics.rst
@@ -1,5 +1,5 @@
-Design Doc 012: Exchange Fee Configuration
-##########################################
+DD12: Exchange Fee Configuration
+################################
.. note::
@@ -466,7 +466,7 @@ Note that for a typical transaction, the number of coins is logarithmic to the
amount. So with the above fee structure, paying amounts around 10 EUR would on
average involve about 6 coins with 1/3rd fees at 0.005, 1/3rd fees at 0.01 and
1/3rd fees at 0.015, resulting in an expected total transaction cost in
-**deposit** fees of 0.03 EUR. In constrast, paying 0.50 cents would require
+**deposit** fees of 0.03 EUR. In contrast, paying 0.50 cents would require
on average 4 coins cost less than 0.02 EUR in **deposit** fees. As a result
of this fee structure, microtransactions with Taler have a higher fee in terms
of percentage, while larger transactions are still highly competitive.
@@ -544,4 +544,4 @@ Other documents regarding fee specifications:
* Fee schedule and metrics from the users' point of view :doc:`008-fees`
-* Wire fee for different wiring methods (``sepa`` or ``x-taler-wire``) <https://docs.taler.net/taler-exchange-manual.html#wire-fee-structure>
+* Wire fee for different wiring methods (``iban`` or ``x-taler-wire``) <https://docs.taler.net/taler-exchange-manual.html#wire-fee-structure>
diff --git a/design-documents/013-peer-to-peer-payments.rst b/design-documents/013-peer-to-peer-payments.rst
index c44ab348..951f7f81 100644
--- a/design-documents/013-peer-to-peer-payments.rst
+++ b/design-documents/013-peer-to-peer-payments.rst
@@ -1,5 +1,5 @@
-Design Doc 013: Wallet-to-Wallet Payments
-#########################################
+DD13: Wallet-to-Wallet Payments
+###############################
Summary
=======
@@ -376,6 +376,8 @@ In this protocol variant, the payer is initiating the process.
3. The payer shares the purse's private key and the base URL
of the exchange where the purse was created with the payee.
This can be done using a ``taler://purse/$BASE_URL/$PURSE_PRIV`` URL.
+ The chapter on ``Refinements`` below clarifies why this
+ step is not quite OK and was modified when implementing the design.
4. The payee uses the new ``/purse/$PURSE_PUB`` endpoint to retrieve
the encrypted contract (if available) and purse balance, which includes all
(coin) deposits and **merges** involving the purse.
@@ -703,7 +705,6 @@ database.)
(merge_request_serial_id BIGSERIAL UNIQUE
,reserve_uuid BYTEA NOT NULL REFERENCES reserves (reserve_uuid) ON DELETE CASCADE
,partner_serial_id INT8 REFERENCES partners(partner_serial_id) ON DELETE CASCADE,
- ,reserve_url TEXT NOT NULL,
,reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32),
,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32),
,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64))
@@ -721,8 +722,6 @@ database.)
IS 'identifies the reserve';
COMMENT ON COLUMN mergers.partner_serial_id
IS 'identifies the partner exchange, NULL in case the target reserve lives at this exchange';
- COMMENT ON COLUMN mergers.reserve_url
- IS 'payto://-URL of the reserve, identifies the exchange and the reserve';
COMMENT ON COLUMN mergers.reserve_pub
IS 'public key of the target reserve';
COMMENT ON COLUMN mergers.purse_pub
@@ -821,7 +820,6 @@ database.)
CREATE TABLE IF NOT EXISTS purse_deposits
(purse_deposit_serial_id BIGSERIAL UNIQUE
,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32),
- ,purse_expiration INT8 NOT NULL
,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
,amount_with_fee_val INT8 NOT NULL
,amount_with_fee_frac INT4 NOT NULL
@@ -832,8 +830,6 @@ database.)
IS 'Requests depositing coins into a purse';
COMMENT ON COLUMN purse_deposits.purse_pub
IS 'Public key of the purse';
- COMMENT ON COLUMN purse_deposits.purse_expiration
- IS 'When the purse is set to expire';
COMMENT ON COLUMN purse_deposits.coin_pub
IS 'Public key of the coin being deposited';
COMMENT ON COLUMN purse_deposits.amount_with_fee_val
@@ -1072,6 +1068,33 @@ Aside from implementation complexity, the solution has the following drawbacks:
account private key before initiating the KYC process.
+Refinements
+===========
+
+In the original design, a payer making a payment offer sends the purse private
+key to the payee, so that the payee can sign the merge request with it. This
+creates a security issue, as theoretically the payee could sign a different
+contract with the purse private key, and conspire with the exchange to replace
+the original contract. In this case, the payer would be making a payment to
+the "wrong" contract, and have no proof of the exchange an payee conspiring
+against it.
+
+A simple fix seems possible: instead of having simply one public-private key
+pair for a purse, we have a PayerContractKey and a PurseMergeKey pair. The payer
+would pay into a purse identified by the PayerContractKey and associate a
+PurseMergeKey with the purse. The payer can then safely share the
+PayeeMergeKey with the payee, as it is ONLY useful for the merge and not to
+sign the contract. Payments would be made into a purse identified by the
+PurseContractKey.
+
+When payments flow in the other direction, the split of the keys seems
+unnecessary (as only a public key is transmitted anyway. However, schema-wise,
+signing the contract with the PurseContractKey and the merge with the
+PurseMergeKey would still work. Only the public PurseContractKey would need
+to be sent to the payer.
+
+
+
Q / A
=====
diff --git a/design-documents/014-merchant-backoffice-ui.rst b/design-documents/014-merchant-backoffice-ui.rst
index 8c4104dc..63c326cc 100644
--- a/design-documents/014-merchant-backoffice-ui.rst
+++ b/design-documents/014-merchant-backoffice-ui.rst
@@ -1,5 +1,5 @@
-Design Doc 014: Merchant backoffice UI
-######################################
+DD14: Merchant backoffice UI
+############################
Motivation
diff --git a/design-documents/015-merchant-backoffice-routing.rst b/design-documents/015-merchant-backoffice-routing.rst
index f6372dcc..e5110bee 100644
--- a/design-documents/015-merchant-backoffice-routing.rst
+++ b/design-documents/015-merchant-backoffice-routing.rst
@@ -1,21 +1,21 @@
-Design Doc 015: Merchant backoffice Routing
-###########################################
+DD15: Merchant backoffice Routing
+#################################
Motivation
==========
A well defined routing will allow users to share backoffice links pointing
-directly into instance pages (settings, orders, products, etc...)
+directly into instance pages (settings, orders, products, etc...)
-The backoffice should load from the instance URL and then allow a internal
+The backoffice should load from the instance URL and then allow an internal
routing of the views with the possibility to accessing them directly when
sharing a link.
This 3 definitions are going to be use in this document:
-* BACKOFFICE_URL as the url where the app is loaded.
-
+* BACKOFFICE_URL as the url where the app is loaded.
+
* BACKEND_URL as the url where the merchant backend is.
* INSTANCE the name of the instance being manage
@@ -27,13 +27,13 @@ Application Ready definition
The application is considered ready after
* the user tried to login.
-
+
* the application checked that the backend url points to a merchant backend
* the merchant backend response successfully
The backoffice test for ``$BACKEND_URL/config`` to define if the $BACKEND_URL is ok.
-The application can potentially test if the protocol or version matched.
+The application can potentially test if the protocol or version matched.
While the application is not ready, just the top navigation bar will be shown
with a message in the left and the lang selection option.
@@ -58,7 +58,7 @@ Knowing that the $BACKEND_URL points to a correct merchant backend the SPA will
check for ``$BACKEND_URL/management/instances``:
* if Unauthorized ask for credentials
-
+
* if error check with the user
* if not found, then url should end with ``/instances/$INSTANCE``. otherwise is
@@ -69,11 +69,11 @@ check for ``$BACKEND_URL/management/instances``:
When a user access the SPA there are 3 scenarios possible:
* **standard**: admin is false so BACKEND_URL points to a non-default instance.
- standard features and links are shown
+ standard features and links are shown
* **admin**: admin is true so BACKEND_URL point to default instance. same as
before and user can create and list instances with some additional links in
- the sidebar.
+ the sidebar.
* **mimic**: admin is true and the request parameter "instance" is set $INSTANCE
instance. BACKEND_URL point to default instance but the user is managing
@@ -99,7 +99,7 @@ parameter (like order id or product id) it should be accessible from the Sidebar
If the user has admin access, this entry points are available:
- /instances: Show the list of instances currently created
- - /instance/new: Show a instance creation form
+ - /instance/new: Show an instance creation form
Where admin or not, there is also this entry points:
@@ -145,7 +145,7 @@ credentials or the backend url
Not found
---------
-For any case that the backend respond 404 the application will render a
+For any case that the backend respond 404 the application will render a
custom not found page
Default instance is missing
@@ -155,6 +155,3 @@ If the **user is admin** AND is loading the setting page (/update), product list
(/products), order list (/orders) or transfer list (/transfers) AND **gets a
404** it will tell the user that it need to create a default instance before
proceeding.
-
-
-
diff --git a/design-documents/016-backoffice-order-management.rst b/design-documents/016-backoffice-order-management.rst
index 00250cd2..e2fca0fe 100644
--- a/design-documents/016-backoffice-order-management.rst
+++ b/design-documents/016-backoffice-order-management.rst
@@ -1,5 +1,5 @@
-Design Doc 016: Backoffice Order Management
-###########################################
+DD16: Backoffice Order Management
+#################################
Summary
=======
@@ -35,7 +35,7 @@ Listing orders
.. image:: ../backoffice-order-list.svg
:width: 800
-4 tabs will be show for a easy access to common filter, click on any of this and
+4 tabs will be show for an easy access to common filter, click on any of this and
search will reset all filter except date
* paid (default)
@@ -88,7 +88,7 @@ This section has two parts:
The first part will add/remove product from the current stock.
* ``name``: search box to select product by description field. if not found it
- will be a 'create new' option leading to the create product without loosing
+ will be a 'create new' option leading to the create product without losing
context
* ``quantity``: mandatory
diff --git a/design-documents/017-backoffice-inventory-management.rst b/design-documents/017-backoffice-inventory-management.rst
index b2fc6dfd..30a6d835 100644
--- a/design-documents/017-backoffice-inventory-management.rst
+++ b/design-documents/017-backoffice-inventory-management.rst
@@ -1,5 +1,5 @@
-Design Doc 017: Backoffice Inventory Management
-###############################################
+DD17: Backoffice Inventory Management
+#####################################
Summary
=======
@@ -55,7 +55,7 @@ Create and Update Product form
.. image:: ../backoffice-product-create.svg
:width: 800
-
+
Update product will use the same form except for the ``product_id``
* product_id: BACKOFFICE_URL + id
@@ -64,7 +64,7 @@ Update product will use the same form except for the ``product_id``
* name: required, one line
* extra: optional, free text area
-* description localized: list with
+* description localized: list with
* lang: dropdown list with supported lang + custom
* description: text area
@@ -95,7 +95,7 @@ Stock management
* ``never`` button will set next restock to never
-* when updating the product, the option ``without stock`` will no be available
+* when updating the product, the option ``without stock`` will no be available
if the product already has stock
* if the product already exist then:
@@ -119,7 +119,7 @@ Alternatives
============
* price and stock columns in the list can be merged into a more complex column
- with the same information
+ with the same information
* rows in the table can be expandable when clicked to get access to some common
actions like increase stock or change price
@@ -128,7 +128,7 @@ Alternatives
:width: 800
* detail page was intentionally left out since all information can be access
- from the update page
+ from the update page
Q&A
===
@@ -138,8 +138,4 @@ Q&A
* can we allow add extra data like order has in contractTerm?, this could be
useful for frontend apps. example of usage: country/state to where the product
- is sold since taxes may vary
-
-
-
-
+ is sold since taxes may vary
diff --git a/design-documents/018-contract-json.rst b/design-documents/018-contract-json.rst
index d7f78257..f930bcbd 100644
--- a/design-documents/018-contract-json.rst
+++ b/design-documents/018-contract-json.rst
@@ -1,5 +1,5 @@
-Design Doc 018: Forgettable Data in JSON Contract Terms
-#######################################################
+DD18: Forgettable Data in JSON Contract Terms
+#############################################
Summary
=======
@@ -92,7 +92,7 @@ scrubbed and canonicalized value is put into the special ``$forgotten$``
member of the parent object.
-.. code-block:: json
+.. code-block:: javascript
{
...props,
@@ -149,7 +149,7 @@ SHA512.
Test vector
-----------
-The follwing input contains top-level and nested forgettable
+The following input contains top-level and nested forgettable
fields, as well as booleans, integers, strings and objects
as well as non-forgettable fields. It is thus suitable as
a minimal interoperability test:
diff --git a/design-documents/019-wallet-backup-merge.rst b/design-documents/019-wallet-backup-merge.rst
index caf45401..f716c7ba 100644
--- a/design-documents/019-wallet-backup-merge.rst
+++ b/design-documents/019-wallet-backup-merge.rst
@@ -1,5 +1,5 @@
-Design Doc 019: Wallet Backup Merging
-#####################################
+DD19: Wallet Backup Merging
+###########################
Summary
=======
diff --git a/design-documents/020-backoffice-tips-management.rst b/design-documents/020-backoffice-tips-management.rst
index 566f8fb5..6049109e 100644
--- a/design-documents/020-backoffice-tips-management.rst
+++ b/design-documents/020-backoffice-tips-management.rst
@@ -1,5 +1,5 @@
-Design Doc 020: Backoffice Tips Management
-##########################################
+DD20: Backoffice Tips Management
+################################
Summary
=======
@@ -93,5 +93,3 @@ When the reserve has not yet funded
.. image:: ../backoffice-reserve-details.unfunded.svg
:width: 400
-
-
diff --git a/design-documents/021-exchange-key-continuity.rst b/design-documents/021-exchange-key-continuity.rst
index e09b6fe0..877ffdf8 100644
--- a/design-documents/021-exchange-key-continuity.rst
+++ b/design-documents/021-exchange-key-continuity.rst
@@ -1,5 +1,5 @@
-Design Doc 021: Exchange Key Continuity
-#######################################
+DD21: Exchange Key Continuity
+#############################
Summary
=======
diff --git a/design-documents/023-taler-kyc.rst b/design-documents/023-taler-kyc.rst
index 3e67bff2..bfc6e514 100644
--- a/design-documents/023-taler-kyc.rst
+++ b/design-documents/023-taler-kyc.rst
@@ -1,5 +1,5 @@
-DD 023: Taler KYC
-#################
+DD23: Taler KYC
+###############
Summary
=======
@@ -17,6 +17,8 @@ banks to identify parties involved in transactions at certain points.
Requirements
============
+The solution should support fees to be paid by the user for the KYC process (#7365).
+
Taler needs to run KYC checks in the following circumstances:
* Customer withdraws money over a monthly threshold
@@ -31,150 +33,254 @@ Taler needs to run KYC checks in the following circumstances:
* Wallet receives money via P2P payments
+ * there are two sub-cases: PUSH and PULL payments
* key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)
* Merchant receives money (Q: any money, or above a monthly threshold?)
* key: IBAN (encoded as payto:// URI)
+* Reserve is "opened" for invoicing or tipping.
+
+ * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)
+
Proposed Solution
=================
-Exchange modifications
-^^^^^^^^^^^^^^^^^^^^^^
+Terminology
+^^^^^^^^^^^
-We introduce a new ``wire_targets`` table into the exchange database. This
-table is referenced as the source or destination of payments (regular deposits
-and also P2P payments). A positive side-effect is that we reduce duplication
-in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
-reference this table. In this table, we additionally store information
-related to the KYC status of the underlying payto://-URI.
-
-The new ``/kyc-check/`` endpoint is based on the ``wire_targets`` serial
-number. Access is ``authenticated`` by also passing the hash of the
-payto://-URI (weak authentication is acceptable, as the KYC status or the
-ability to initiate a KYC process are not very sensitive). Given this pair,
-the ``/kyc-check/`` endpoint returns either the (positive) KYC status or
-redirects the client (202) to the current stage of the KYC process. (The
-endpoint may have to create and store a nonce to be used during
-``/kyc-proof/``, depending on the OAuth variant used.) The redirection is
-offered using an HTTP-redirect for Web-based clients and a JSON body with
-information for triggering a browser-based KYC process using OAuth 2.0.
-
-The OAuth 2.0 process is setup to end at a new ``/kyc-proof/`` endpoint. This
-endpoint then updates the KYC table of the exchange with the legitimization
-status (which is checked using OAuth 2.0). The endpoint also wakes up any
-long-polling ``/kyc-check/`` requests. Naturally, the exchange's OAuth 2.0
-client credentials must be configured apriori with the legitimization service.
+* **Check**: A check establishes a particular attribute of a user, such as their name based on an ID document and lifeness, mailing address, phone number, taxpayer identity, etc.
-When withdrawing, the exchange checks if the KYC status is acceptable. If no
-KYC was done and if either the amount withdrawn over the last X days exceeds
-the threshold or the reserve received received a P2P transfer, then a ``202
-Accepted`` is returned which redirects the consumer to the new ``/kyc-check/``
-handler.
+* **Condition**: A condition specifies when KYC is required. Conditions include the *type of operation*, a threshold amount (e.g. above EUR:1000) and possibly a time period (e.g. over the last month).
-When depositing, the exchange checks the KYC status and if negative, returns an
-additional information field that tells the merchant the ``wire_target_serial``
-number needed to begin the KYC process (this is independent of the amount)
-at the new ``/kyc-check/`` handler.
+* **Configuration**: The configuration determines the *legitimization rules*, and specifies which providers offer which *checks* at what *cost*.
-When tracking deposits, the exchange also adds the ``wire_target_serial`` to
-the reply if the KYC status is negative.
+* **Cost**: Metric for the business expense for a KYC check at a certain *provider*. Not in any currency, costs are simply relative and non-negative values. Costs are considered when multiple choices are allowed by the *configuration*.
-The aggregator is modified to only SELECT deposits where the ``wire_target``
-has the KYC status set to positive (unless KYC is disabled in the exchange
-configuration).
+* **Expiration**: KYC legitimizations may be outdated. Expiration rules determine when *checks* have to be performed again.
+* **Legitimization rules**: The legitimization rules determine under which *conditions* which *checks* must be performend and the *expiration* time period for the *checks*.
- ..note::
+* **Logic**: Logic refers to a specific bit of code (realized as an exchange plugin) that enables the interaction with a specific *provider*. Logic typically requires *configuration* for access control (such as an authorization token) and possibly the endpoint of the specific *provider* implementing the respective API.
- Unrelated: We may want to consider directly deleting prewire records
- instead of setting them to ``finished`` in ``taler-exchange-transfer``.
+* **Provider**: A provider performs a specific set of *checks* at a certain *cost*. Interaction with a provider is performed by provider-specific *logic*.
+* **Type of operation**: The operation type determines which Taler-specific operation has triggered the KYC requirement. We support four types of operation: withdraw (by customer), deposit (by merchant), P2P receive (by wallet) and (high) wallet balance.
-Exchange database schema changes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+New Endpoints
+^^^^^^^^^^^^^
-Note that there is may be some slight complication in the migration as the
-h_wire in deposits is salted, while the h_payto in the new wire_targets is
-expected to be unsalted. So converting the existing information to create the
-wire_targets table will be tricky!
+We introduce a new ``wire_targets`` table into the exchange database. This
+table is referenced as the source or destination of payments (regular deposits
+and also P2P payments). A positive side-effect is that we reduce duplication
+in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
+reference this table.
+
+We introduce a new ``legitimization_processes`` table that tracks the status
+of a legitimization process at a provider, including the configuration section
+name, the user/account name at the provider, and some legitimization
+identifier for the process at the provider. In this table, we additionally
+store information related to the KYC status of the underlying payto://-URI, in
+particular when the KYC expires (0 if it was never done).
+
+Finally, we introduce a new ``legitimization_requirements`` table that
+contains a list of checks required for a particular wire target. When KYC is
+triggered (say when some endpoint returns an HTTP status code of 451) a
+new requirement is first put into the requirements table. Then, when the
+client identifies as business or individual the specific legitimization
+process is started. When the taler-exchange-aggregator triggers a KYC check
+the merchant can observe this when a 202 (Accepted) status code is returned
+on GET ``/deposits/`` with the respective legitimization requirement row.
+
+The new ``/kyc-check/`` endpoint is based on the legitimization requirements
+serial number and receives the business vs. individual status from the client.
+Access is ``authenticated`` by also passing the hash of the payto://-URI.
+(Weak authentication is acceptable, as the KYC status or the ability to
+initiate a KYC process are not very sensitive.) Given this triplet, the
+``/kyc-check/`` endpoint returns either the (positive) KYC status or redirects
+the client (202) to the next required stage of the KYC process. The
+redirection must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET. As this endpoint is involved in every KYC check at the beginning, this is also the place where we can
+integrate the payment process for the KYC fee.
+
+The specific KYC provider to be executed depends on the configuration (see
+below) which specifies a ``$PROVIDER_SECTION`` for each authentication procedure.
+For each (enabled) provider, the exchange has a logic plugin which
+(asynchronously) determines the redirect URL for a given wire target. See
+below for a description of the high-level process for different providers.
+
+Upon completion of the process at the KYC provider, the provider must trigger
+a GET request to a new ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` endpoint.
+This may be done either by redirecting the browser of the user to that
+endpoint. Once this endpoint is triggered, the exchange will pass the
+received arguments to the respective logic plugin. The logic plugin will then
+(asynchronously) update the KYC status of the user. The logic plugin should
+return a human-readable HTML page with the KYC result to the user.
+
+Alternatively, the KYC confirmation may be triggered by a ``/kyc-webhook``
+request. As KYC providers do not necessarily support passing detailed
+information in the URL arguments, the ``/kyc-webhook`` only needs to specify
+either the ``PROVIDER_SECTION`` *or* the ``LOGIC`` (the name of the plugin
+implementing the KYC API). The API-specific webhook logic must then figure
+out what exactly the webhook is about on its own. The ``/kyc-webhook/``
+endpoint works for GET or POST, again as details depend on the KYC provider.
+In contrast to ``kyc-proof``, the response does NOT go to the end-users'
+browser and should thus only indicate success or failure.
+
+
+Legitimization Hooks
+^^^^^^^^^^^^^^^^^^^^
-We can *either* not support a fully automatic migration, or do an "expensive"
-migration with C logic (so not just SQL statements).
+When withdrawing, the exchange checks if the KYC status is acceptable. If no
+KYC was done and if either the amount withdrawn over a particular timeframe
+exceeds the threshold or the reserve received received a P2P transfer, then a
+``451 Unavailable for Legal Reasons`` is returned which redirects the consumer
+to the new ``/kyc-check/`` handler.
+
+When depositing, the exchange aggregator (!) checks the KYC status and if
+negative, returns an additional information field via the
+``aggregation_transient`` table which is returned via GET ``/deposts/`` to the
+merchant. This way, the merchant learns the ``requirement_row`` needed to
+begin the KYC process (this is independent of the amount) at the new
+``/kyc-check/`` handler.
+
+When merging into a reserve, the KYC status is checked and again the
+merge fails with ``451 Unavailable for Legal Reasons`` to trigger the
+KYC process.
+
+To allow the wallet to do the KYC check if it is about to exceed a set balance
+threshold, we modify the ``/keys`` response to add an optional array
+``wallet_balance_limit_without_kyc`` of threshold amounts is returned.
+Whenever the wallet crosses one of these thresholds for the first time, it
+should trigger the KYC process. If this field is absent, there is no limit.
+If the field is provided, a correct wallet must create a long-term
+account-reserve key pair. This should be the same key that is also used to
+receive wallet-to-wallet payments. Then, *before* a wallet performs an
+operation that would cause it to exceed the balance threshold in terms of
+funds held from a particular exchange, it *should* first request the user to
+complete the KYC process.
+
+For that, the wallet should POST to the new ``/wallet-kyc`` endpoint,
+providing its long-term reserve-account public key and a signature requesting
+permission to exceed the account limit. Here, the ``balance`` specified should
+be the threshold (from the ``wallet_balance_limit_without_kyc`` array) that
+the wallet would cross, and *not* the *exact* balance of the wallet. The
+exchange will respond with a wire target UUID. The wallet can then use this
+UUID to being the KYC process at ``/kyc-check/``. The wallet must only proceed
+to obtain funds exceeding the threshold after the KYC process has
+concluded. While wallets could be "hacked" to bypass this measure (we cannot
+cryptographically enforce this), such modifications are a terms of service
+violation which may have legal consequences for the user.
+
+
+
+Configuration Options
+^^^^^^^^^^^^^^^^^^^^^
+
+The configuration specifies a set of providers, one per configuration section:
+
+[kyc-provider-$PROVIDER_ID]
+# How expensive is it to use this provider?
+# Used to pick the cheapest provider possible.
+COST = NUMBER
+# Which plugin is responsible for this provider?
+LOGIC = PLUGIN_NAME
+# Which type of user does this provider handle?
+# Either INDIVIDUAL or BUSINESS.
+USER_TYPE = INDIVIDUAL
+# Which checks does this provider provide?
+# List of strings, no specific semantics.
+PROVIDED_CHECKS = SMS GOVID PHOTO
+# Plus additional logic-specific options, e.g.:
+AUTHORIZATION_TOKEN = superdupersecret
+FORM_ID = business_legi_form
+# How long is the check considered valid?
+EXPIRATION = DURATION
+
+The configuration also specifies a set of legitimization
+requirements, one per configuration section:
+
+[kyc-legitimization-$RULE_NAME]
+# Operation that triggers this legitimization.
+# Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
+# or WALLET-BALANCE.
+OPERATION_TYPE = WITHDRAW
+# Required checks to be performed.
+# List of strings, must individually match the
+# strings in one or more provider's PROVIDED_CHECKS.
+REQUIRED_CHECKS = SMS GOVID
+# Threshold amount above which the legitimization is
+# triggered. The total must be exceeded in the given
+# timeframe. Can be 'forever'.
+THRESHOLD = AMOUNT
+# Timeframe over which the amount to be compared to
+# the THRESHOLD is calculated.
+# Ignored for WALLET-BALANCE.
+TIMEFRAME = DURATION
+
+
+
+Exchange Database Schema
+^^^^^^^^^^^^^^^^^^^^^^^^
.. sourcecode:: sql
- -- Everything in one big transaction
- BEGIN;
- -- Check patch versioning is in place.
- SELECT _v.register_patch('exchange-TBD', NULL, NULL);
- --
CREATE TABLE IF NOT EXISTS wire_targets
(wire_target_serial_id BIGSERIAL UNIQUE
,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64),
,payto_uri STRING NOT NULL
- ,kyc_ok BOOLEAN NOT NULL DEFAULT (false)
- ,oauth_username STRING NOT NULL
- ,PRIMARY KEY (h_wire)
- );
+ ,PRIMARY KEY (h_payto)
+ ) SHARD BY (h_payto);
COMMENT ON TABLE wire_targets
IS 'All recipients of money via the exchange';
COMMENT ON COLUMN wire_targets.payto_uri
IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
COMMENT ON COLUMN wire_targets.h_payto
IS 'Unsalted hash of payto_uri';
- COMMENT ON COLUMN wire_targets.kyc_ok
- IS 'true if the KYC check was passed successfully';
- COMMENT ON COLUMN wire_targets.oauth_username
- IS 'Name of the user that was used for OAuth 2.0-based legitimization';
- --
- -- NOTE: logic to fill wire_target missing, so this
- -- CANNOT work if the database contains any data!
- --
- ALTER TABLE wire_out
- ADD COLUMN wire_target_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
- DROP COLUMN wire_target;
- COMMENT ON COLUMN wire_out.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
- --
- ALTER TABLE reserves_in
- ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
- DROP COLUMN sender_account_details;
- COMMENT ON COLUMN wire_out.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
- --
- ALTER TABLE reserves_close
- ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
- DROP COLUMN receiver_account;
- COMMENT ON COLUMN reserves_close.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status. Note that closing does not depend on KYC.';
- --
- ALTER TABLE deposits
- ADD COLUMN wire_target_serial_id INT8 NOT NULL,
- ADD COLUMN salt BYTEA NOT NULL CHECK (LENGTH(salt)=64),
- DROP COLUMN h_wire,
- DROP COLUMN wire;
- COMMENT ON COLUMN deposits.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
- -- Complete transaction
- --
- -- FIXME: 512-bit SALT is likely not specified/checked
- -- anywhere in the code (salt==string), and we probably
- -- should move to a 128-bit salt anyway!
- --
- COMMIT;
-
-
-TODO: Check if we missed miss any tables to migrate!
+
+ CREATE TABLE IF NOT EXISTS legitimization_requirements
+ (legitimization_requirement_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)
+ ,required_checks VARCHAR NOT NULL
+ ,UNIQUE (h_payto, required_checks);
+ ) PARTITION BY HASH (h_payto);
+
+ CREATE TABLE IF NOT EXISTS legitimization_processes
+ (legitimization_serial_id BIGSERIAL UNIQUE
+ ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64)
+ ,expiration_time INT8 NOT NULL DEFAULT (0)
+ ,provider_section VARCHAR NOT NULL
+ ,provider_user_id VARCHAR DEFAULT NULL
+ ,provider_legitimization_id VARCHAR DEFAULT NULL
+ ) PARTITION BY HASH (h_payto);
+
+ COMMENT ON COLUMN legitimizations.legitimization_serial_id
+ IS 'unique ID for this legitimization process at the exchange';
+ COMMENT ON COLUMN legitimizations.h_payto
+ IS 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)';
+ COMMENT ON COLUMN legitimizations.expiration_time
+ IS 'in the future if the respective KYC check was passed successfully';
+ COMMENT ON COLUMN legitimizations.provider_section
+ IS 'Configuration file section with details about this provider';
+ COMMENT ON COLUMN legitimizations.provider_user_id
+ IS 'Identifier for the user at the provider that was used for the legitimization. NULL if provider is unaware.';
+ COMMENT ON COLUMN legitimizations.provider_legitimization_id
+ IS 'Identifier for the specific legitimization process at the provider. NULL if legitimization was not started.';
Merchant modifications
^^^^^^^^^^^^^^^^^^^^^^
+A new setting is required where the merchant backend
+can be configured for a business (default) or individual.
+
+.. note::
+
+ This still needs to be done!
+
We introduce new ``kyc_status``, ``kyc_timestamp`` and ``kyc_serial`` fields
into a new table with primary keys ``exchange_url`` and ``account``. This
status is updated whenever a deposit is created or tracked, or whenever the
@@ -206,33 +312,190 @@ long-poller return with positive news.
..note::
- Semi-related: The TMH_setup_wire_account() should be changed to use
+ Semi-related: The TMH_setup_wire_account() is changed to use
128-bit salt values (to keep ``deposits`` table small) and checks for salt
to be well-formed should be added "everywhere".
+An additional complication will arise once the exchange can trigger a KYC
+fee (402) on ``/kyc-check/``. In this case, the merchant SPA must show the QR
+code to the merchant to allow the merchant to pay the KYC fee with a wallet.
+
Bank requirements
^^^^^^^^^^^^^^^^^
-The exchange primarily requires an OAuth 2.0 login page where the user
-can either login (and share an access token that grants access to only
-the username) or register to initiate the KYC process.
+The exchange primarily requires a KYC provider to be operated by the
+bank that offers an endpoint for with an API implemented by one of
+the logic plugins (and the respective legitimization configuration).
-Alternatives
-============
+Logic plugins
+^^^^^^^^^^^^^
+
+The ``$PROVIDER_SECTION`` is based on the name of the configuration section,
+not on the name of the logic plugin (that we call ``$LOGIC``). Using the
+configuration section, the exchange then determines the logic plugin to use.
+
+This section describes the general API for all of the supported KYC providers,
+as well as some details of how this general API could be implemented by the logic for
+different APIs.
+
+
+General KYC Logic Plugin API
+----------------------------
+
+This section provides a sketch of the proposed API for the KYC logic plugins.
+
+* initiation of KYC check (``kyc-check``):
+
+ - inputs:
+ + provider_section (for additional configuration)
+ + individual or business user
+ + h_payto
+ - outputs:
+ + success/provider-failure
+ + redirect URL (or NULL)
+ + provider_user_id (or NULL)
+ + provider_legitimization_id (or NULL)
+
+* KYC status check (``kyc-proof``):
+
+ - inputs:
+ + provider_section (for additional configuration)
+ + h_payto
+ + provider_user_id (or NULL)
+ + provider_legitimization_id (or NULL)
+ - outputs:
+ + success/pending/user-aborted/user-failure/provider-failure status code
+ + HTML response for end-user
+
+* Webhook notification handler (``kyc-webhook``):
-We may not need the oauth_username, but it seems saner to store it to
-provide a link to the legitimization resource server.
+ - inputs:
+ + HTTP method (GET/POST)
+ + rest of URL (after provider_section)
+ + HTTP body (if applicable!)
+ - outputs:
+ + success/pending/user-aborted/user-failure/provider-failure status code
+ + h_payto (for DB status update)
+ + HTTP response to be returned to KYC provider
-We could also store the access token, but that seems slightly more
-dangerous and given the close business relationship is unnecessary.
+The plugins do not directly interact with the database, the caller sets the
+expiration on ``success`` and also updates ``provider_user_id`` and
+``provider_legitimization_id`` in the tables as required.
-We may want to store some additional "permission level" obtained from the
-resource server to say for which of the operations (see requirements section)
-the legitimization is sufficient.
+For the webhook, we need a way to lookup ``h_payto`` by other data, so the
+KYC logic plugin API should be provided a method lookup with:
+
+ - inputs:
+ + ``provider_section``
+ + ``provider_legitimization_id``
+ - outputs:
+ + ``h_payto``
+ + ``legitimization_process_row``
+
+
+OAuth 2.0 specifics
+-------------------
+
+In terms of configuration, the OAuth 2.0 logic requires the respective client
+credentials to be configured apriori to enable access to the legitimization
+service.
+
+For the ``/kyc-check/`` endpoint, the OAuth 2.0 logic may need to create and
+store a nonce to be used during ``/kyc-proof/``, depending on the OAuth
+variant used. This may require another exchange table. The OAuth 2.0 process
+must then be set up to end at the new ``/kyc-proof/$PROVIDER_ID/`` endpoint.
+
+This ``/kyc-proof/oauth2/`` endpoint must query the OAuth 2.0 server using the
+``code`` argument provided as a query parameter. Based on the result, it then
+updates the KYC table of the exchange with the legitimization status and
+returns a human-readable KYC status page.
+
+The ``/kyc-webhook/`` is not applicable.
+
+
+Persona specifics
+-----------------
+
+We would use the hosted flow. Endpoints return a ``request-id``, which we should
+log for diagnosis.
+
+For ``/kyc-check/``:
+
+* Post to ``/api/v1/accounts`` using ``reference-id`` set to our ``h_payto``.
+ Returns ``id`` (account_id).
+
+* Create ``/verify`` endpoint using ``template-id`` (from configuration),
+ and ``account_id`` (from previous step) and a ``reference-id`` (use
+ the ``legitimization_serial_id`` for the new process). Set
+ ``redirect-uri`` to ``/kyc-proof/$PROVIDER_ID/``. However, we cannot
+ rely on the user clicking this, so we must also configure a webhook.
+ The request returns a '``verification-id``. That we store under
+ the ``provider_legitimization_id`` in the database.
+
+For ``/kyc-proof/``:
+
+* Use the ``/api/v1/verifications`` endpoint to get the verification
+ status. Requires the ``verification-id`` from the previous step.
+ Results include: created/pending/completed/expired (aborted)/failed.
+
+For ``/kyc-webhook/``:
+
+* The webhook is authenticated using a shared secret, which should
+ be in the configuration. So all we should have to do is parse
+ the POSTed body to find the status and the ``verification-id`` to
+ lookup ``h_payto`` and return the result.
+
+
+KYC AID specifics
+-----------------
+
+For ``/kyc-check/``:
+
+* Post to ``/applicants`` with a type (person or company) to
+ obtain ``applicant_id``. Store that under ``provider_user_id``.
+ ISSUE: *we* need to get the company_name, business_activity_id
+ and registration_country before this somehow!
+
+* start with create form URL ``/forms/$FORM_ID/urls``
+ providing our ``h_payto`` as the ``external_applicant_id``,
+ using the ``applicant_id`` from above,
+ and the ``/kyc-proof/$PROVIDER_ID`` for the ``redirect_url``.
+
+* redirect customer to the ``form_url``,
+ store the ``verification_id`` under ``provider_legitimization_id``
+ in the database.
+
+For ``/kyc-proof/``:
+
+* Not needed, just return an error.
+
+For ``/kyc-webhook/``:
+
+* For security, we should probably simply trigger the GET on
+ ``/verifications/{verification_id}`` to not trust an unsigned POST
+ to tell us anything for sure. The result is then returned.
+
+
+
+Alternatives
+============
+
+We could also store the access token (returned by OAuth 2.0), but that seems
+slightly more dangerous and given the close business relationship is
+unnecessary. Furthermore, not all APIs offer this.
+
+We could extend the KYC logic API to return key attributes about the user
+(such as legal name, phone number, address, etc.) which we could then sign and
+return to the user. This would be useful in P2P payments to identify the
+origin of an invoice. However, we might want to be careful to not disclose
+the key attributes via the API by accident. This could likely be done by
+limiting access to the respective endpoint to messages with a signature by the
+reserve private key (which is the only case where we care to certify things
+anyway).
Drawbacks
diff --git a/design-documents/024-age-restriction.rst b/design-documents/024-age-restriction.rst
index c7433f00..5b6e091d 100644
--- a/design-documents/024-age-restriction.rst
+++ b/design-documents/024-age-restriction.rst
@@ -1,5 +1,5 @@
-DD 024: Anonymous Age Restriction Extension for GNU Taler
-#########################################################
+DD24: Anonymous Age Restriction Extension for GNU Taler
+#######################################################
Summary
=======
@@ -18,7 +18,7 @@ sensitive private information is disclosed.
We want to offer a better mechanism for age-restriction with GNU Taler that
* ensures anonymity and unlinkability of purchases
-* can be set to particular age groups by parents/wardens at withdrawal
+* can be set to particular age groups by parents/wardens at withdrawal
* is bound to particular coins/tokens
* can be verified by the merchant at purchase time
* persists even after refresh
@@ -29,8 +29,6 @@ optional feature that can be switched on by the exchange operator.
Requirements
============
-TODO
-
* legal requirements for merchants must allow for this kind of mechanism
@@ -55,22 +53,68 @@ protocol, that gives the minor/ward a 1/κ chance to raise the minimum age for
the new coin).
The proposed solution maintains the guarantees of GNU Taler with respect to
-anonymity and unlinkability. (TODO: refer to the paper, once published)
+anonymity and unlinkability. We have published a paper
+`Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
+with the details.
¹) Once the feature is enabled and the age groups are defined, the exchange has
to stick to that decision until the support for age restriction is disabled.
We might reconsider this design decision at some point.
-Building Blocks
-^^^^^^^^^^^^^^^
+Main ideas and building blocks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-TODO: Summarize the design based on the five functions ``Commit()``,
-``Attest()``, ``Verify()``, ``Derive()``, ``Compare()``.
+The main ideas are as follows:
+
+#. The exchange defines and publishes M+1 different *age groups* of increasing
+ order: :math:`0 < a_1 < \ldots < a_M` with :math:`a_i \in \mathbb{N}`. The
+ zeroth age group is :math:`\{0,\ldots,a_1-1\}`.
+
+#. An **unrestricted age commitment** is defined as a vector of length M of
+ pairs of Edx25519_ public and private keys on Curve25519. In other words: one
+ key pair for each age group after the zeroth: :math:`\bigl\langle (q_1,
+ p_1), \ldots, (q_M, p_M) \bigr\rangle`. Here, :math:`q_i` are the public keys
+ (mnemonic: **q-mitments**), :math:`p_i` are the private keys.
+
+#. A **restricted age commitment** *to age group m* is derived from an
+ unrestricted age commitment by removing all private keys for
+ indices larger than m: :math:`\bigl\langle (q_1, p_1), \ldots, (q_m, p_m),
+ \, (q_{m+1}, \perp), \ldots, (q_M, \perp )\bigr\rangle`. F.e. if *none* of
+ the private keys is provided, the age commitment would be restricted to the
+ zeroth age group.
+
+#. The act of restricting an unrestricted age commitment is performed by the
+ parent/ward.
+#. An *age commitment* (without prefix) is just the vector of public keys:
+ :math:`\vec{Q} := \langle q_1, \ldots, q_M \rangle`. Note that from
+ just the age commitment one can not deduce if it originated from an
+ unrestricted or restricted one (and what age).
+
+#. An *attestation of age group k* is essentially the signature to any message
+ with the private key for slot k, if the corresponding private key is
+ available in a restricted age commitment. (Unrestricted age commitments can
+ attest for any age group).
+
+#. An age commitment is *bound to a particular coin* by incorporating the
+ SHA256 hash value of the age commitment (i.e. the M public keys) into the
+ signature of the coin. So instead of signing :math:`\text{FDH}_N(C_p)` with
+ the RSA private key of a denomination with support for age restriction, we
+ sign :math:`\text{FDH}_N(C_p, h_Q)`. Here, :math:`C_p` is the EdDSA public
+ key of a coin and :math:`h_Q` is the hash of the age commitment :math:`\vec{Q}`.
+ **Note:** A coin with age restriction can only be validated when both, the
+ public key of the coin itself **and** the hash of the age commitment, are
+ present. This needs to be supported in each subsystem: Exchange, Wallet and
+ Merchant.
+
+
+TODO: Summarize the design based on the five functions ``Commit()``,
+``Attest()``, ``Verify()``, ``Derive()``, ``Compare()``, once the paper from
+Özgür and Christian is published.
-Changes in the Exchange
-^^^^^^^^^^^^^^^^^^^^^^^
+Changes in the Exchange API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
The necessary changes in the exchange involve
@@ -82,49 +126,84 @@ The necessary changes in the exchange involve
Extension for age restriction
-----------------------------
+.. note::
+
+ Registering an extension is defined in
+ :doc:`design document 006 ― Extensions <006-extensions>`.
+
+
The exchange indicates support for age-restriction in response to ``/keys`` by
-registering the extension ``age_restriction.v1`` with a value type
+registering the extension ``age_restriction`` with a value type
``ExtensionAgeRestriction``:
.. ts:def:: ExtensionAgeRestriction
interface ExtensionAgeRestriction {
- // The fields ``description``and ``required`` are mandatory for each
- // extension.
-
- // Description will be something like:
- // "Support for age restriction version 1 enabled. See
- // https://docs.taler.net/design-documents/024-age-restriction.html";
- description: string;
-
- // Age restriction is not required to be supported by a wallet or
- // merchant, so ``required`` will be set to ``false``.
- required: boolean;
-
- // Age restriction specific fields.
-
- // Representation of the age groups as comma separated edges: Increasing
- // from left to right, the values mark the begining of an age group up
- // to, but not including the next value. The initial age group starts at
- // 0 and is not listed. Example: "8:10:12:14:16:18:21".
- // This field is mandatory and binding in the sense that its value is
- // taken into consideration when signing the denominations in
- // ``ExchangeKeysResponse.age_restricted_denoms``.
- age_groups: string;
+ // The field ``critical`` is mandatory for an extension.
+ // Age restriction is not required to be understood by an client, so
+ // ``critical`` will be set to ``false``.
+ critical: false;
+
+ // The field ``version`` is mandatory for an extension. It is of type
+ // `LibtoolVersion`.
+ version: "1";
+
+ // Age restriction specific configuration
+ config: ConfigAgeRestriction;
}
-Registering an extension is defined in
-:doc:`design document 006 *Extensions* <006-extensions>`.
+.. ts:def:: ConfigAgeRestriction
+
+ interface ConfigAgeRestriction {
+ // The age groups. This field is mandatory and binding in the sense
+ // that its value is taken into consideration when signing the
+ // age restricted denominations in the `ExchangeKeysResponse`
+ age_groups: AgeGroups;
+ }
+
+Age Groups
+~~~~~~~~~~
+
+Age groups are represented as a finite list of positive, increasing integers
+that mark the beginning of the *next* age group. The value 0 is omitted but
+implicitly marks the beginning of the *zeroth* age group and the first number
+in the list marks the beginning of the *first* age group. Age groups are
+encoded as a colon separated string of integer values. They are referred to by
+their *slot*, i.e. "age group 3" is the age group that starts with the 3.
+integer in the list.
+
+For example: the string "8:10:12:14:16:18:21" represents the age groups
+
+0. {0,1,2,3,4,5,6,7}
+#. {8,9}
+#. {10,11}
+#. {12,13}
+#. {14,15}
+#. {16,17}
+#. {18,19,20}
+#. {21, ⋯ }
+
+The field ``age_groups`` of type `AgeGroups` is mandatory and binding in the
+sense that its value is taken into consideration when signing the denominations
+in ``ExchangeKeysResponse.age_restricted_denoms``.
+
+.. ts:def:: AgeGroups
+
+ // Representation of the age groups as colon separated edges: Increasing
+ // from left to right, the values mark the beginning of an age group up
+ // to, but not including the next value. The initial age group starts at
+ // 0 and is not listed. Example: "8:10:12:14:16:18:21".
+ type AgeGroups = string;
+
Age restricted denominations
-----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If age-restriction is registered as an extension under the name
-``age_restriction.v1``, as described above, the root-object
-``ExchangeKeysResponse`` in response to ``/keys`` MUST be extended by an
-additional field ``age_restricted_denoms``. This is an *additional* list of
-denominations that must be used for during modified ``refresh`` and ``deposit``
-operations (see below).
+If age-restriction is registered as extension ``age_restriction``, as described
+above, the root-object ``ExchangeKeysResponse`` in response to ``/keys`` MUST
+be extended by an additional field ``age_restricted_denoms``. This is an
+*additional* list of denominations that must be used during the modified
+``refresh`` and ``deposit`` operations (see below).
The data structure for those denominations is the same as for the regular ones
in ``ExchangeKeysResponse.denoms``. **However**, the following differences
@@ -138,6 +217,11 @@ apply for each denomination in the list:
2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``.
+And similar to ``.denoms``, if the query parameter ``last_issue_date`` was
+provided by the client, the exchange will only return the keys that have
+changed since the given timestamp.
+
+
.. ts:def:: ExchangeKeysResponse
interface ExchangeKeysResponse {
@@ -145,60 +229,493 @@ apply for each denomination in the list:
// List of denominations that support age-restriction with the age groups
// given in age_groups. This is only set **iff** the extension
- // ``age_restriction.v1`` is registered under ``entensions`` with type
+ // ``age_restriction`` is registered under ``entensions`` with type
// ``ExtensionAgeRestriction``.
//
// The data structure for each denomination is the same as for the
// denominations in ExchangeKeysResponse.denoms. **However**, the
// following differences apply for each denomination in the list:
//
- // 1. The value of TALER_DenominationKeyValidityPS.denom_hash
+ // 1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
// is taken over the public key of the denomination __and__ the
// string in ``ExtensionAgeRestriction.age_groups`` from the
// corresponding extension object.
//
- // 2. The value of TALER_DenominationKeyValidityPS.purpose is set to
- // TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY
- age_restricted_denoms: Denoms[];
+ // 2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
+ // ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``
+ //
+ // Similar as for ``.denoms``, if the query parameter ``last_issue_date``
+ // was provided by the client, the exchange will only return the keys that
+ // have changed since the given timestamp.
+ age_restricted_denoms: Denom[];
//...
}
+SQL schema
+-----------
+
+The exchange has to mark denominations with support for age restriction as such
+in the database. Also, during the melting phase of the refresh operation, the
+exchange will have to persist the SHA256 hash of the age commitment of the
+original coin.
+
+The schema for the exchange is changed as follows:
+
+.. sourcecode:: sql
+
+ -- Everything in one big transaction
+ BEGIN;
+ -- Check patch versioning is in place.
+ SELECT _v.register_patch('exchange-TBD', NULL, NULL);
+
+ -- Support for age restriction is marked per denomination.
+ ALTER TABLE denominations
+ ADD COLUMN age_restricted BOOLEAN NOT NULL DEFAULT (false);
+ COMMENT ON COLUMN denominations.age_restriced
+ IS 'true if this denomination can be used for age restriction';
+
+ -- During the melting phase of the refresh, the wallet has to present the
+ -- hash value of the age commitment (only for denominations with support
+ -- for age restriction).
+ ALTER TABLE refresh_commitments
+ ADD COLUMN age_commitment_h BYTEA CHECK (LENGTH(age_commitment_h)=64);
+ COMMENT ON COLUMN refresh_commitments.age_commitment_h
+ IS 'SHA256 hash of the age commitment of the old coin, iff the corresponding
+ denomimination has support for age restriction, NULL otherwise.';
+ COMMIT;
+
+Note the constraint on ``refresh_commitments.age_commitment_h``: It can be
+NULL, but only iff the corresponding denomination (indirectly referenced via
+table ``known_coins``) has ``.age_restricted`` set to true. This constraint
+can not be expressed reliably with SQL.
+
+
+Protocol changes
+----------------
+
+Withdraw
+~~~~~~~~
+
+The withdraw protocol is affected in the following situations:
+
+- A wire transfer to the exchange (to fill a reserve) was marked by the
+ originating bank as coming from a bank account of a minor, belonging to a of
+ a specific age group, or by other means.
+- A Peer-to-Peer transaction was performed between customers. The receiving
+ customer's KYC result tells the exchange that the customer belongs to a
+ specific age group.
+
+In these cases, the wallet will have to perform a zero-knowledge protocol with
+exchange as part of the the withdraw protocol, which we sketch here. Let
+
+- :math:`\kappa` be the same cut-and-choose parameter for the refresh-protocol.
+- :math:`\Omega \in E` be a published, nothing-up-my-sleeve, constant
+ group-element on the elliptic curve.
+- :math:`a \in \{1,\ldots,M\}` be the maximum age (group) for which the wallet
+ has to prove its commitment.
+
+The values :math:`\kappa`, :math:`\Omega` and :math:`a` are known to the
+Exchange and the Wallet. Then, Wallet and Exchange run the following protocol
+for the withdrawal of one coin:
+
+- *Wallet*
+ 1. creates planchets :math:`C_i` for :math:`i \in \{1,\ldots,\kappa\}` as candidates for *one* coin.
+ #. creates age-commitments :math:`\vec{Q}^i` for :math:`i \in \{1,\ldots,\kappa\}` as follows:
+
+ a) creates :math:`a`-many Edx25519-keypairs :math:`(p^i_j, q^i_j)`
+ randomly for :math:`j \in \{1,\ldots,a\}` (with public keys :math:`q^i_j`),
+ #) chooses randomly :math:`(M - a)`-many scalars :math:`s^i_j` for :math:`j \in \{a+1,\ldots,M\}`,
+ #) calculates :math:`\omega^i_j = s^i_j*\Omega` for :math:`j \in \{a+1,\ldots,M \}`,
+ #) sets :math:`\vec{Q}^i := (q^i_1,\ldots,q^i_a,\omega^i_{a+1},\ldots,\omega^i_M)`
+
+ #. calculates :math:`f_i := \text{FDH}(C_i, H(\vec{Q}^i))` for :math:`i \in \{ 1,\ldots,\kappa \}`.
+ #. chooses random blindings :math:`\beta_i(.)` for :math:`i \in \{1,\ldots,\kappa\}`. The blinding functions depend on the cipher (RSA, CS).
+ #. calculates :math:`F := \text{H}(\beta_1(f_1)||\ldots||\beta_\kappa(f_\kappa))`
+ #. sends :math:`F` to the Exchange
+
+- *Exchange*
+ 7. receives and stores :math:`F`
+ #. chooses randomly :math:`\gamma \in \{1,\ldots,\kappa\}` and
+ #. sends :math:`\gamma` to the Wallet.
+
+- *Wallet*
+ 10. receives :math:`\gamma`
+ #. sends to the Exchange the tuple :math:`\left(r_\gamma, \vec{\beta}, \vec{\vec{Q}}, \vec{\vec{S}}\right)` with
+
+ - :math:`r_\gamma := \beta_\gamma(f_\gamma)`
+ - :math:`\vec{\beta} := (\beta^1,\ldots,\beta^{\gamma-1},\bot,\beta^{\gamma+1},\ldots,\beta^\kappa)`
+ - :math:`\vec{\vec{Q}} := (\vec{Q}^1,\ldots,\vec{Q}^{\gamma-1},\bot,\vec{Q}^{\gamma+1},\ldots,\vec{Q}^\kappa)`
+ - :math:`\vec{\vec{S}} := (\vec{S}^1,\ldots,\vec{S}^{\gamma-1},\bot,\vec{S}^{\gamma+1},\ldots,\vec{S}^\kappa)`
+ with :math:`\vec{S}^i := (s^i_j)`
+
+- *Exchange*
+ 12. receives :math:`\left(r, (\beta^i), (\vec{Q}^i), (\vec{B}^i) \right)`
+ #. calculates :math:`g_i := \beta_i\left(\text{FDH}(\vec{Q}^i)\right)` for :math:`i \neq \gamma`
+ #. compares :math:`F \overset{?}{=} \text{H}(g_1||\ldots||g_{\gamma - 1}||r||g_{\gamma+1}||\ldots||g_\kappa)` and bails out on inequality
+ #. for each :math:`\vec{B}^i, i \neq \gamma`
+
+ i. calculates :math:`\tilde{\omega}^i_j := b^i_j * \Omega` for :math:`j \in \{a+1,\ldots,M\}`
+ #. compares each :math:`\tilde{\omega}^i_j` to :math:`q^i_j` from :math:`\vec{Q}^i = (q^i_1, \ldots, q^i_M)` and bails out on inequality
+ #. signs :math:`r`
+ #. sends (blinded) signature :math:`\sigma_r` to Wallet
+
+- *Wallet*
+ 18. receives :math:`\sigma_r`
+ #. calculates (unblinded) signature :math:`\sigma_\gamma := \beta^{-1}_\gamma(\sigma_r)` for coin :math:`C_\gamma`.
+
+
+Note that the batch version of withdraw allows the withdrawal of *multiple*
+coins at once. For that scenario the protocol sketched above is adapted to
+accomodate for handling multiple coins at once -- thus multiplying the amount
+of data by the amount of coins in question--, but all with the same value of
+:math:`\gamma`.
+
+The *actual* implementation of the protocol above will have a major optimization
+to keep the bandwidth usage to a minimum. Instead of generating and sending
+the age commitment (array of public keys) and blindings for each coin, the
+wallet *MUST* derive the corresponding blindings and the age commitments from
+the coin's private key itself as follows:
+
+Let
+
+- :math:`c_s` be the private key of the coin,
+- :math:`m \in \{1,\ldots,M\}` be the maximum age (according to the reserve)
+ that a wallet can commit to during the withdrawal.
+- :math:`P` be a published constant Edx25519-public-key to which the private
+ key is not known to any client.
+
+
+Then calculate the blinding :math:`\beta` for the coin as
+
+.. math::
+ \beta &:= \text{HKDF}(c_s, \text{"blinding"})
+
+If the denomination is using Clause-Schnorr signatures, calculate the nonce
+:math:`n` for the coin as
+
+.. math::
+ n &:= \text{HKDF}(c_s, \text{"cs-nonce"})
+
+
+
+For the age commitment, calculate:
+
+1. For age group :math:`a \in \{1,\ldots,m\}`, set
+
+.. math::
+ s_a &:= \text{HDKF}(c_s, \text{"age-commitment"}, a) \\
+ p_a &:= \text{Edx25519\_generate\_private}(s_a) \\
+ q_a &:= \text{Edx25519\_public\_from\_private}(p_a)
+
+2. For age group :math:`a \in \{m,\ldots,M\}`, set
+
+.. math::
+ f_a &:= \text{HDKF}(c_s, \text{"age-factor"}, a) \\
+ q_a &:= \text{Edx25519\_derive\_public}(P, f_a).
+
+Then the vector :math:`\vec{q} = \{q_1,\ldots,q_M\}` is then the age commitment
+associated to the coin's private key :math:`c_s`. For the non-disclosed coins,
+the wallet can use the vector :math:`(p_1,\ldots,p_m,\bot,\ldots,\bot)` of
+private keys for the attestation.
+
+Provided with the private key :math:`c_s`, the exchange can therefore calculate
+the blinding :math:`\beta`, the nonce :math:`n` (if needed) and the age
+commitment :math:`\vec{q}` itself, along with the coin's public key :math:`C_p`
+and use the value of
+
+.. math::
+
+ \text{TALER\_CoinPubHashP}(C_p, \text{age\_commitment\_hash}(\vec{q}))
+
+during the verification of the original age-withdraw-commitment.
+
+
+For the withdrawal with age restriction, a sketch of the corresponding database
+schema in the exchange is given here:
+
+.. graphviz::
+
+ digraph deposit_policies {
+ rankdir = LR;
+ splines = true;
+ fontname="monospace"
+ node [
+ fontname="monospace"
+ shape=record
+ ]
+
+ subgraph cluster_commitments {
+ label=<<B>withdraw_age_commitments</B>>
+ margin=20
+ commitments [
+ label="<id>withdraw_age_commitments_id\l|h_commitment\l|amount_with_fee_val\l|amount_with_fee_frac\l|noreveal_index\l|max_age_group\l|<res>reserve_pub\l|reserve_sig\l|timestamp\l"
+ ]
+ }
+
+ subgraph cluster_reveals {
+ label=<<B>withdraw_age_reveals</B>>
+ margin=20
+ reveals [
+ label="freshcoin_index\l|<comm>withdraw_age_commitments_id\l|<denom>denominations_serial\l|h_coin_ev\l"
+ ]
+ }
+
+ commitments:res->reserves:id [ label="n:1"; fontname="monospace"];
+ reveals:comm -> commitments:id [ label="n:1"; fontname="monospace" ];
+ reveals:denom -> denominations:id [ label="n:1"; fontname="monospace"] ;
+
+ }
+
+
+Refresh - melting phase
+~~~~~~~~~~~~~~~~~~~~~~~
+
+During the melting phase of the refresh, the wallet has to present the hash
+value of the age commitment (for denominations with support for age
+restriction). Therefore, in the ``/coins/$COIN_PUB/melt`` POST request, the
+``MeltRequest`` object is extended with an optional field
+``age_commitment_hash``:
+
+.. ts:def:: MeltRequest
+
+ interface MeltRequest {
+ ...
+
+ // SHA256 hash of the age commitment of the coin, IFF the denomination
+ // has age restriction support. MUST be omitted otherwise.
+ age_commitment_hash?: AgeCommitmentHash;
+
+ ...
+ }
+
+.. ts:def:: AgeCommitmentHash
+
+ type AgeCommitmentHash = SHA256HashCode;
+
+The responses to the POST request remain the same.
+
+For normal denominations *without* support for age restriction, the calculation
+for the signature check is as before (borrowing notation from
+`Florian's thesis <https://taler.net/papers/thesis-dold-phd-2019.pdf>`_):
+
+.. math::
+ \text{FDH}_N(C_p)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}\,N
+
+Here, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is its
+signature and :math:`\langle e, N \rangle` is the RSA public key of the
+denomination.
+
+For denominations *with* support for age restriction, the exchange takes the
+hash value ``age_commitment_hash`` (abbreviated as :math:`h_a`) into account
+when verifying the coin's signature:
+
+.. math::
+ \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+
+
+
+Refresh - reveal phase
+~~~~~~~~~~~~~~~~~~~~~~
+
+During the reveal phase -- that is upon POST to ``/refreshes/$RCH/reveal`` --
+the client has to provide the original age commitment of the old coin (i.e. the
+vector of public keys), iff the corresponding denomination had support for age
+restriction. The size of the vector is defined by the Exchange implicetly as
+the amount of age groups defined in the field ``.age_groups`` of the
+``ExtensionAgeRestriction``.
+
+.. ts:def:: RevealRequest
+
+ interface RevealRequest {
+ ...
+
+ // Iff the corresponding denomination has support for age restriction,
+ // the client MUST provide the original age commitment, i.e. the vector
+ // of public keys.
+ // The size of the vector is defined by the Exchange implicetly as the
+ // amount of age groups defined in the field ``.age_groups`` of the
+ // ``ExtensionAgeRestriction``.
+ old_age_commitment?: Edx25519PublicKey[];
+
+
+ ...
+ }
+
+
+The exchange can now check if the provided public keys ``.old_age_commitment``
+have the same SHA256 hash value when hashed in sequence as the
+``age_commitment_hash`` of the original coin from the call to melt.
+
+The existing `cut&choose protocol during the reveal phase
+</core/api-exchange.html#post--refreshes-$RCH-reveal>`__ is extended to perform
+the following additional computation and checks:
-Refresh
--------
+Using the κ-1 transfer secrets :math:`\tau_i` from the reveal request, the
+exchange derives κ-1 age commitments from the ``old_age_commitment`` by calling
+``Edx25519_derive_public()`` on each `Edx25519PublicKey`, with :math:`\tau_i`
+as the seed, and then calculates the corresponding κ-1 hash values :math:`h_i`
+of those age commitments.
-TODO: Extension of the cut'n-choose-protocol.
+It then calculates the κ-1 blinded hashes
+:math:`m_i = r^{e_i}\text{FDH}_N(C^{(i)}_p, h_i)` (using the notation from Florian's
+thesis) of the disclosed coins and together with the :math:`m_\gamma` of the
+undisclosed coin, calculates the hash
+:math:`h'_m = H(m_1,\cdots,m_\gamma,\cdots,m_\kappa)` which is then used in the
+final verification step of the cut&choose protocol.
Deposit
--------
+~~~~~~~
-TODO: Add opaque hash value of the commitments to the protocol
+As always, the merchant has to provide the public key of a coin during a POST
+to ``/coins/$COIN_PUB/deposit``. However, for coins with age restriction, the
+signature check requires the hash of the age commitment. Therefore the request
+object ``DepositRequest`` is extended by an optional field
+``age_commitment_hash`` which MUST be set (with the SHA256 hash of the age
+commitment), iff the corresponding denomination had support for age restriction
+enabled. The merchant has received this value prior from the customer during
+purchase.
-Changes in the Merchant
-^^^^^^^^^^^^^^^^^^^^^^^
+.. ts:def:: DepositRequest
-TODO: Spending protocol
+ interface DepositRequest {
+ ...
+ // Iff the corresponding denomination had support for age restriction
+ // enabled, this field MUST contain the SHA256 value of the age commitment that
+ // was provided during the purchase.
+ age_commitment_hash?: AgeCommitmentHash;
-Changes in the Wallet
-^^^^^^^^^^^^^^^^^^^^^
+ ...
+ }
+
+Again, the exchange can now check the validity of the coin with age restriction
+by evaluating
+
+.. math::
+ \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+Also again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
+its signature, :math:`\langle e, N \rangle` is the RSA public key of the
+denomination and :math:`h_a` is the value from ``age_commitment_hash``.
+
+
+
+Changes in the Merchant API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+Claiming the order
+------------------
+
+If an order requires a minimum age, the merchant MUST express that required
+minimum age in response to order claim by the wallet, that is, a POST to
+``[/instances/$INSTANCE]/orders/$ORDER_ID/claim``.
+
+The object ``ContractTerms`` is extended by an optional field
+``minimum_age`` that can be any integer greater than 0. In reality
+this value will not be smaller than, say, 8, and not larger than, say, 21.
+
+.. ts:def:: ContractTerms
+
+ interface ContractTerms {
+ ...
+
+ // If the order requires a minimum age greater than 0, this field is set
+ // to the integer value of that age. In reality this value will not be
+ // smaller than, say, 8, and not larger than, say, 21.
+ minimum_age?: Integer;
+
+ ...
+ }
+
+By sending the contract term with the field ``minimum_age`` set to an
+non-zero integer value, the merchant implicetly signals that it understands the
+extension ``age_restriction`` for age restriction from the exchange.
+
+
+Making the payment
+------------------
+
+If the ``ContractTerms`` had a non-zero value in field
+``minimum_age``, the wallet has to provide evidence of that minimum
+age by
+
+#. *either* using coins which are of denominations that had *no* age support
+ enabled,
+
+#. *or* using coins which are of denominations that have support for age
+ restriction enabled
-TODO.
+ * and then ―for each such coin― it has the right private key of the
+ restricted age commitment to the age group into which the required minimum
+ age falls (i.e. a non-empty entry at the right index in vector of Edx25519
+ keys, see above).
-* choosing age-restriction during withdrawal coins from denominations with
- support for age restriction.
-* Define protocol to pass denominations to child/ward.
+ * and signs the required minimum age with each coin's private key
+ corresponding to the age group,
+ * and sends ―for each coin― the complete age commitment and the signature to
+ the merchant.
+
+The object ``CoinPaySig`` used within a ``PayRequest`` during a POST to
+``[/instances/$INSTANCE]/orders/$ORDER_ID/pay`` is extended as follows:
+
+.. ts:def:: CoinPaySig
+
+ export interface CoinPaySig {
+ ...
+
+ // If a minimum age was required by the order and the wallet had coins that
+ // are at least committed to the corresponding age group, this is the
+ // signature of the minimum age as a string, using the private key to the
+ // corresponding age group.
+ minimum_age_sig?: Edx25519Signature;
+
+ // If a minimum age was required by the order, this is age commitment bound
+ // to the coin, i.e. the complete vector of Edx25519_ public keys, one for each
+ // age group (as defined by the exchange).
+ age_commitment?: Edx25519PublicKey[];
+
+ }
+
+
+The merchant can now verify
+
+#. the validity of each (age restricted) coin by evaluating
+
+ .. math:: \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+ Again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
+ its signature, :math:`\langle e, N \rangle` is the RSA public key of the
+ denomination and :math:`h_a` is the SHA256 hash value of the vector in
+ ``age_commitment``.
+
+#. the minimum age requirement by checking the signature in ``minimum_age_sig``
+ against the public key ``age_commitment[k]`` of the corresponding age group,
+ say, ``k``. (The minimum age must fall into the age group at index ``k`` as
+ defined by the exchange).
+
+**Note**: This applies only to coins for denominations that have support for
+age restriction. Denominations *without* support for age restriction *always*
+satisfy any minimum age requirement.
+
+
+
+Changes in the Wallet
+^^^^^^^^^^^^^^^^^^^^^
+
+A wallet implementation SHOULD support denominations with age restriction. In
+that case it SHOULD allow to select an age group as upper bound during
+withdraw.
Alternatives
============
-TODO.
-
* ID-based systems
* credit/debit card based systems
@@ -206,12 +723,10 @@ TODO.
Drawbacks
=========
-TODO.
-
* age groups, once defined, are set permanently
-* age restricted coins are basically shared between ward and warden.
Also discuss:
+
* storage overhead
* computational overhead
* bandwidth overhead
@@ -220,19 +735,115 @@ Also discuss:
Discussion / Q&A
================
-We had some very engaged discussions on the GNU Taler mailing list
-`<mailto:taler@gnu.org>`:
+We had some very engaged discussions on the GNU Taler `mailing list <taler@gnu.org>`__:
+
+* `Money with capabilities <https://lists.gnu.org/archive/html/taler/2021-08/msg00005.html>`_
+
+* `On age-restriction (was: online games in China) <https://lists.gnu.org/archive/html/taler/2021-09/msg00006.html>`__
+
+* `Age-restriction is about coins, not currencies <https://lists.gnu.org/archive/html/taler/2021-09/msg00021.html>`__
+
+* The published paper: `Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
+
+
+.. _Edx25519:
+
+Edx25519
+========
+
+Edx25519 is a variant of EdDSA on curve25519 which allows for repeated
+derivation of private and public keys, independently. It is implemented in
+`GNUNET with commit ce38d1f6c9bd7857a1c3bc2094a0ee9752b86c32.
+<https://git.gnunet.org/gnunet.git/commit/?id=ce38d1f6c9bd7857a1c3bc2094a0ee9752b86c32>`__
+
+The private keys in Edx25519 initially correspond to the data after expansion
+and clamping in EdDSA. However, this correspondence is lost after deriving
+further keys from existing ones. The public keys and signature verification
+are compatible with EdDSA.
+
+The scheme is as follows:
+
+::
+
+ /* Private keys in Edx25519 are pairs (a, b) of 32 byte each.
+ * Initially they correspond to the result of the expansion
+ * and clamping in EdDSA.
+ */
+
+ Edx25519_generate_private(seed) {
+ /* EdDSA expand and clamp */
+ dh := SHA-512(seed)
+ a := dh[0..31]
+ b := dh[32..64]
+ a[0] &= 0b11111000
+ a[31] &= 0b01111111
+ a[31] |= 0b01000000
+
+ return (a, b)
+ }
+
+ Edx25519_public_from_private(private) {
+ /* Public keys are the same as in EdDSA */
+ (a, _) := private
+ return [a] * G
+ }
+
+ Edx25519_blinding_factor(P, seed) {
+ /* This is a helper function used in the derivation of
+ * private/public keys from existing ones. */
+ h1 := HKDF_32(P, seed)
+
+ /* Ensure that h == h % L */
+ h := h1 % L
+
+ /* Optionally: Make sure that we don't create weak keys. */
+ P' := [h] * P
+ if !( (h!=1) && (h!=0) && (P'!=E) ) {
+ return Edx25519_blinding_factor(P, seed+1)
+ }
+
+ return h
+ }
+
+ Edx25519_derive_private(private, seed) {
+ /* This is based on the definition in
+ * GNUNET_CRYPTO_eddsa_private_key_derive. But it accepts
+ * and returns a private pair (a, b) and allows for iteration.
+ */
+ (a, b) := private
+ P := Edx25519_public_key_from_private(private)
+ h := Edx25519_blinding_factor(P, seed)
-* Money with capabilities
- `<https://lists.gnu.org/archive/html/taler/2021-08/msg00005.html>`_
-
+ /* Carefully calculate the new value for a */
+ a1 := a / 8;
+ a2 := (h * a1) % L
+ a' := (a2 * 8) % L
-* On age-restriction (was: online games in China)
- `<https://lists.gnu.org/archive/html/taler/2021-09/msg00006.html>`_
+ /* Update b as well, binding it to h.
+ This is an additional step compared to GNS. */
+ b' := SHA256(b ∥ h)
-* Age-restriction is about coins, not currencies
- `<https://lists.gnu.org/archive/html/taler/2021-09/msg00021.html>`_
+ return (a', b')
+ }
+ Edx25519_derive_public(P, seed) {
+ h := Edx25519_blinding_factor(P, seed)
+ return [h]*P
+ }
-The upcoming paper on anonymous age-restriction for GNU Taler from Özgür Kesim
-and Christian Grothoff will be cited here, once it is published.
+ Edx25519_sign(private, message) {
+ /* As in Ed25519, except for the origin of b */
+ (d, b) := private
+ P := Edx25519_public_from_private(private)
+ r := SHA-512(b ∥ message)
+ R := [r] * G
+ s := r + SHA-512(R ∥ P ∥ message) * d % L
+
+ return (R,s)
+ }
+
+ Edx25519_verify(P, message, signature) {
+ /* Identical to Ed25519 */
+ (R, s) := signature
+ return [s] * G == R + [SHA-512(R ∥ P ∥ message)] * P
+ }
diff --git a/design-documents/025-withdraw-from-wallet.rst b/design-documents/025-withdraw-from-wallet.rst
new file mode 100644
index 00000000..a00d7a1b
--- /dev/null
+++ b/design-documents/025-withdraw-from-wallet.rst
@@ -0,0 +1,66 @@
+DD25: Withdraw coins manually starting from the wallet
+######################################################
+
+Summary
+=======
+
+This document presents the design discussion about the manual withdraw screens
+
+Motivation
+==========
+
+To have a way to initiate a withdrawal process and complete with a bank that is
+not aware of GNU Taler
+
+Proposed Solution
+=================
+
+Access to the feature
+^^^^^^^^^^^^^^^^^^^^^
+
+Adding a withdraw button in the main balance page to initiate the process.
+
+This feature can be use in the Pay call-to-action when there is not enough coins.
+
+Start
+^^^^^
+
+This screen the user will be able to select the currency from a list of known
+currencies, switch the exchange, go to a page to add an exchange and define an
+amount to be withdraw.
+
+.. image:: ../wallet-start-manual-withdraw.svg
+ :width: 800
+
+
+Success
+^^^^^^^
+
+Here the user will see the account details where it needs to send money to
+complete the withdrawal.
+
+.. image:: ../wallet-confirm-withdraw.svg
+ :width: 800
+
+Transaction history
+^^^^^^^^^^^^^^^^^^^
+
+The account information should be added into the Withdrawal Transaction detail
+screen if the withdrawal is still pending. This will also affect the use case
+when the user started the transaction from a taler-aware bank (in which case the
+user doesn't need to do an extra step to complete the process) so the text
+should be consistent in both scenarios.
+
+Alternatives
+============
+
+* removing the amount field, the exchange will send coins equal to the amount it received
+
+* showing the fees, can we calculate the withdrawal fee?
+
+* should we show the terms of service?
+
+* exchange field first has been discussed, but the exchange list its only showing the
+ current currency exchanges, the user need to switch the currency first. Adding a
+ new exchange should be done in a different context that can be accessed using the
+ ``add exchange`` link
diff --git a/design-documents/026-refund-fees.rst b/design-documents/026-refund-fees.rst
new file mode 100644
index 00000000..b6a31fe0
--- /dev/null
+++ b/design-documents/026-refund-fees.rst
@@ -0,0 +1,61 @@
+DD26: Refunds and Fees
+######################
+
+Summary
+=======
+
+This document discusses what should happen with deposit fees when a
+deposit is refunded.
+
+
+Motivation
+==========
+
+When a user receives a refund, we have to decide what happens to the deposit
+fee that was originally paid on the associated deposit. Originally, we said
+that the deposit fee is waived when any refund happens. However, if the
+refund fee is zero and the deposit fee is non-zero, this results in a
+problematic scenario where merchants issue minuscule refunds that primarily
+enable customers to effectively obtain the deposit fee.
+
+
+Requirements
+============
+
+ * If the refund and refresh fees are zero, it should be possible for
+ consumers to get 100% of their digital cash back on refunds.
+ * This should not result in a problematic situation where merchants
+ conspire with consumers and issue minuscule refunds to allow consumers
+ to work around deposit fees.
+
+Proposed Solution
+=================
+
+ * Only waive the deposit fee for full refunds where for the
+ specific coin (!) the refunded amount is the total value of the
+ refunded deposit.
+
+Alternatives
+============
+
+ * Only waive the deposit fee for full refunds where for the
+ specific coin the refunded amount is the total value of the
+ denomination of the coin. This may slightly simplify the
+ logic, but has the problem that it does not enable 100%
+ refunds if the original payment already required a refresh
+ because the coin's value exceeded the paid amount.
+ * Waive the deposit fee on any (including partial) refund.
+ This creates a bad incentive structure if combined refresh
+ and refund fees are below deposit fees.
+
+Drawbacks
+=========
+
+ * We need to update and test an already complex fee calculation
+ logic.
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/027-sandboxing-taler.rst b/design-documents/027-sandboxing-taler.rst
new file mode 100644
index 00000000..c671e55d
--- /dev/null
+++ b/design-documents/027-sandboxing-taler.rst
@@ -0,0 +1,165 @@
+DD27: Sandboxing all the Taler services
+#######################################
+
+.. note::
+
+ This design document is currently a draft, it
+ does not reflect any implementation decisions yet.
+
+Summary
+=======
+
+This document presents a method of deploying all the Taler
+services via one Docker container.
+
+Motivation
+==========
+
+It is very difficult to build GNU Taler from scratch. It is even more difficult
+to install, configure and launch it correctly.
+
+The purpose of the sandbox is to have a demonstration system that can be both
+build and launched with ideally a single command.
+
+Requirements
+============
+
+- No external services should be required, the only dependencies should be:
+
+ - podman/docker
+ - optionally: configuration files to further customize the setup
+
+- All services that are used should be installed from repositories
+ and not built from scratch (i.e. debian repos or PyPI)
+
+- There should be some "admin page" for the whole sandbox that:
+
+ - Show an overview of all deployed services, a link to their documentation
+ and the endpoints they expose
+ - Show very simple statistics (e.g. number of transactions / withdrawals)
+ - Allows generating and downloading the auditor report
+
+- Developers should be able to launch the sandbox on their own machine
+
+ - Possibly using nightly repos instead of the official stable repos
+
+- We should be able to deploy it on $NAME.sandbox.taler.net
+
+Design
+======
+
+The container is based on Debian Sid, and it installs all
+the services from their Debian packages. During the build
+process, it creates all the 'static' configuration. This
+one includes all the .conf-files, the database setup and the
+keying.
+
+Subsequently at the launch step, the system will create all
+the remaining RESTful resources. Such RESTful resources include
+the merchant instances and all the euFin accounts, both at Sandbox
+and at Nexus.
+
+The sandbox will serve one HTTP base URL and make any service
+reachable at $baseUrl/$service. For example, the exchange base
+URL will be "$baseUrl/exchange".
+
+The sandbox allows to configure:
+
+- which host it binds to, typically localhost+port.
+- which host is being reverse proxied to the sandbox. This
+ helps to generate valid URIs of services.
+
+All the other values will be hard-coded in the preparation.
+
+The database is aunched *in* the same container along the
+other services.
+
+Open questions
+==============
+
+- How to collect the static configuration values?
+
+ - => Via a configuration file that you pass to the container via
+ a mounted directory (=> ``-v $MYCONFIG:/sandboxconfig``)
+ - If we don't pass any config, the container should have
+ sane defaults
+ - This is effectively a "meta configuration", because it will
+ be used to generate the actual configuration files
+ and do RESTful configuration at launch time.
+
+- How to persist, at build time, the information
+ needed later at launch time to create the RESTful
+ resources?
+
+ - => The configuration should be done at launch-time of the container.
+
+- Should we at this iteration hard-code passwords too?
+ With generated passwords, (1) it won't be possible to
+ manually log-in to services, (2) it won't be possible
+ to write the exchange password for Nexus in the conf.
+ Clearly, that's a problem when the sandbox is served
+ to the outside.
+
+- How is data persisted? (i.e. where do we store stuff)
+
+ - By allowing to mount some data directory on the host of the container
+ (This stores the DB files, config files, key files, etc.)
+ - ... even for data like the postgresql database
+ - future/optional: we *might* allow connection to an external postgresql database as well
+
+- How are services supervised?
+
+ - SystemD? gnunet-arm? supervisord? something else?
+
+ - SystemD does not work well inside containers
+
+ - alternative: one container per service, use (docker/podman)-compose
+
+ - Either one docker file per service, *or* one base container that
+ can be launched as different services via command line arg
+
+ - Advantage: It's easy to see the whole architecture from the compose yaml file
+ - Advantage: It would be easy to later deploy this on kubernetes etc.
+
+ - list of containers:
+
+ - DB container (postgres)
+ - Exchange container (contains all exchange services, for now)
+ - Split this up further?
+ - Merchant container
+
+- Do we have multi-tenancy for the sandbox? (I.e. do we allow multiple
+ currencies/exchanges/merchants/auditors per sandbox)
+
+ - Might be simpler if we disallow this
+
+- How do we handle TLS
+
+ - Do we always do HTTPs in the sandbox container?
+ - We need to think about external and internal requests
+ to the sandbox
+
+- How do we handle (external vs internal) URLs
+
+ - If we use http://localhost:$PORT for everything, we can't expose
+ the services externally
+ - Example 1: Sandbox should run on sb1.sandbox.taler.net.
+
+ - What will be the base URL for the exchange in the merchant config?
+ - If it's https://sb1.sandbox.taler.net/exchange, we need some /etc/hosts entry
+ inside the container
+ - Once you want to expose the sandbox internally, you need a proper TLS cert (i.e. letsencrypt)
+ - Inside the container, you can get away with self-signed certificates
+ - Other solution: Just require the external nginx (e.g. at gv) to reverse proxy
+ sb1.sandbox.taler.net back to the container. This means that all communication
+ between services inside the sandbox container goes through gv
+
+ - Not great, but probably fine for first iteration
+ - Disadvantages: To test the container in the non-localhost mode, you need the external proxy running
+
+- Where do we take packages from?
+
+ - By default, from the stable taler-systems.com repos and PyPI
+ - Alternatively, via the nightly gv debian repo
+ - Since we install packages at container build time, this setting (stable vs nightly)
+ results in different container base images
diff --git a/design-documents/028-deposit-policies.rst b/design-documents/028-deposit-policies.rst
new file mode 100644
index 00000000..d0a1b107
--- /dev/null
+++ b/design-documents/028-deposit-policies.rst
@@ -0,0 +1,188 @@
+DD28: Deposit Policy Extensions
+###############################
+
+.. note::
+
+ This is Work-In-Progress.
+
+Summary
+*******
+
+We will propose here a plugable mechanism in the exchange to support deposits
+with associated policy. An exchange can enable support for such policies via
+configuration.
+
+The inital set of policy extensions that an exchange might provide consists of
+
+Merchant refunds
+ Merchant can grant customers refundable payments. In this case, the
+ amount of the deposit is put into escrow by the exchange for a certain
+ period until which the customer can claim a refund.
+
+Escrowed payments
+ A trustor puts coins into escrow with the exchange. It can be claimed
+ by a beneficiary until a certain deadline, when the claim is signed by
+ both, the beneficiary's and the trustor's keys.
+
+Brandt-Vickrey auctions
+ A bidder puts coins into escrow with the exhange in order to
+ participate in an Brandt-Vickrey auction. The deposit confirmation is
+ proof to the seller for the escrow and contains a hash of the auction
+ meta-data and a deadline. After successfull execution of the auction,
+ the seller provides a valid transcript to the exchange from which the
+ exchange learns which bidder(s) won the auction for which prices. It
+ then transfers the amounts from the winners' coins to the seller. In
+ case of a timeout and for all losing bidders, the coins can be
+ refreshed.
+
+The policies shall be implemented as *extensions* to the exchange (see
+:doc:`006-extensions`).
+
+Motivation
+**********
+
+TODO
+
+Background and Requirements
+***************************
+
+TODO
+
+Proposed Solution
+*****************
+
+TODO, explain:
+
+- C-structs for policy extensions (esp. the handlers)
+- Naming conventions for policy extensions
+- Deadlines and -handling
+- API-endpoints (``/extensions/policy_...``)
+- Typical choreography of a deposit with policy and its fulfillment
+
+Database-schema
+===============
+
+TODO: Description
+
+.. graphviz::
+
+ digraph deposit_policies {
+ rankdir = LR;
+ splines = false;
+ fontname="monospace"
+ node [
+ fontname="monospace"
+ shape=record
+ ]
+
+ subgraph cluster_deposits {
+ label=<<B>deposits</B>>
+ margin=20
+ deposits [
+ label="...|<ref>policy_details_id (null)\l|...|timestamp\l|..."
+ ]
+ }
+
+ subgraph cluster_policy_details {
+ label=<<B>policy_details</B>>
+ margin=20
+ policy_details [
+ label="<id>id\l|<hash>policy_hash_code (unique)\l|deadline\l|commitment (amount)\l|accumulated_total (amount)\l|fee (amount)\l|transferable (amount)\l|fulfillment_state\l|<fid>fulfillment_id (null)\l"
+ ]
+ }
+
+ subgraph cluster_policy_fulfillments {
+ label=<<B>policy_fulfillments</B>>
+ margin=20
+ rank=min;
+ policy_fulfillments [
+ label="<id>id\l|proof\l|timestamp\l|<codes>policy_hash_codes (blob)\l"
+ ]
+ }
+
+ deposits:ref->policy_details:id [ label="n:1"; fontname="monospace" ];
+ policy_details:fid->policy_fulfillments:id [label="n:1"; fontname="monospace" ];
+ }
+
+
+The field ``policy_hash_codes`` in table ``policy_fulfillments`` is a binary
+blob that consists of the concatenation of the sorted
+``policy_details.policy_hash_code`` entries from all policies that are fulfilled by
+this proof.
+
+
+Policy Fulfillment States
+=========================
+
+The fulfillment of a policy can be in one of the following five states:
+
+Ready
+ The policy is funded and ready. The exchange is waiting for a proof of
+ fulfillment to arrive before the deadline.
+
+Insufficient
+ The policy lacks funding, that is ``accumulated_total`` <
+ ``commitment``, but has otherwise been accepted. Funding can be
+ continued by calling ``/deposit`` or ``/batch-deposit`` with more coins
+ and the same policy details.
+
+Success
+ The policy is provably fulfilled. The amounts for payout, fees and
+ refresh are transfered/can be claimed. Note that a policy fulfillment
+ handler can change the values for the amounts for payout, fees and
+ refresh.
+
+Timeout
+ The policy has timed out. The amounts for payout and refresh are
+ transfered/can be claimed.
+
+Failure
+ The policy is in an failure state. Payouts and refreshes are
+ blocked, timeouts are ignored.
+
+
+
+Invariants
+^^^^^^^^^^
+
+The following invariants need to be fulfilled and be checked by the auditor:
+
+- The fulfillment state of a policy is **Insufficient** IF AND ONLY IF the
+ amount in ``policy_details.commitment`` is equal or larger than the amount in
+ ``policy_details.accumulated_total``.
+
+- The sum of amounts in ``policy_details.fee`` and
+ ``policy_details.transferable`` MUST be equal or less than the amount in
+ ``policy_details.accumulated_total``.
+
+- The amount in ``policy_details.accumulated_total`` MUST be equal to the total
+ sum of contributions of the individual coins of the deposits that reference
+ this policy.
+
+- Each hash code encoded in ``policy_fulfillments.policy_hash_codes`` MUST
+ refer to an existing ``policy_details.hash_code`` AND its ``.fulfillment_id``
+ MUST point to the same ``policy_fulfillments.id``.
+
+- Conversely: If a ``policy_details.fulfillment_id`` points to an entry in
+ ``policy_fulfillment``, the ``policy_details.policy_hash_code`` MUST be
+ present in that entry's ``.policy_hash_codes``.
+
+
+
+Alternatives
+============
+
+TODO
+
+Drawbacks
+=========
+
+TODO
+
+
+Discussion / Q&A
+================
+
+TODO
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/029-mobile-ui.rst b/design-documents/029-mobile-ui.rst
new file mode 100644
index 00000000..ddb5b0ca
--- /dev/null
+++ b/design-documents/029-mobile-ui.rst
@@ -0,0 +1,41 @@
+DD29: Mobile P2P UI
+###################
+
+Summary
+=======
+
+Design the ui and interaction of p2p payments using mobile
+
+Motivation
+==========
+
+The wallet user should be able to easily send and receive money to and from other wallet.
+
+Requirements
+============
+
+ * It should work even if the user does not have other communication channel
+ * Use the swipe up (to send) and swipe down (to request) gesture as other apps do
+ * It should be able to request payment before any withdraw (currency still unknown)
+ * It should support multi-device
+ * The user may opt-in to be findable through a registry service
+
+Proposed Solution
+=================
+
+.. image:: ../wallet-mobile-overview.svg
+ :width: 800
+
+
+Alternatives
+============
+
+Drawbacks
+=========
+
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/030-offline-payments.rst b/design-documents/030-offline-payments.rst
new file mode 100644
index 00000000..f2b3027e
--- /dev/null
+++ b/design-documents/030-offline-payments.rst
@@ -0,0 +1,99 @@
+DD30: Offline payments
+######################
+
+Summary
+=======
+
+This design document discusses support for offline payments.
+
+Motivation
+==========
+
+Many proposed CBDCs claim to support offline payments.
+Taler is explicitly meant to be an online payment system. However, since there
+recently seems to be an increased interest in offline CBDC solutions,
+we have decided to still explore how Taler could support offline payments in the future.
+
+While we still recommend online-only payments, this work operates under the the
+following theme: "If Taler can support offline payments that are no worse than
+those of competing systems (that often offer less freedom and privacy to
+users), why should we claim we can't support them and fare worse in comparisons"?
+
+Requirements
+============
+
+TBD.
+
+Possible Solutions
+==================
+
+Approach 1: Trust-based offline payments
+----------------------------------------
+
+The merchant simply trusts payments without depositing them at the exchange, up
+to a certain threshold and when an emergency mode is activated.
+
+Advantages:
+
+- Offers the most user freedom
+- Very few protocol/implementation changes required
+
+Disadvantages:
+
+- Requires manual switching to "emergency mode" where merchant
+ just trusts payments without verification.
+- Linkability of transactions unavoidable, because refresh is not available offline
+- Unclear if/how business logic based on order state will be affected.
+ (Will there be a "paid-offline" / "paid-unconfirmed" state for orders?)
+
+Approach 2: Full HSM wallet
+---------------------------
+
+Implement the full wallet in a hardware security module. When paying, the HSM
+wallet attests itself to the merchant, who then (if configured appropriately)
+trusts that the coins are still valid without talking to the exchange first.
+
+Advantages:
+
+- Easy to lift coins out of the HSM into a software wallet.
+ The HSM would internally mark the coins as "online" and give
+ the coin secrets to the software wallet.
+- Few exchange/merchant protocol changes required.
+
+Disadvantages:
+
+- Complex implementation, might not be supported on commodity HSMs.
+- Requires medium-to-major merchant backend changes (to verify HSM sigs and
+ deposit coins once offline)
+- Difficult to support P2P payments
+- Depending on available coins, linkable transactions
+ might be unavoidable.
+- Since the HSM wallet supports the full protocol, regulators might
+ be encouraged to make the HSM a requirement for *all* payments.
+- The usual (justified!) HSM security and freedom concerns
+
+Approach 3: Light-weight HSM balance register
+---------------------------------------------
+
+The HSM maintains an offline balance, effectively as a single, HSM protected
+register. To obtain offline cash, the software wallet spends coins to get a
+signature that can be passed to the HSM to increment the offline balance.
+To spend, the software wallet requests an "offline deposit permission"
+that decrements the HSM balance register. The exchange accepts these offline
+deposit permissions in lieu of a normal coin deposit permission.
+
+Advantages:
+
+- Trivially supports P2P payments
+- Cleanly separates normal Taler functionality from offline functionality
+
+Disadvantages:
+
+- Requires non-trivial (but backwards compatible) protocol changes
+- The usual (justified!) HSM security and freedom concerns
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/031-invoicing.rst b/design-documents/031-invoicing.rst
new file mode 100644
index 00000000..419c299d
--- /dev/null
+++ b/design-documents/031-invoicing.rst
@@ -0,0 +1,202 @@
+DD31: Invoicing
+###############
+
+Summary
+=======
+
+This document proposes new endpoints to support invoicing,
+that incidentally also address the long-standing tipping
+reserve expiration problem.
+
+
+Motivation
+==========
+
+We want to support a limited number of PULL payment requests where a purse is
+created for a reserve without immediately requiring a purse fee. However, we
+must prevent users from excessively creating purses (and uploading contracts)
+as we do not want the exchange to be abused as a storage layer. Furthermore,
+it would be good if the user sending a PULL payment request was properly
+identified to the payer.
+
+This design addresses bugs #7269 and #7274.
+
+
+Requirements
+============
+
+ * Effectively limit the number of open purses created by each individual
+ (or require purse fees if limit is exceeded).
+
+ * Ensure user has done KYC before doing a merge.
+ (Assuming the exchange does KYC at all.)
+
+ * Use information from KYC process to help payer identify payee.
+
+ * Reasonable UX and overall design impact.
+
+ * Wallets may want to pay for the reserve with coins
+ (reserve fresh, not created via bank transfer), while
+ tipping merchants likely want to pay from the reserve
+ balance itself. So both styles of payment should be
+ supported.
+
+Unclear in the current proposal are:
+
+ * Here (and in other places!), the payment of the KYC
+ fee remains, eh, obscure. This should probably be
+ part of the KYC endpoints, and not for each
+ KYC-trigger.
+
+ * Proposed table structure does not properly capture
+ if user paid extra for more purses (I could open
+ for 3 years, then pay for 5x purses in year 1, but
+ should not automatically get 5x purses in years 2/3).
+
+
+Proposed Solution
+=================
+
+Allow users to tie their identity to a reserve "on demand" and when doing so
+charge the ``account_fee``, bump the number of open purses threshold in the
+``reserves`` table and stop auto-closing of the reserve. This will ensure that
+the users can withdraw the reserve balance into their wallet even after a
+longer time period. This helps if the invoice is paid after a significant
+delay, and also addresses the unwanted tipping reserve closure
+problem. Introduce a way to force an immediate closure of a reserve, allowing
+P2P reserve from invoices to be send to a bank account (this allows a wallet
+to be used for convenient invoicing and not strictly require the wallet to
+receive the funds) and also allowing the user to recover funds from a tipping
+reserve after tips are no longer issued.
+
+The solution needs three new tables for:
+
+ * account creation data:
+
+ - serial
+ - timestamp
+ - signature affirming desire to create account
+ - KYC requirement row
+
+ * account creation payment data:
+
+ - serial (for replication)
+ - coin signature (affirming payment)
+ - amount contributed
+ - account creation link (serial row ID)
+
+ * reserve closure request data:
+
+ - serial (for replication)
+ - timestamp
+ - reserve signature
+ - target account payto:// URI
+
+
+Specifically, the solution involves three new endpoints:
+
+Opening reserves
+----------------
+
+ * This new endpoint ``/reserve/$RID/open`` allows the user to
+ pay (for a year) to create a fixed number of purses and
+ to keep the reserve ``open`` (preventing auto-close); the
+ endpoint typically triggers a first (balance-independent)
+ KYC process (451) for a new KYC operation ``invoicing``
+ (unless KYC is off).
+
+ * Upon completion of the ``invoicing`` KYC, the wallet
+ must again try to ``/open``. If successful, the wallet
+ may be asked to pay the annual fee (402). However,
+ usually the wallet should be aware of the fee, and already
+ have included a suitable deposit in the POST to the endpoint.
+
+ * Once the annual fee is paid, the now open
+ reserve is set to a non-zero counter of allowed concurrently
+ open purses, and the expiration time of the reserve is bumped
+ to the end of the time period for which the fee was paid.
+
+Reserve Attestation
+-------------------
+
+ * This new endpoint ``/reserve/$RID/attest`` allows the user to
+ obtain exchange-signed KYC information about themselves.
+ This will basically be a list of (GANA standardized) attributes
+ and exchange signatures. The user can then choose which of
+ these attributes to include when invoicing. The available
+ set of attributes may differ depending on the KYC providers
+ configured and the attributes returned by the KYC logic.
+ We may choose to not use any fancy cryptography here, and
+ simply sign the different attributes individually. However,
+ we should always sign over the ``$RID`` to ensure that the
+ resulting signatures are meaningful.
+
+ * When receiving an invoice (PULL payment request), we may want to
+ mandate a certain minimal set of attributes that *should* always
+ be included, and if that is absent warn the receiver that the
+ sender of the invoice did not properly identify themselves.
+
+ * By making this a new endpoint, the client can re-request
+ the signatures on-demand. This is useful if we use the
+ EdDSA online signatures of the exchange, which likely expire
+ long before the user changes their attributes.
+
+
+Closing reserves
+----------------
+
+ * This new endpoint ``/reserve/$RID/close`` allows the user to
+ force-close a reserve that has not yet expired. This is useful
+ in case invoices have been paid into the reserve and the
+ user wants to get their money out. The ``close`` endpoint
+ must be provided with an appropriate payto://-URI as
+ reserves that were filled by P2P merge operations may not
+ already have an associated bank account (empty ``reserves_in``
+ table).
+
+
+Alternatives
+============
+
+We could require the target account to be already specified on ``open``.
+However, that prevents people from invoicing that have no account (or we'd
+have to allow ``payto://void/``, which then prevents people from closing later
+if they got a bank account at a later stage).
+
+We could allow an amount to be specified on ``close`` to partially wire the
+funds in a reserve. However, reserves are not supposed to be used as bank
+accounts (either to wallet *or* exceptionally to bank account, please!), and
+this would conflict with the current implementation of the
+**taler-exchange-closer**. So no amount is likely better for minimal
+regulatory and implementation trouble.
+
+Closing a reserve could also prevent the future use of the reserve for
+invoicing. Right now, the specification allows this to continue, effectively
+allowing users to repeatedly close an account to drain its funds to a bank
+account instead of into a wallet.
+
+We could mandate a fixed set of attributes. However, it is unclear whether all
+exchanges will always have KYC providers enabled that offer any particular set
+of attributes. It is conceivable that some exchanges may not run with any kind
+of KYC, or just with phone-number validation, while others may require
+government IDs but not phone numbers. So we can easily end up with completely
+disjunct sets of attributes across operators.
+
+We could not warn users if *insufficient* attributes were provided in an
+invoice. However, that seems dangerous, especially as fake invoices are a
+common attacker trick.
+
+We could use attribute-based credentials (ABC) for the attestations. Benefits
+and (complexity) drawbacks of such a change should be discussed with Martin.
+
+
+Drawbacks
+=========
+
+Quite a bit of work to implement.
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/032-brandt-vickrey-auctions.rst b/design-documents/032-brandt-vickrey-auctions.rst
new file mode 100644
index 00000000..52a46cc0
--- /dev/null
+++ b/design-documents/032-brandt-vickrey-auctions.rst
@@ -0,0 +1,312 @@
+DD32: Brandt-Vickrey Auctions
+#############################
+
+Summary
+=======
+
+This document describes the design for how (SMC) auctions could be done with
+funds held in escrow by a Taler exchange. It is taking major inspiration from
+Markus Teich's Master's thesis on "Implementing Privacy Preserving Auction
+Protocols".
+
+The support for these types of auctions will be in the form of an extension for
+a deposit policy.
+
+
+Motivation
+==========
+
+Escrow is a key capability of payment systems. With SMC auctions we can
+broaden the escrow functionality beyond the (simplistic) refunds we currently
+offer. This provides a first new use-case for the extension mechanism. By
+using SMC auctions (libbrandt), we also provide privacy for another business
+process.
+
+We expect the design to be useful for three primary use-cases:
+
+ * Auctions in the style of Ebay (consumer goods)
+
+ * Auctions for commodities and stock trading
+
+ * Auctions in currency exchange, including crypto-currencies
+
+We do not consider the use-case of high-value art auctions, as here the Taler
+payment system is likely unsuitable for the high transaction values, the
+privacy for the buyer would still be problematic from a money-laundering
+perspective, and the cost of a trusted auctioneer manually running the auction
+is small compared to the transaction value, so the benefits from automation
+are also minor.
+
+
+Requirements
+============
+
+The discovery of ongoing auctions and the exchange of meta-data about the
+auction (duration, price ranges, conditions of the sale, etc.) is considered
+out-of-scope for the Taler protocol. Taler is only used to escrow a payment
+once participants have decided to make a bid on an aution.
+
+ * Support libbrandt-style SMC multiparty computation to determine
+ auction winner. No party is trusted to learn the bids during
+ the bidding phase, including the exchange. This implies that
+ participants have to always escrow the largest possible amount
+ in the auction, even if their actual bid is much lower.
+
+ * Integrate nicely with existing exchange functionality, including
+ minimal changes to existing endpoints and introducing as few new
+ endpoints as reasonable.
+
+ * Verifiablity of the auction outcome by the exchange and the
+ auditor. There is not supposed to be any trusted third party.
+ Naturally, when selling real-world goods, external enforcement
+ of the transfer of the good may be required.
+
+ * Guaranteed payment. If the auction is successful (there were at
+ least two bidders), the seller must unconditionally receive the
+ payment.
+
+ * Participants pay fees (possibly even just for participation),
+ to ensure only truly interested parties with skin in the game
+ participate (protection against denial-of-service attacks).
+
+ * To ensure participants can perform the required computations
+ in each round, the number of bidders on an auction may need
+ to be limited.
+
+
+
+Proposed Solution
+=================
+
+We will for now consider five types of parties involved in
+the auction:
+
+ * A seller, who is offering an item and who sets some basic
+ rules for the auction (like the price range, duration of
+ rounds, delivery conditions, seller's bank account,
+ auction operator, etc.).
+
+ * A number of bidders, who make bids on the auction, with
+ the highest bidder winnig the auction and paying the
+ second highest price to the seller.
+
+ * An auction operator, who collects messages from the seller
+ and the bidders and ultimately announces the (then universally
+ verifiable) outcome. In the original paper of Brand, this
+ would be the ``blackboard``. The auction operator may
+ also facilitate the discovery of auctions, but this is out
+ of scope. The auction operator may charge fees for setting
+ up and running an auction. However, from the Taler perspective,
+ paying an auction operator to run an auction is the same
+ as paying any other merchant and thus out of scope for this
+ design document.
+
+ * An exchange that supports the ``policy_vickrey_auction`` extension and
+ holds the funds for bids in escrow for the duration of the auction. Upon
+ completion of the auction, the exchange pays the seller from the auction's
+ winner and refunds the other bidders.
+
+ * An auditor that verifies that the exchange made the payments
+ correctly.
+
+The high-level protocol for a bidder's interaction with the auction operator
+and the Taler exchange is already described in Teich's thesis in Figure 3.2:
+the bidder begins by registering (say with an ephemeral EdDSA key) at the
+auction operator. Further messages from the bidder during this auction must
+then always be signed by the respective private key.
+
+The auction operator checks if there is a free slot (to limit the number of
+bidders per auction) and if one is free, gives the bidder a (modest) timeout
+until when it must prove that it escrowed an appropriate amount at the
+exchange. If no slots are free, the auction operator may allow the
+prospective bidder to long-poll for slots to become available (say because
+another prospective bidder failed to provide a proof of escrow on time).
+
+The bidder then uses the existing ``/deposit`` endpoint at the exchange to
+escrow the maximum bid. Escrowing the maximum bid ensures that no information
+about the actual bid is leaked to the exchange, and that any bid that could be
+made by the bidder can always be executed. In the ``/deposit``, the contract
+hash is set to information that includes those private parts of the auction
+meta data that do not concern the exchange (such as information about the item
+being sold). The seller's account information is included as the receiver of
+the funds. Additionally, the ``/deposit`` handler accepts an extension object
+which specifies the (SMC auction) extension and relevant meta-data about the
+auction (in particular, the bidder's ephemeral EdDSA public key, until when
+the auction runs, and (possibly) key material about the auction operator).
+
+The resulting proofs of deposits (plural, as there may be multiple coins
+involved) are then returned to the bidder. Note that the deposit confirmation
+signatures cover both the hash of the contract terms and the extension object.
+The deposit confirmations are then forwarded by the bidder to the auction
+operator, possibly already together with first (sealed) information about the
+bid.
+
+The auction operator then runs the auction protocol with all participants
+until conclusion. Once the winner and price have been determined, the auction
+operator POSTs the resulting transcript to a new
+``/extensions/policy_brandt_vickrey_auction`` endpoint of the exchange. Here,
+the extension-specific logic stores the transcript in its database (in a new
+table) and then simulates the auction again (using libbrandt), again
+determining the winner and price. The extension configuration (and thus
+``/keys``) may stipendulate some fee(s) charged by the exchange to handle the
+``/extensions/policy_brandt_vickrey_auction`` request. The fees should be
+covered by the seller. We note that the transcript inherently contains the
+deposit confirmations originally issued by the exchange for the auction. So,
+the exchange can identify all of the coins that were escrowed (it should also
+double-check that the coins were escrowed for the correct auction). It then
+refunds the bids from the loosing bidders, pays the price to the seller from
+the winner (minus auction fee), and partially refunds the winner the difference
+between the escrowed amount and the winning bid.
+
+ .. note::
+
+ Partial refunds are currently implemented using the ``refunds`` table.
+ The refunds table requires refund message signatures by the merchant's
+ public key. Thus, this table will need to be generalized to include
+ some indicator as to whether the refund signature is valid or
+ whether some other mechanism justified the refund. The most trivial
+ way would probably be to allow NULL values for the signature. However,
+ likely a link to the extension transcript should then be stored in
+ another column to make it easier for the auditor to look for
+ "alternative" justifications in those cases.
+
+In case participants are identified as malicious, the auction meta data should
+specify the penalty those participants must pay to the seller. Again, the
+exchange should assess the auction transcript and then trigger the correct
+transactions.
+
+The auditor of the exchange can again simulate the auction protocol and can
+thus confirm that the exchange's ultimate transactions were correct.
+
+Transcripts
+^^^^^^^^^^^
+
+A transcript of a Brandt-Vickrey auction is the JSON encoding of an object of
+type `BrandtVickreyAuctionTranscript`.
+
+ .. ts:def:: BrandtVickreyAuctionTranscript
+
+ // This structure defines the transcript of an auction of Brandt-Vickrey kind.
+ interface BrandtVickreyAuctionTranscript {
+ // The auction definition.
+ auction: BrandtVickreyAuction;
+
+ // The public keys of the bidders, in Crockford Base32 encoding.
+ bidders: EddsaPublicKey[];
+
+ // Signatures of the auction in Crockford Base32 encoding.
+ // One signature per bidder.
+ signatures: EddsaSignature[];
+
+ // List of policy hash codes that identify policy details associated with
+ // each bidder. Those codes were generated by the policy extension
+ // policy_brandt_vickrey_auction during the deposit of coins for this
+ // auction.
+ policy_hash_codes: HashCode[];
+
+ // The transcript of all messages received by the seller.
+ transcript: BrandtVickreyAuctionMessage[];
+
+ // Optionally, the seller can provide the winners it had calculated.
+ winners?: BrandtVickreyAuctionWinner[];
+
+ // The signature over the hash of this JSON object, without the
+ // key ``sig`` and in normalized form, basically over
+ // H(auction, bidders, signatures, transcripts, winners?)
+ // It is signed by the private key that corresponds to the public key
+ // in `BrandtVickreyAuction`.``pubkey``.
+ // This signature is in Crockford Base32 encoding.
+ sig: EddsaSignature;
+ }
+
+
+ .. ts:def:: BrandtVickreyAuctionMessage
+
+ interface BrandtVickreyAuctionMessage {
+ // The index of the bidder into the
+ // `BrandtVickreyAuctionTranscript`.``bidders`` array.
+ bidder: number;
+
+ // The raw message in Crockford Base32 encoding.
+ msg: string;
+
+ // The signature over the message. The signature is in Crockford Base32
+ // encoding. It must be signed by the private key corresponding to the
+ // bidder's public key in `BrandtVickreyAuctionTranscript`.``bidders``.
+ sig: EddsaSignature;
+ }
+
+
+
+ .. ts:def:: BrandtVickreyAuctionWinner
+
+ interface BrandtVickreyAuctionWinner {
+ // The index of the bidder into the
+ // `BrandtVickreyAuctionTranscript`.bidder array.
+ bidder: number;
+
+ // The index of the winning price into the
+ // `BrandtVickreyAuction`.prices array.
+ price_idx: number;
+
+ // The winning price
+ price: Amount;
+ }
+
+
+Alternatives
+============
+
+If currency is sold for currency in an auction, the seller could also escrow
+the currency being sold. This could be done by a simple parallel extension
+for sellers that provides the seller's escrow proof as input into the auction
+protocol. The result would effectively be an auction-driven equivalent of the
+atomic swap protocols for crytocurrencies.
+
+Instead of the exchange and the auditor re-running the auction protocol
+internally against the transcript, it might suffice if the auction operator,
+seller and all bidders jointly attest to the outcome. However, this presumes
+that there are no malicious participants. Thus, this is an optimization that
+can help, but likely should not be relied upon. The exchange may stipendulate
+different fees if auction participants provide signatures demonstrating that
+they agree upon the outcome of the auction.
+
+
+Drawbacks
+=========
+
+Forcing participants to escrow the largest possible bid may exclude some
+bidders. However, it can be assumed that the seller (wanting to get as many
+high bids as possible) will set a reasonable bidding range to not exclude
+realistic bids. If the seller set the bidding range wrong and receives no bids
+as a result, the auction can of course simply be repeated. Finally, excluding
+bidders that can only make rather low bids may help keep the number of
+participants managable. Given the three application domains we focus on,
+it seems that the number of bidders regularly excluded from the auction due
+to this constraint should be acceptable.
+
+
+Discussion / Q&A
+================
+
+A possible challenge that may require more thought is how to deal with auction
+participants dropping out and not sending any more messages and the
+equivalent attack from the auction operator of suppressing messages from
+certain participants. The latter case can likely be addressed partially by
+network-level anonymization of all participants, as then the auction operator
+doesn't have the ability to target specific users. However, a conspirator
+could still deanonymize themselves to the auctioneer with the objective of the
+auction operator then suppressing messages from other (anonymous)
+participants and thereby possibly excluding higher bids from those users.
+
+ .. note::
+
+ As described above, the Master's thesis of Markus Teich proposes to
+ address the issue of bidders dropping out of the protocol by fining them,
+ for example by keeping (some of) the escrowed funds. This may work, but
+ only if we assume that the auction operator is not maliciously dropping
+ messages from some bidders.
+
+
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/033-database.rst b/design-documents/033-database.rst
new file mode 100644
index 00000000..ec91f6dc
--- /dev/null
+++ b/design-documents/033-database.rst
@@ -0,0 +1,152 @@
+Database Schema and Versioning
+##############################
+
+Summary
+=======
+
+This document describes how we version database schema in GNU Taler
+and enable migrations.
+
+
+Motivation
+==========
+
+As Taler evolves, it will be necessary to add new tables, change existing
+tables, modify indices and make other changes to the database schema. As
+production systems exist, existing data will need to be migrated to the new
+schema, and we need to do this in a systematic way. While certain major
+changes may require radical or manual interventions, we should have a
+systematic way of dealing with minor or modest modifications to the schema.
+
+
+Requirements
+============
+
+* The approach should be language-independent
+* Schema migration should be reasonably performant
+* Schema migration must be usable, and in particular safe to use for
+ operators without significant risk
+* We need to support key database features we might need to use,
+ such as partitioning or sharding
+
+
+Proposed Solution
+=================
+
+We use the "versioning.sql" system to store the current set of patches that
+have been applied to the database so far in a "_v" schema. This allows us to
+quickly test which version of the database we are on and which migrations may
+still need to be applied.
+
+For each component, all tables are placed into a SCHEMA named after the
+component.
+
+We then use a set of numbered SQL files that create, alter or drop tables and
+indices (like exchange-0001.sql, exchange-0002.sql, ...) to setup the
+database. However, some setups need additional arguments, such as the number
+of partitions. Those setups are then NOT performed explicitly, but by creating
+stored procedures and registering those stored procedures in a general global
+"master" table to be called from the main setup logic with arguments in a
+particular order under certain conditions.
+
+When setting up a database, there is no point in incrementally defining
+ordinary stored procedures that are used at runtime (not the ones to setup the
+tables we talked about above). Thus, all of the stored procedures used by the
+runtime system are placed in a file "procedures.sql" which is loaded
+last. This makes changes to stored procedures particularly easy, as one simply
+edits "procedures.sql". Loading "procedures.sql" also does not change "_v".
+
+A "drop.sql" file is created that DROPs the main SCHEMA of the component and
+additionally unregisters all patches from the "_v" schema. The drop script
+is run during tests to fully reset the database.
+
+Exchange details
+^^^^^^^^^^^^^^^^
+
+The exchange uses "exchange_tables" to create the master
+table mentioned above. In "exchange_tables", entries are
+executed in the order of the "table_serial_id". Each
+entry has a "name", which is the name of the affected table
+(or at least the prefix in the case of partitioned or sharded
+tables). The "version" field stores which "exchange-XXXX.sql"
+file setup the respective table entry, but is for now mostly
+for internal documentation. The "action" defines both the
+condition under which to run a function. Specifically,
+actions can be:
+
+* create --- run on the master table and each shard; used to create or alter the main table
+* constrain --- run only on the partitions/shards, or on master if there are no partitions; used to setup constraints like uniqueness that only apply to the lowest levels of the table
+* master -- run only on the master table; used to setup triggers and other constraints that only apply to the master table
+* foreign -- run only on the master table and only if there are no partition; used to setup foreign key constraints that are not supported on partitioned or sharded tables
+
+The "partitioned" field indicates that this table is partitioned and instructs the functions to create partitions (or shards)
+for this table.
+
+The "by_range" field indicates if the table is partitioned by
+range, which prevents automatic generation of partitions as
+is done if partitioned by hash.
+
+The "finished" field is initially false, but set to TRUE once the respective
+function has been executed.
+
+The main "do_create_tables" function triggers the unfinished actions
+registered in the "exchange_tables" table. It is given arguments to control
+the number of partitions, the use of partitions and (in the future) the use of
+sharding.
+
+The individual actions use helper functions ("create_partitioned_table",
+"comment_partitioned_table" and "comment_partitioned_column") to facilitate
+the creation of tables and associated comments. These functions are used so
+that we can only define the schema or comment once, and have it applied to
+tables with names and creation syntax that changes slightly if we use shards
+or partitions.
+
+Some additional logic will be needed to deal nicely with
+sharding (which is currently not supported), as with
+sharing we will need to detach shards, migrate shards, and
+re-attach shards. So this will require additional stored
+procedures to support operations on shards.
+
+
+Merchant details
+^^^^^^^^^^^^^^^^
+
+The merchant does not (yet) need any type of master table, as we do not
+(yet) use any kind of sharding or partitioning. There are also no
+stored procedures being used by the backend. Hence, it is simply the
+"versioning.sql"-controlled table creation/alteration sequence
+(merchant-0001.sql, etc.) and the "drop.sql" to reset everything.
+
+
+Alternatives
+============
+
+* We might want to consider storing more meta-data
+ in the database, such as the use of sharding, the
+ names of the shard servers, or even just the number
+ of partitions.
+
+* We could require dumping out the old database and
+ loading it in via some explicit importer during each
+ migration; having migration logic in C would enable more
+ powerful migrations, but dumping and reloading the entire
+ database would also be more expensive. It would have the
+ advantage of basically having the old database around in
+ case of migration trouble, so the cost disadvantage might
+ not be so bad (as admins are likely to make a backup anyway).
+ OTOH, doing the migration with the equivalent of a
+ taler-auditor-sync would require quite a bit more code
+ than the simple ALTER/CREATE statements in an SQL file.
+
+
+Drawbacks
+=========
+
+* not exactly trival logic
+* some complexity to implement
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/_svgs/escrow-flow.svg b/design-documents/_svgs/escrow-flow.svg
new file mode 100644
index 00000000..6fc0a51f
--- /dev/null
+++ b/design-documents/_svgs/escrow-flow.svg
@@ -0,0 +1 @@
+<svg aria-labelledby="chart-title-graph-div chart-desc-graph-div" role="img" viewBox="-111 -10 1273 1070" style="max-width: 1273px;" height="1070" xmlns="http://www.w3.org/2000/svg" width="100%" id="graph-div"><title id="chart-title-graph-div"></title><desc id="chart-desc-graph-div"></desc><style>#graph-div {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#graph-div .error-icon{fill:#552222;}#graph-div .error-text{fill:#552222;stroke:#552222;}#graph-div .edge-thickness-normal{stroke-width:2px;}#graph-div .edge-thickness-thick{stroke-width:3.5px;}#graph-div .edge-pattern-solid{stroke-dasharray:0;}#graph-div .edge-pattern-dashed{stroke-dasharray:3;}#graph-div .edge-pattern-dotted{stroke-dasharray:2;}#graph-div .marker{fill:#333333;333;}#graph-div .marker.cross{333;}#graph-div svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#graph-div .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#graph-div text.actor&gt;tspan{fill:black;stroke:none;}#graph-div .actor-line{stroke:grey;}#graph-div .messageLine0{stroke-width:1.5;stroke-dasharray:none;;}#graph-div .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;;}#graph-div #arrowhead path{fill:#333;;}#graph-div .sequenceNumber{fill:white;}#graph-div #sequencenumber{fill:#333;}#graph-div #crosshead path{fill:#333;;}#graph-div .messageText{fill:#333;;}#graph-div .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#graph-div .labelText,#graph-div .labelText&gt;tspan{fill:black;stroke:none;}#graph-div .loopText,#graph-div .loopText&gt;tspan{fill:black;stroke:none;}#graph-div .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#graph-div .note{stroke:#aaaa33;fill:#fff5ad;}#graph-div .noteText,#graph-div .noteText&gt;tspan{fill:black;stroke:none;}#graph-div .activation0{fill:#f4f4f4;stroke:#666;}#graph-div .activation1{fill:#f4f4f4;stroke:#666;}#graph-div .activation2{fill:#f4f4f4;stroke:#666;}#graph-div .actorPopupMenu{position:absolute;}#graph-div .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#graph-div .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#graph-div .actor-man circle,#graph-div line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#graph-div :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol height="24" width="24" id="computer"><path d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z" transform="scale(.5)"></path></symbol></defs><defs><symbol clip-rule="evenodd" fill-rule="evenodd" id="database"><path d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z" transform="scale(.5)"></path></symbol></defs><defs><symbol height="24" width="24" id="clock"><path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z" transform="scale(.5)"></path></symbol></defs><g><line stroke="#999" stroke-width="0.5px" class="200" y2="1004" x2="75" y1="5" x1="75" id="actor6934"></line><g id="root-6934"><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="0" x="0"></rect><text style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: &quot;Open Sans&quot;, sans-serif;" class="actor" alignment-baseline="central" dominant-baseline="central" y="32.5" x="75"><tspan dy="0" x="75">Seller</tspan></text></g></g><g><line stroke="#999" stroke-width="0.5px" class="200" y2="1004" x2="575" y1="5" x1="575" id="actor6935"></line><g id="root-6935"><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="0" x="500"></rect><text style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: &quot;Open Sans&quot;, sans-serif;" class="actor" alignment-baseline="central" dominant-baseline="central" y="32.5" x="575"><tspan dy="0" x="575">Exchange</tspan></text></g></g><g><line stroke="#999" stroke-width="0.5px" class="200" y2="1004" x2="949" y1="5" x1="949" id="actor6936"></line><g id="root-6936"><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="0" x="874"></rect><text style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: &quot;Open Sans&quot;, sans-serif;" class="actor" alignment-baseline="central" dominant-baseline="central" y="32.5" x="949"><tspan dy="0" x="949">Buyer</tspan></text></g></g><defs><marker orient="auto" markerHeight="12" markerWidth="12" markerUnits="userSpaceOnUse" refY="5" refX="9" id="arrowhead"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker refY="4" refX="16" orient="auto" markerHeight="8" markerWidth="15" id="crosshead"><path style="stroke-dasharray: 0, 0;" d="M 9,2 V 6 L16,4 Z" stroke-width="1px" stroke="#000000" fill="black"></path><path style="stroke-dasharray: 0, 0;" d="M 0,1 L 6,7 M 6,1 L 0,7" stroke-width="1px" stroke="#000000" fill="none"></path></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="18" id="filled-head"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker orient="auto" markerHeight="40" markerWidth="60" refY="15" refX="15" id="sequencenumber"><circle r="6" cy="15" cx="15"></circle></marker></defs><g><rect class="note" ry="0" rx="0" height="52" width="272" stroke="#666" fill="#EDF2AE" y="75" x="-61"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="80" x="75"><tspan x="75">The seller creates an escrow account </tspan></text><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="96" x="75"><tspan x="75"> with specific terms and a public key EID</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="142" x="325">register escrow (EID, [m, M], starttime, endtime)</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="175" x2="575" y1="175" x1="75"></line><g><rect class="activation0" ry="0" rx="0" height="48" width="10" stroke="#666" fill="#EDF2AE" y="175" x="570"></rect></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="190" x="323">(EID + sig_e(EID, [m, M], starttime, endtime))</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="223" x2="75" y1="223" x1="570"></line><g><rect class="note" ry="0" rx="0" height="52" width="188" stroke="#666" fill="#EDF2AE" y="233" x="-19"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="238" x="75"><tspan x="75">Seller publishes the EID, </tspan></text><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="254" x="75"><tspan x="75"> for example for an auction</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="300" x="512">publish auction (EID, [m, M], starttime, endtime, sig)</text><line style="stroke-dasharray: 3, 3; fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine1" y2="333" x2="949" y1="333" x1="75"></line><g><rect class="note" ry="0" rx="0" height="53" width="326" stroke="#666" fill="#EDF2AE" y="343" x="786"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="348" x="949"><tspan x="949">An interested bidder can receive the list of</tspan></text><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="364" x="949"><tspan x="949"> depositors (not amounts) for the escrow account</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="411" x="762">list(EID)</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="444" x2="575" y1="444" x1="949"></line><g><rect class="activation0" ry="0" rx="0" height="48" width="10" stroke="#666" fill="#EDF2AE" y="444" x="570"></rect></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="459" x="765">list of depositors</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="492" x2="949" y1="492" x1="580"></line><g><rect class="note" ry="0" rx="0" height="52" width="315" stroke="#666" fill="#EDF2AE" y="502" x="791.5"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="507" x="949"><tspan x="949">A interested bidder can deposit coins into the </tspan></text><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="523" x="949"><tspan x="949">escrow account and receives a proof of escrow</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="569" x="762">deposit(EID, amount, coins[], DEPOSIT_ID)</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="602" x2="575" y1="602" x1="949"></line><g><rect class="activation0" ry="0" rx="0" height="48" width="10" stroke="#666" fill="#EDF2AE" y="602" x="570"></rect></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="617" x="765">proof (EID, DEPOSIT_ID, [m,M])</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="650" x2="949" y1="650" x1="580"></line><g><rect class="note" ry="0" rx="0" height="52" width="233" stroke="#666" fill="#EDF2AE" y="660" x="832.5"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="665" x="949"><tspan x="949">The bidder can now use the proof</tspan></text><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="681" x="949"><tspan x="949"> to participate in the auction</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="727" x="512">bid (EID, proof(EID, [m,M]))</text><line style="stroke-dasharray: 3, 3; fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine1" y2="760" x2="75" y1="760" x1="949"></line><g><rect class="note" ry="0" rx="0" height="52" width="299" stroke="#666" fill="#EDF2AE" y="770" x="799.5"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="775" x="949"><tspan x="949">The winning bidder provides a release token </tspan></text><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="791" x="949"><tspan x="949"> for the winning amount to the seller</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="837" x="512">EID, DEPOSIT_ID, amount, coins[], sig_b(...)</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="870" x2="75" y1="870" x1="949"></line><g><rect class="note" ry="0" rx="0" height="36" width="208" stroke="#666" fill="#EDF2AE" y="880" x="-29"></rect><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;" dy="1em" class="noteText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="885" x="75"><tspan x="75">The seller can claim the coins</tspan></text></g><text style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;" dy="1em" class="messageText" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="931" x="325">claim(EID, DEPOSIT_ID, amount, coins[], sig_b, sig_s(sig_b))</text><line style="fill: none;" marker-end="url(#arrowhead)" stroke="none" stroke-width="2" class="messageLine0" y2="964" x2="575" y1="964" x1="75"></line><g><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="984" x="0"></rect><text style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: &quot;Open Sans&quot;, sans-serif;" class="actor" alignment-baseline="central" dominant-baseline="central" y="1016.5" x="75"><tspan dy="0" x="75">Seller</tspan></text></g><g><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="984" x="500"></rect><text style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: &quot;Open Sans&quot;, sans-serif;" class="actor" alignment-baseline="central" dominant-baseline="central" y="1016.5" x="575"><tspan dy="0" x="575">Exchange</tspan></text></g><g><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="984" x="874"></rect><text style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: &quot;Open Sans&quot;, sans-serif;" class="actor" alignment-baseline="central" dominant-baseline="central" y="1016.5" x="949"><tspan dy="0" x="949">Buyer</tspan></text></g></svg>
diff --git a/design-documents/index.rst b/design-documents/index.rst
index 7de0d2ea..4afb7280 100644
--- a/design-documents/index.rst
+++ b/design-documents/index.rst
@@ -33,4 +33,13 @@ and protocol.
022-wallet-auditor-reports
023-taler-kyc
024-age-restriction
+ 025-withdraw-from-wallet
+ 026-refund-fees
+ 027-sandboxing-taler.rst
+ 028-deposit-policies
+ 029-mobile-ui
+ 030-offline-payments
+ 031-invoicing
+ 032-brandt-vickrey-auctions
+ 033-database
999-template
diff --git a/extract-tsdefs/.gitignore b/extract-tsdefs/.gitignore
new file mode 100644
index 00000000..fa298802
--- /dev/null
+++ b/extract-tsdefs/.gitignore
@@ -0,0 +1,3 @@
+extract.js
+node_modules/
+dist
diff --git a/extract-tsdefs/README.md b/extract-tsdefs/README.md
new file mode 100644
index 00000000..e668be8d
--- /dev/null
+++ b/extract-tsdefs/README.md
@@ -0,0 +1,8 @@
+# Auto-generated wallet-core API docs
+
+Usage:
+```
+pnpm install
+pnpm run compile
+node dist/extract.js $WALLET_CORE_DIR $OUTFILENAME
+```
diff --git a/extract-tsdefs/extract.ts b/extract-tsdefs/extract.ts
new file mode 100644
index 00000000..5be82479
--- /dev/null
+++ b/extract-tsdefs/extract.ts
@@ -0,0 +1,341 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import * as ts from "typescript";
+import * as fs from "fs/promises";
+import * as path from "path";
+import * as prettier from "prettier";
+
+if (process.argv.length != 4) {
+ console.log(
+ `usage: ${process.argv[0]} ${process.argv[1]} WALLET_CORE_REPO OUTFILE`
+ );
+ process.exit(2);
+}
+
+const walletRootDir = process.argv[2];
+const outfile = process.argv[3];
+
+const walletCoreDir = path.join(walletRootDir, "packages/taler-wallet-core");
+const excludedNames = new Set([
+ "TalerErrorCode",
+ "WalletBackupContentV1",
+ "Array",
+]);
+
+const configFile = ts.findConfigFile(
+ walletCoreDir,
+ ts.sys.fileExists,
+ "tsconfig.json"
+);
+if (!configFile) throw Error("tsconfig.json not found");
+const { config } = ts.readConfigFile(configFile, ts.sys.readFile);
+
+const { options, fileNames, errors } = ts.parseJsonConfigFileContent(
+ config,
+ ts.sys,
+ walletCoreDir
+);
+
+const program = ts.createProgram({
+ options,
+ rootNames: fileNames,
+ configFileParsingDiagnostics: errors,
+});
+
+const checker = program.getTypeChecker();
+
+const walletApiTypesFiles = `${walletCoreDir}/src/wallet-api-types.ts`;
+console.log("api types file:", walletApiTypesFiles);
+
+const sourceFile = program.getSourceFile(walletApiTypesFiles);
+
+if (!sourceFile) {
+ throw Error();
+}
+
+const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
+
+const fileSymbol = program.getTypeChecker().getSymbolAtLocation(sourceFile);
+
+const expo = fileSymbol?.exports;
+if (!expo) {
+ throw Error();
+}
+
+interface PerOpGatherState {
+ opName: string;
+ nameSet: Set<string>;
+ group: string;
+ /**
+ * Enum member declaration in the form 'Foo = "bar"'.
+ */
+ enumMemberDecl: string | undefined;
+}
+
+interface GatherState {
+ declTexts: Map<string, string>;
+}
+
+function gatherDecls(
+ node: ts.Node,
+ gatherState: GatherState,
+ perOpState: PerOpGatherState
+): void {
+ switch (node.kind) {
+ case ts.SyntaxKind.EnumDeclaration:
+ // Always handled via parent
+ return;
+ case ts.SyntaxKind.Identifier:
+ case ts.SyntaxKind.TypeReference: {
+ console.log(`start typeref-or-id ${node.getText()}`);
+ const type = checker.getTypeAtLocation(node);
+ if (type.flags === ts.TypeFlags.String) {
+ console.log("string!");
+ break;
+ }
+ const symbol = type.symbol || type.aliasSymbol;
+ if (!symbol) {
+ console.log(`no type symbol for ${node.getText()}`);
+ break;
+ }
+ const name = symbol.name;
+ console.log(`symbol name: ${type.symbol?.name}`);
+ console.log(`alias symbol name: ${type.aliasSymbol?.name}`);
+ if (perOpState.nameSet.has(name)) {
+ console.log("already found!");
+ break;
+ }
+ perOpState.nameSet.add(name);
+ if (excludedNames.has(name)) {
+ console.log("excluded!");
+ break;
+ }
+ const decls = symbol.getDeclarations();
+ decls?.forEach((decl) => {
+ const sourceFilename = decl.getSourceFile().fileName;
+ if (path.basename(sourceFilename).startsWith("lib.")) {
+ return;
+ }
+ switch (decl.kind) {
+ case ts.SyntaxKind.EnumMember: {
+ gatherDecls(decl.parent, gatherState, perOpState);
+ console.log("enum member", decl.getText());
+ break;
+ }
+ case ts.SyntaxKind.InterfaceDeclaration:
+ case ts.SyntaxKind.EnumDeclaration:
+ case ts.SyntaxKind.TypeAliasDeclaration: {
+ const declText = printer.printNode(
+ ts.EmitHint.Unspecified,
+ decl,
+ decl.getSourceFile()!
+ );
+ gatherState.declTexts.set(name, declText);
+ console.log(declText);
+ break;
+ }
+ default:
+ console.log(`unknown decl kind ${ts.SyntaxKind[decl.kind]}`);
+ break;
+ }
+ gatherDecls(decl, gatherState, perOpState);
+ console.log(`end typeref-or-id ${node.getText()}`);
+ });
+ break;
+ }
+ default:
+ break;
+ }
+ console.log(`syntax children for ${node.getText()}`);
+ node.forEachChild((child) => {
+ console.log(`syntax child: ${ts.SyntaxKind[child.kind]}`);
+ gatherDecls(child, gatherState, perOpState);
+ });
+ //console.log(`// unknown node kind ${ts.SyntaxKind[node.kind]}`);
+ return;
+}
+
+function getOpEnumDecl(decl: ts.Declaration): string | undefined {
+ let enumMemberDecl: undefined | string = undefined;
+ function walk(node: ts.Node) {
+ node.forEachChild((x) => {
+ console.log(`child kind: ${ts.SyntaxKind[x.kind]}`);
+ console.log(x.getText());
+ switch (x.kind) {
+ case ts.SyntaxKind.PropertySignature: {
+ const sig = x as ts.PropertySignature;
+ if (sig.name.getText() == "op") {
+ const type = checker.getTypeFromTypeNode(sig.type!);
+ enumMemberDecl = type.symbol.declarations![0]!.getText();
+ }
+ break;
+ }
+ }
+ walk(x);
+ });
+ }
+ walk(decl);
+ return enumMemberDecl;
+}
+
+const main = async () => {
+ const f = await fs.open(outfile, "w");
+ const gatherState: GatherState = {
+ declTexts: new Map<string, string>(),
+ };
+ const perOpStates: PerOpGatherState[] = [];
+
+ let currentGroup: string = "Unknown Group";
+
+ expo.forEach((v, k) => {
+ if (!v.name.endsWith("Op")) {
+ return;
+ }
+ const decls = v.getDeclarations();
+ decls?.forEach((decl) => {
+ console.log(`export decl, kind ${ts.SyntaxKind[decl.kind]}`);
+
+ const commentRanges = ts.getLeadingCommentRanges(
+ sourceFile.getFullText(),
+ decl.getFullStart()
+ );
+ commentRanges?.forEach((r) => {
+ const text = sourceFile.getFullText().slice(r.pos, r.end);
+ console.log("comment text:", text);
+ const groupPrefix = "group:";
+ const loc = text.indexOf(groupPrefix);
+ if (loc >= 0) {
+ const groupName = text.slice(loc + groupPrefix.length);
+ console.log("got new group", groupName);
+ currentGroup = groupName;
+ }
+ });
+
+ const perOpState: PerOpGatherState = {
+ opName: v.name,
+ nameSet: new Set<string>(),
+ group: currentGroup,
+ enumMemberDecl: getOpEnumDecl(decl),
+ };
+ let declText = printer.printNode(
+ ts.EmitHint.Unspecified,
+ decl,
+ decl.getSourceFile()!
+ );
+ if (perOpState.enumMemberDecl) {
+ declText = declText + `\n// ${perOpState.enumMemberDecl}\n`;
+ }
+ console.log("replacing group in", declText);
+ // Remove group comments
+ declText = declText.replace(/\/\/ group: [^\n]*[\n]/m, "");
+ perOpState.nameSet.add(v.name);
+ gatherState.declTexts.set(v.name, declText);
+ gatherDecls(decl, gatherState, perOpState);
+ perOpStates.push(perOpState);
+ });
+ });
+
+ const allNames: Set<string> = new Set();
+
+ for (const g of perOpStates) {
+ for (const k of g.nameSet.values()) {
+ allNames.add(k);
+ }
+ }
+
+ const commonNames: Set<string> = new Set();
+
+ for (const name of allNames) {
+ let count = 0;
+ for (const g of perOpStates) {
+ for (const k of g.nameSet.values()) {
+ if (name === k) {
+ count++;
+ }
+ }
+ }
+ if (count > 1) {
+ console.log(`common name: ${name}`);
+ commonNames.add(name);
+ }
+ }
+
+ const groups = new Set<string>();
+ for (const g of perOpStates) {
+ groups.add(g.group);
+ }
+
+ await f.write(`# Wallet-Core API Documentation\n`);
+
+ await f.write(
+ `This file is auto-generated from [wallet-core](https://git.taler.net/wallet-core.git/tree/packages/taler-wallet-core/src/wallet-api-types.ts).\n`
+ );
+
+ await f.write(`## Overview\n`);
+ for (const g of groups.values()) {
+ await f.write(`### ${g}\n`);
+ for (const op of perOpStates) {
+ if (op.group !== g) {
+ continue;
+ }
+ await f.write(`* [${op.opName}](#${op.opName.toLowerCase()})\n`);
+ }
+ }
+
+ await f.write(`## Operation Reference\n`);
+ for (const g of perOpStates) {
+ // Not yet supported, switch to myst first!
+ // await f.write(`(${g.opName.toLowerCase()})=\n`);
+ await f.write(`### ${g.opName}\n`);
+ for (const name of g.nameSet.values()) {
+ if (commonNames.has(name)) {
+ continue;
+ }
+ const text = gatherState.declTexts.get(name);
+ if (!text) {
+ continue;
+ }
+ await f.write("```typescript\n");
+ const formatted = prettier.format(text, {
+ semi: true,
+ parser: "typescript",
+ });
+ await f.write(`${formatted}\n`);
+ await f.write("```\n");
+ }
+ await f.write("\n");
+ }
+
+ await f.write(`## Common Declarations\n`);
+ for (const name of commonNames.values()) {
+ const text = gatherState.declTexts.get(name);
+ if (!text) {
+ continue;
+ }
+ await f.write("```typescript\n");
+ const formatted = prettier.format(text, {
+ semi: true,
+ parser: "typescript",
+ });
+ await f.write(`${formatted}`);
+ await f.write("```\n");
+ }
+
+ await f.close();
+};
+
+main();
diff --git a/extract-tsdefs/myout.md b/extract-tsdefs/myout.md
new file mode 100644
index 00000000..abecccf0
--- /dev/null
+++ b/extract-tsdefs/myout.md
@@ -0,0 +1,2517 @@
+# Wallet API Documentation
+```{note}
+This file is auto-generated from [wallet-core](https://git.taler.net/wallet-core.git/tree/packages/taler-wallet-core/src/wallet-api-types.ts)
+```
+## Overview
+### Initialization
+* [InitWalletOp](#initwalletop)
+### Basic Wallet Information
+* [GetBalancesOp](#getbalancesop)
+### Managing Transactions
+* [GetTransactionsOp](#gettransactionsop)
+* [DeleteTransactionOp](#deletetransactionop)
+* [RetryTransactionOp](#retrytransactionop)
+### Withdrawals
+* [GetWithdrawalDetailsForAmountOp](#getwithdrawaldetailsforamountop)
+* [GetWithdrawalDetailsForUriOp](#getwithdrawaldetailsforuriop)
+* [AcceptBankIntegratedWithdrawalOp](#acceptbankintegratedwithdrawalop)
+* [AcceptManualWithdrawalOp](#acceptmanualwithdrawalop)
+### Merchant Payments
+* [PreparePayForUriOp](#preparepayforuriop)
+* [ConfirmPayOp](#confirmpayop)
+* [AbortPayWithRefundOp](#abortpaywithrefundop)
+* [ApplyRefundOp](#applyrefundop)
+### Tipping
+* [PrepareTipOp](#preparetipop)
+* [AcceptTipOp](#accepttipop)
+### Exchange Management
+* [ListExchangesOp](#listexchangesop)
+* [AddExchangeOp](#addexchangeop)
+* [SetExchangeTosAcceptedOp](#setexchangetosacceptedop)
+* [GetExchangeTosOp](#getexchangetosop)
+* [ListCurrenciesOp](#listcurrenciesop)
+### Deposits
+* [CreateDepositGroupOp](#createdepositgroupop)
+* [TrackDepositGroupOp](#trackdepositgroupop)
+### Backups
+* [ExportBackupRecoveryOp](#exportbackuprecoveryop)
+* [ImportBackupRecoveryOp](#importbackuprecoveryop)
+* [RunBackupCycleOp](#runbackupcycleop)
+* [AddBackupProviderOp](#addbackupproviderop)
+* [GetBackupInfoOp](#getbackupinfoop)
+* [SetWalletDeviceIdOp](#setwalletdeviceidop)
+* [ExportBackupPlainOp](#exportbackupplainop)
+### Peer Payments
+* [InitiatePeerPushPaymentOp](#initiatepeerpushpaymentop)
+* [CheckPeerPushPaymentOp](#checkpeerpushpaymentop)
+* [AcceptPeerPushPaymentOp](#acceptpeerpushpaymentop)
+* [InitiatePeerPullPaymentOp](#initiatepeerpullpaymentop)
+* [CheckPeerPullPaymentOp](#checkpeerpullpaymentop)
+* [AcceptPeerPullPaymentOp](#acceptpeerpullpaymentop)
+### Database Management
+* [ExportDbOp](#exportdbop)
+* [ClearDbOp](#cleardbop)
+* [RecycleOp](#recycleop)
+### Testing and Debugging
+* [RunIntegrationTestOp](#runintegrationtestop)
+* [WithdrawTestBalanceOp](#withdrawtestbalanceop)
+* [WithdrawTestkudosOp](#withdrawtestkudosop)
+* [TestPayOp](#testpayop)
+* [WithdrawFakebankOp](#withdrawfakebankop)
+* [GetPendingTasksOp](#getpendingtasksop)
+* [DumpCoinsOp](#dumpcoinsop)
+* [SetCoinSuspendedOp](#setcoinsuspendedop)
+* [ForceRefreshOp](#forcerefreshop)
+## Operation Reference
+(initwalletop)=
+### InitWalletOp
+```typescript
+// group: Initialization
+/**
+ * Initialize wallet-core.
+ *
+ * Must be the request before any other operations.
+ */
+export type InitWalletOp = {
+ op: WalletApiOperation.InitWallet;
+ request: {};
+ response: {};
+};
+
+```
+```typescript
+// Enum value:
+// WalletApiOperation.InitWallet = "initWallet"
+
+```
+
+(getbalancesop)=
+### GetBalancesOp
+```typescript
+// group: Basic Wallet Information
+/**
+ * Get current wallet balance.
+ */
+export type GetBalancesOp = {
+ request: {};
+ response: BalancesResponse;
+};
+
+```
+```typescript
+export interface BalancesResponse {
+ balances: Balance[];
+}
+
+```
+```typescript
+export interface Balance {
+ available: AmountString;
+ pendingIncoming: AmountString;
+ pendingOutgoing: AmountString;
+ hasPendingTransactions: boolean;
+ requiresUserInput: boolean;
+}
+
+```
+
+(gettransactionsop)=
+### GetTransactionsOp
+```typescript
+// group: Managing Transactions
+/**
+ * Get transactions.
+ */
+export type GetTransactionsOp = {
+ request: TransactionsRequest;
+ response: TransactionsResponse;
+};
+
+```
+```typescript
+export interface TransactionsRequest {
+ /**
+ * return only transactions in the given currency
+ */
+ currency?: string;
+ /**
+ * if present, results will be limited to transactions related to the given search string
+ */
+ search?: string;
+}
+
+```
+```typescript
+export interface TransactionsResponse {
+ transactions: Transaction[];
+}
+
+```
+```typescript
+export declare type Transaction =
+ | TransactionWithdrawal
+ | TransactionPayment
+ | TransactionRefund
+ | TransactionTip
+ | TransactionRefresh
+ | TransactionDeposit
+ | TransactionPeerPullCredit
+ | TransactionPeerPullDebit
+ | TransactionPeerPushCredit
+ | TransactionPeerPushDebit;
+
+```
+```typescript
+export interface TransactionWithdrawal extends TransactionCommon {
+ type: TransactionType.Withdrawal;
+ /**
+ * Exchange of the withdrawal.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+ withdrawalDetails: WithdrawalDetails;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.Withdrawal = "withdrawal"
+
+```
+```typescript
+export declare type WithdrawalDetails =
+ | WithdrawalDetailsForManualTransfer
+ | WithdrawalDetailsForTalerBankIntegrationApi;
+
+```
+```typescript
+interface WithdrawalDetailsForManualTransfer {
+ type: WithdrawalType.ManualTransfer;
+ /**
+ * Payto URIs that the exchange supports.
+ *
+ * Already contains the amount and message.
+ */
+ exchangePaytoUris: string[];
+ reservePub: string;
+}
+
+```
+```typescript
+// Enum value:
+// WithdrawalType.ManualTransfer = "manual-transfer"
+
+```
+```typescript
+interface WithdrawalDetailsForTalerBankIntegrationApi {
+ type: WithdrawalType.TalerBankIntegrationApi;
+ /**
+ * Set to true if the bank has confirmed the withdrawal, false if not.
+ * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI.
+ * See also bankConfirmationUrl below.
+ */
+ confirmed: boolean;
+ /**
+ * If the withdrawal is unconfirmed, this can include a URL for user
+ * initiated confirmation.
+ */
+ bankConfirmationUrl?: string;
+ reservePub: string;
+}
+
+```
+```typescript
+// Enum value:
+// WithdrawalType.TalerBankIntegrationApi = "taler-bank-integration-api"
+
+```
+```typescript
+export interface TransactionPayment extends TransactionCommon {
+ type: TransactionType.Payment;
+ /**
+ * Additional information about the payment.
+ */
+ info: OrderShortInfo;
+ /**
+ * Wallet-internal end-to-end identifier for the payment.
+ */
+ proposalId: string;
+ /**
+ * How far did the wallet get with processing the payment?
+ */
+ status: PaymentStatus;
+ /**
+ * Amount that must be paid for the contract
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that was paid, including deposit, wire and refresh fees.
+ */
+ amountEffective: AmountString;
+ /**
+ * Amount that has been refunded by the merchant
+ */
+ totalRefundRaw: AmountString;
+ /**
+ * Amount will be added to the wallet's balance after fees and refreshing
+ */
+ totalRefundEffective: AmountString;
+ /**
+ * Amount pending to be picked up
+ */
+ refundPending: AmountString | undefined;
+ /**
+ * Reference to applied refunds
+ */
+ refunds: RefundInfoShort[];
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.Payment = "payment"
+
+```
+```typescript
+export declare enum PaymentStatus {
+ /**
+ * Explicitly aborted after timeout / failure
+ */
+ Aborted = "aborted",
+ /**
+ * Payment failed, wallet will auto-retry.
+ * User should be given the option to retry now / abort.
+ */
+ Failed = "failed",
+ /**
+ * Paid successfully
+ */
+ Paid = "paid",
+ /**
+ * User accepted, payment is processing.
+ */
+ Accepted = "accepted",
+}
+
+```
+```typescript
+export interface RefundInfoShort {
+ transactionId: string;
+ timestamp: TalerProtocolTimestamp;
+ amountEffective: AmountString;
+ amountRaw: AmountString;
+}
+
+```
+```typescript
+export interface TransactionRefund extends TransactionCommon {
+ type: TransactionType.Refund;
+ refundedTransactionId: string;
+ info: OrderShortInfo;
+ /**
+ * Amount pending to be picked up
+ */
+ refundPending: AmountString | undefined;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.Refund = "refund"
+
+```
+```typescript
+export interface TransactionTip extends TransactionCommon {
+ type: TransactionType.Tip;
+ amountRaw: AmountString;
+ /**
+ * More information about the merchant
+ */
+ amountEffective: AmountString;
+ merchantBaseUrl: string;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.Tip = "tip"
+
+```
+```typescript
+export interface TransactionRefresh extends TransactionCommon {
+ type: TransactionType.Refresh;
+ exchangeBaseUrl: string;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
+
+```
+```typescript
+/**
+ * Deposit transaction, which effectively sends
+ * money from this wallet somewhere else.
+ */
+export interface TransactionDeposit extends TransactionCommon {
+ type: TransactionType.Deposit;
+ depositGroupId: string;
+ /**
+ * Target for the deposit.
+ */
+ targetPaytoUri: string;
+ /**
+ * Raw amount that is being deposited
+ */
+ amountRaw: AmountString;
+ /**
+ * Effective amount that is being deposited
+ */
+ amountEffective: AmountString;
+}
+
+```
+```typescript
+/**
+ * Credit because we were paid for a P2P invoice we created.
+ */
+export interface TransactionPeerPullCredit extends TransactionCommon {
+ type: TransactionType.PeerPullCredit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+ /**
+ * URI to send to the other party.
+ */
+ talerUri: string;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.PeerPullCredit = "peer-pull-credit"
+
+```
+```typescript
+export interface PeerInfoShort {
+ expiration: TalerProtocolTimestamp | undefined;
+ summary: string | undefined;
+}
+
+```
+```typescript
+/**
+ * Debit because we paid someone's invoice.
+ */
+export interface TransactionPeerPullDebit extends TransactionCommon {
+ type: TransactionType.PeerPullDebit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.PeerPullDebit = "peer-pull-debit"
+
+```
+```typescript
+/**
+ * We received money via a P2P payment.
+ */
+export interface TransactionPeerPushCredit extends TransactionCommon {
+ type: TransactionType.PeerPushCredit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.PeerPushCredit = "peer-push-credit"
+
+```
+```typescript
+/**
+ * We sent money via a P2P payment.
+ */
+export interface TransactionPeerPushDebit extends TransactionCommon {
+ type: TransactionType.PeerPushDebit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+ /**
+ * URI to accept the payment.
+ */
+ talerUri: string;
+}
+
+```
+```typescript
+// Enum value:
+// TransactionType.PeerPushDebit = "peer-push-debit"
+
+```
+
+(deletetransactionop)=
+### DeleteTransactionOp
+```typescript
+/**
+ * Delete a transaction locally in the wallet.
+ */
+export type DeleteTransactionOp = {
+ request: DeleteTransactionRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface DeleteTransactionRequest {
+ transactionId: string;
+}
+
+```
+
+(retrytransactionop)=
+### RetryTransactionOp
+```typescript
+/**
+ * Immediately retry a transaction.
+ */
+export type RetryTransactionOp = {
+ request: RetryTransactionRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface RetryTransactionRequest {
+ transactionId: string;
+}
+
+```
+
+(getwithdrawaldetailsforamountop)=
+### GetWithdrawalDetailsForAmountOp
+```typescript
+// group: Withdrawals
+/**
+ * Get details for withdrawing a particular amount (manual withdrawal).
+ */
+export type GetWithdrawalDetailsForAmountOp = {
+ request: GetWithdrawalDetailsForAmountRequest;
+ response: ManualWithdrawalDetails;
+};
+
+```
+```typescript
+export interface GetWithdrawalDetailsForAmountRequest {
+ exchangeBaseUrl: string;
+ amount: string;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface ManualWithdrawalDetails {
+ /**
+ * Did the user accept the current version of the exchange's
+ * terms of service?
+ */
+ tosAccepted: boolean;
+ /**
+ * Amount that the user will transfer to the exchange.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that will be added to the user's wallet balance.
+ */
+ amountEffective: AmountString;
+ /**
+ * Ways to pay the exchange.
+ */
+ paytoUris: string[];
+}
+
+```
+
+(getwithdrawaldetailsforuriop)=
+### GetWithdrawalDetailsForUriOp
+```typescript
+/**
+ * Get details for withdrawing via a particular taler:// URI.
+ */
+export type GetWithdrawalDetailsForUriOp = {
+ request: GetWithdrawalDetailsForUriRequest;
+ response: WithdrawUriInfoResponse;
+};
+
+```
+```typescript
+export interface GetWithdrawalDetailsForUriRequest {
+ talerWithdrawUri: string;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface WithdrawUriInfoResponse {
+ amount: AmountString;
+ defaultExchangeBaseUrl?: string;
+ possibleExchanges: ExchangeListItem[];
+}
+
+```
+
+(acceptbankintegratedwithdrawalop)=
+### AcceptBankIntegratedWithdrawalOp
+```typescript
+/**
+ * Accept a bank-integrated withdrawal.
+ */
+export type AcceptBankIntegratedWithdrawalOp = {
+ request: AcceptBankIntegratedWithdrawalRequest;
+ response: AcceptWithdrawalResponse;
+};
+
+```
+```typescript
+export interface AcceptBankIntegratedWithdrawalRequest {
+ talerWithdrawUri: string;
+ exchangeBaseUrl: string;
+ forcedDenomSel?: ForcedDenomSel;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface AcceptWithdrawalResponse {
+ reservePub: string;
+ confirmTransferUrl?: string;
+ transactionId: string;
+}
+
+```
+
+(acceptmanualwithdrawalop)=
+### AcceptManualWithdrawalOp
+```typescript
+/**
+ * Create a manual withdrawal.
+ */
+export type AcceptManualWithdrawalOp = {
+ request: AcceptManualWithdrawalRequest;
+ response: AcceptManualWithdrawalResult;
+};
+
+```
+```typescript
+export interface AcceptManualWithdrawalRequest {
+ exchangeBaseUrl: string;
+ amount: string;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface AcceptManualWithdrawalResult {
+ /**
+ * Payto URIs that can be used to fund the withdrawal.
+ */
+ exchangePaytoUris: string[];
+ /**
+ * Public key of the newly created reserve.
+ */
+ reservePub: string;
+ transactionId: string;
+}
+
+```
+
+(preparepayforuriop)=
+### PreparePayForUriOp
+```typescript
+// group: Merchant Payments
+/**
+ * Prepare to make a payment
+ */
+export type PreparePayForUriOp = {
+ op: WalletApiOperation.PreparePayForUri;
+ request: PreparePayRequest;
+ response: PreparePayResult;
+};
+
+```
+```typescript
+// Enum value:
+// WalletApiOperation.PreparePayForUri = "preparePayForUri"
+
+```
+```typescript
+export interface PreparePayRequest {
+ talerPayUri: string;
+}
+
+```
+```typescript
+/**
+ * Result of a prepare pay operation.
+ */
+export declare type PreparePayResult =
+ | PreparePayResultInsufficientBalance
+ | PreparePayResultAlreadyConfirmed
+ | PreparePayResultPaymentPossible;
+
+```
+```typescript
+export interface PreparePayResultInsufficientBalance {
+ status: PreparePayResultType.InsufficientBalance;
+ proposalId: string;
+ contractTerms: ContractTerms;
+ amountRaw: string;
+ noncePriv: string;
+}
+
+```
+```typescript
+export interface PreparePayResultAlreadyConfirmed {
+ status: PreparePayResultType.AlreadyConfirmed;
+ contractTerms: ContractTerms;
+ paid: boolean;
+ amountRaw: string;
+ amountEffective: string;
+ contractTermsHash: string;
+ proposalId: string;
+}
+
+```
+```typescript
+// Enum value:
+// PreparePayResultType.AlreadyConfirmed = "already-confirmed"
+
+```
+```typescript
+/**
+ * Payment is possible.
+ */
+export interface PreparePayResultPaymentPossible {
+ status: PreparePayResultType.PaymentPossible;
+ proposalId: string;
+ contractTerms: ContractTerms;
+ contractTermsHash: string;
+ amountRaw: string;
+ amountEffective: string;
+ noncePriv: string;
+}
+
+```
+```typescript
+// Enum value:
+// PreparePayResultType.PaymentPossible = "payment-possible"
+
+```
+
+(confirmpayop)=
+### ConfirmPayOp
+```typescript
+/**
+ * Confirm a payment that was previously prepared with
+ * {@link PreparePayForUriOp}
+ */
+export type ConfirmPayOp = {
+ op: WalletApiOperation.ConfirmPay;
+ request: ConfirmPayRequest;
+ response: ConfirmPayResult;
+};
+
+```
+```typescript
+// Enum value:
+// WalletApiOperation.ConfirmPay = "confirmPay"
+
+```
+```typescript
+export interface ConfirmPayRequest {
+ proposalId: string;
+ sessionId?: string;
+ forcedCoinSel?: ForcedCoinSel;
+}
+
+```
+```typescript
+export declare type ConfirmPayResult =
+ | ConfirmPayResultDone
+ | ConfirmPayResultPending;
+
+```
+```typescript
+/**
+ * Result for confirmPay
+ */
+export interface ConfirmPayResultDone {
+ type: ConfirmPayResultType.Done;
+ contractTerms: ContractTerms;
+ transactionId: string;
+}
+
+```
+```typescript
+// Enum value:
+// ConfirmPayResultType.Done = "done"
+
+```
+```typescript
+export interface ConfirmPayResultPending {
+ type: ConfirmPayResultType.Pending;
+ transactionId: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+
+(abortpaywithrefundop)=
+### AbortPayWithRefundOp
+```typescript
+/**
+ * Abort a pending payment with a refund.
+ */
+export type AbortPayWithRefundOp = {
+ request: AbortPayWithRefundRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AbortPayWithRefundRequest {
+ proposalId: string;
+}
+
+```
+
+(applyrefundop)=
+### ApplyRefundOp
+```typescript
+/**
+ * Check for a refund based on a taler://refund URI.
+ */
+export type ApplyRefundOp = {
+ request: ApplyRefundRequest;
+ response: ApplyRefundResponse;
+};
+
+```
+```typescript
+export interface ApplyRefundRequest {
+ talerRefundUri: string;
+}
+
+```
+```typescript
+export interface ApplyRefundResponse {
+ contractTermsHash: string;
+ transactionId: string;
+ proposalId: string;
+ amountEffectivePaid: AmountString;
+ amountRefundGranted: AmountString;
+ amountRefundGone: AmountString;
+ pendingAtExchange: boolean;
+ info: OrderShortInfo;
+}
+
+```
+
+(preparetipop)=
+### PrepareTipOp
+```typescript
+// group: Tipping
+/**
+ * Query and store information about a tip.
+ */
+export type PrepareTipOp = {
+ request: PrepareTipRequest;
+ response: PrepareTipResult;
+};
+
+```
+```typescript
+export interface PrepareTipRequest {
+ talerTipUri: string;
+}
+
+```
+```typescript
+export interface PrepareTipResult {
+ /**
+ * Unique ID for the tip assigned by the wallet.
+ * Typically different from the merchant-generated tip ID.
+ */
+ walletTipId: string;
+ /**
+ * Has the tip already been accepted?
+ */
+ accepted: boolean;
+ /**
+ * Amount that the merchant gave.
+ */
+ tipAmountRaw: AmountString;
+ /**
+ * Amount that arrived at the wallet.
+ * Might be lower than the raw amount due to fees.
+ */
+ tipAmountEffective: AmountString;
+ /**
+ * Base URL of the merchant backend giving then tip.
+ */
+ merchantBaseUrl: string;
+ /**
+ * Base URL of the exchange that is used to withdraw the tip.
+ * Determined by the merchant, the wallet/user has no choice here.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Time when the tip will expire. After it expired, it can't be picked
+ * up anymore.
+ */
+ expirationTimestamp: TalerProtocolTimestamp;
+}
+
+```
+
+(accepttipop)=
+### AcceptTipOp
+```typescript
+/**
+ * Accept a tip.
+ */
+export type AcceptTipOp = {
+ request: AcceptTipRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AcceptTipRequest {
+ walletTipId: string;
+}
+
+```
+
+(listexchangesop)=
+### ListExchangesOp
+```typescript
+// group: Exchange Management
+/**
+ * List exchanges known to the wallet.
+ */
+export type ListExchangesOp = {
+ request: {};
+ response: ExchangesListResponse;
+};
+
+```
+```typescript
+export interface ExchangesListResponse {
+ exchanges: ExchangeListItem[];
+}
+
+```
+
+(addexchangeop)=
+### AddExchangeOp
+```typescript
+/**
+ * Add / force-update an exchange.
+ */
+export type AddExchangeOp = {
+ request: AddExchangeRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AddExchangeRequest {
+ exchangeBaseUrl: string;
+ forceUpdate?: boolean;
+}
+
+```
+
+(setexchangetosacceptedop)=
+### SetExchangeTosAcceptedOp
+```typescript
+/**
+ * Accept a particular version of the exchange terms of service.
+ */
+export type SetExchangeTosAcceptedOp = {
+ request: AcceptExchangeTosRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AcceptExchangeTosRequest {
+ exchangeBaseUrl: string;
+ etag: string | undefined;
+}
+
+```
+
+(getexchangetosop)=
+### GetExchangeTosOp
+```typescript
+/**
+ * Get the current terms of a service of an exchange.
+ */
+export type GetExchangeTosOp = {
+ request: GetExchangeTosRequest;
+ response: GetExchangeTosResult;
+};
+
+```
+```typescript
+export interface GetExchangeTosRequest {
+ exchangeBaseUrl: string;
+ acceptedFormat?: string[];
+}
+
+```
+```typescript
+export interface GetExchangeTosResult {
+ /**
+ * Markdown version of the current ToS.
+ */
+ content: string;
+ /**
+ * Version tag of the current ToS.
+ */
+ currentEtag: string;
+ /**
+ * Version tag of the last ToS that the user has accepted,
+ * if any.
+ */
+ acceptedEtag: string | undefined;
+ /**
+ * Accepted content type
+ */
+ contentType: string;
+}
+
+```
+
+(listcurrenciesop)=
+### ListCurrenciesOp
+```typescript
+/**
+ * List currencies known to the wallet.
+ */
+export type ListCurrenciesOp = {
+ request: {};
+ response: WalletCurrencyInfo;
+};
+
+```
+```typescript
+export interface WalletCurrencyInfo {
+ trustedAuditors: {
+ currency: string;
+ auditorPub: string;
+ auditorBaseUrl: string;
+ }[];
+ trustedExchanges: {
+ currency: string;
+ exchangeMasterPub: string;
+ exchangeBaseUrl: string;
+ }[];
+}
+
+```
+
+(createdepositgroupop)=
+### CreateDepositGroupOp
+```typescript
+// group: Deposits
+/**
+ * Create a new deposit group.
+ *
+ * Deposit groups are used to deposit multiple coins to a bank
+ * account, usually the wallet user's own bank account.
+ */
+export type CreateDepositGroupOp = {
+ request: CreateDepositGroupRequest;
+ response: CreateDepositGroupResponse;
+};
+
+```
+```typescript
+export interface CreateDepositGroupRequest {
+ depositPaytoUri: string;
+ amount: AmountString;
+}
+
+```
+```typescript
+export interface CreateDepositGroupResponse {
+ depositGroupId: string;
+ transactionId: string;
+}
+
+```
+
+(trackdepositgroupop)=
+### TrackDepositGroupOp
+```typescript
+/**
+ * Track the status of a deposit group by querying the exchange.
+ */
+export type TrackDepositGroupOp = {
+ request: TrackDepositGroupRequest;
+ response: TrackDepositGroupResponse;
+};
+
+```
+```typescript
+export interface TrackDepositGroupRequest {
+ depositGroupId: string;
+}
+
+```
+```typescript
+export interface TrackDepositGroupResponse {
+ responses: {
+ status: number;
+ body: any;
+ }[];
+}
+
+```
+
+(exportbackuprecoveryop)=
+### ExportBackupRecoveryOp
+```typescript
+// group: Backups
+/**
+ * Export the recovery information for the wallet.
+ */
+export type ExportBackupRecoveryOp = {
+ request: {};
+ response: BackupRecovery;
+};
+
+```
+
+(importbackuprecoveryop)=
+### ImportBackupRecoveryOp
+```typescript
+/**
+ * Import recovery information into the wallet.
+ */
+export type ImportBackupRecoveryOp = {
+ request: RecoveryLoadRequest;
+ response: {};
+};
+
+```
+```typescript
+/**
+ * Load recovery information into the wallet.
+ */
+export interface RecoveryLoadRequest {
+ recovery: BackupRecovery;
+ strategy?: RecoveryMergeStrategy;
+}
+
+```
+```typescript
+/**
+ * Strategy for loading recovery information.
+ */
+export declare enum RecoveryMergeStrategy {
+ /**
+ * Keep the local wallet root key, import and take over providers.
+ */
+ Ours = "ours",
+ /**
+ * Migrate to the wallet root key from the recovery information.
+ */
+ Theirs = "theirs",
+}
+
+```
+
+(runbackupcycleop)=
+### RunBackupCycleOp
+```typescript
+/**
+ * Manually make and upload a backup.
+ */
+export type RunBackupCycleOp = {
+ request: {};
+ response: {};
+};
+
+```
+
+(addbackupproviderop)=
+### AddBackupProviderOp
+```typescript
+/**
+ * Add a new backup provider.
+ */
+export type AddBackupProviderOp = {
+ request: AddBackupProviderRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AddBackupProviderRequest {
+ backupProviderBaseUrl: string;
+ name: string;
+ /**
+ * Activate the provider. Should only be done after
+ * the user has reviewed the provider.
+ */
+ activate?: boolean;
+}
+
+```
+
+(getbackupinfoop)=
+### GetBackupInfoOp
+```typescript
+/**
+ * Get some useful stats about the backup state.
+ */
+export type GetBackupInfoOp = {
+ request: {};
+ response: BackupInfo;
+};
+
+```
+```typescript
+export interface BackupInfo {
+ walletRootPub: string;
+ deviceId: string;
+ providers: ProviderInfo[];
+}
+
+```
+```typescript
+/**
+ * Information about one provider.
+ *
+ * We don't store the account key here,
+ * as that's derived from the wallet root key.
+ */
+export interface ProviderInfo {
+ active: boolean;
+ syncProviderBaseUrl: string;
+ name: string;
+ terms?: BackupProviderTerms;
+ /**
+ * Last communication issue with the provider.
+ */
+ lastError?: TalerErrorDetail;
+ lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp;
+ lastAttemptedBackupTimestamp?: TalerProtocolTimestamp;
+ paymentProposalIds: string[];
+ backupProblem?: BackupProblem;
+ paymentStatus: ProviderPaymentStatus;
+}
+
+```
+```typescript
+export interface BackupProviderTerms {
+ supportedProtocolVersion: string;
+ annualFee: AmountString;
+ storageLimitInMegabytes: number;
+}
+
+```
+```typescript
+export type BackupProblem =
+ | BackupUnreadableProblem
+ | BackupConflictingDeviceProblem;
+
+```
+```typescript
+export interface BackupUnreadableProblem {
+ type: "backup-unreadable";
+}
+
+```
+```typescript
+export interface BackupConflictingDeviceProblem {
+ type: "backup-conflicting-device";
+ otherDeviceId: string;
+ myDeviceId: string;
+ backupTimestamp: AbsoluteTime;
+}
+
+```
+```typescript
+export type ProviderPaymentStatus =
+ | ProviderPaymentTermsChanged
+ | ProviderPaymentPaid
+ | ProviderPaymentInsufficientBalance
+ | ProviderPaymentUnpaid
+ | ProviderPaymentPending;
+
+```
+```typescript
+export interface ProviderPaymentTermsChanged {
+ type: ProviderPaymentType.TermsChanged;
+ paidUntil: AbsoluteTime;
+ oldTerms: BackupProviderTerms;
+ newTerms: BackupProviderTerms;
+}
+
+```
+```typescript
+// Enum value:
+// ProviderPaymentType.TermsChanged = "terms-changed"
+
+```
+```typescript
+export interface ProviderPaymentPaid {
+ type: ProviderPaymentType.Paid;
+ paidUntil: AbsoluteTime;
+}
+
+```
+```typescript
+// Enum value:
+// ProviderPaymentType.Paid = "paid"
+
+```
+```typescript
+export interface ProviderPaymentInsufficientBalance {
+ type: ProviderPaymentType.InsufficientBalance;
+}
+
+```
+```typescript
+export interface ProviderPaymentUnpaid {
+ type: ProviderPaymentType.Unpaid;
+}
+
+```
+```typescript
+// Enum value:
+// ProviderPaymentType.Unpaid = "unpaid"
+
+```
+```typescript
+export interface ProviderPaymentPending {
+ type: ProviderPaymentType.Pending;
+}
+
+```
+
+(setwalletdeviceidop)=
+### SetWalletDeviceIdOp
+```typescript
+/**
+ * Set the internal device ID of the wallet, used to
+ * identify whether a different/new wallet is accessing
+ * the backup of another wallet.
+ */
+export type SetWalletDeviceIdOp = {
+ request: SetWalletDeviceIdRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface SetWalletDeviceIdRequest {
+ /**
+ * New wallet device ID to set.
+ */
+ walletDeviceId: string;
+}
+
+```
+
+(exportbackupplainop)=
+### ExportBackupPlainOp
+```typescript
+/**
+ * Export a backup JSON, mostly useful for testing.
+ */
+export type ExportBackupPlainOp = {
+ request: {};
+ response: WalletBackupContentV1;
+};
+
+```
+
+(initiatepeerpushpaymentop)=
+### InitiatePeerPushPaymentOp
+```typescript
+// group: Peer Payments
+/**
+ * Initiate an outgoing peer push payment.
+ */
+export type InitiatePeerPushPaymentOp = {
+ request: InitiatePeerPushPaymentRequest;
+ response: InitiatePeerPushPaymentResponse;
+};
+
+```
+```typescript
+export interface InitiatePeerPushPaymentRequest {
+ amount: AmountString;
+ partialContractTerms: any;
+}
+
+```
+```typescript
+export interface InitiatePeerPushPaymentResponse {
+ exchangeBaseUrl: string;
+ pursePub: string;
+ mergePriv: string;
+ contractPriv: string;
+ talerUri: string;
+ transactionId: string;
+}
+
+```
+
+(checkpeerpushpaymentop)=
+### CheckPeerPushPaymentOp
+```typescript
+/**
+ * Check an incoming peer push payment.
+ */
+export type CheckPeerPushPaymentOp = {
+ request: CheckPeerPushPaymentRequest;
+ response: CheckPeerPushPaymentResponse;
+};
+
+```
+```typescript
+export interface CheckPeerPushPaymentRequest {
+ talerUri: string;
+}
+
+```
+```typescript
+export interface CheckPeerPushPaymentResponse {
+ contractTerms: any;
+ amount: AmountString;
+ peerPushPaymentIncomingId: string;
+}
+
+```
+
+(acceptpeerpushpaymentop)=
+### AcceptPeerPushPaymentOp
+```typescript
+/**
+ * Accept an incoming peer push payment.
+ */
+export type AcceptPeerPushPaymentOp = {
+ request: AcceptPeerPushPaymentRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AcceptPeerPushPaymentRequest {
+ /**
+ * Transparent identifier of the incoming peer push payment.
+ */
+ peerPushPaymentIncomingId: string;
+}
+
+```
+
+(initiatepeerpullpaymentop)=
+### InitiatePeerPullPaymentOp
+```typescript
+/**
+ * Initiate an outgoing peer pull payment.
+ */
+export type InitiatePeerPullPaymentOp = {
+ request: InitiatePeerPullPaymentRequest;
+ response: InitiatePeerPullPaymentResponse;
+};
+
+```
+```typescript
+export interface InitiatePeerPullPaymentRequest {
+ /**
+ * FIXME: Make this optional?
+ */
+ exchangeBaseUrl: string;
+ amount: AmountString;
+ partialContractTerms: any;
+}
+
+```
+```typescript
+export interface InitiatePeerPullPaymentResponse {
+ /**
+ * Taler URI for the other party to make the payment
+ * that was requested.
+ */
+ talerUri: string;
+ transactionId: string;
+}
+
+```
+
+(checkpeerpullpaymentop)=
+### CheckPeerPullPaymentOp
+```typescript
+/**
+ * Prepare for an incoming peer pull payment.
+ */
+export type CheckPeerPullPaymentOp = {
+ request: CheckPeerPullPaymentRequest;
+ response: CheckPeerPullPaymentResponse;
+};
+
+```
+```typescript
+export interface CheckPeerPullPaymentRequest {
+ talerUri: string;
+}
+
+```
+```typescript
+export interface CheckPeerPullPaymentResponse {
+ contractTerms: any;
+ amount: AmountString;
+ peerPullPaymentIncomingId: string;
+}
+
+```
+
+(acceptpeerpullpaymentop)=
+### AcceptPeerPullPaymentOp
+```typescript
+/**
+ * Accept an incoming peer pull payment.
+ */
+export type AcceptPeerPullPaymentOp = {
+ request: AcceptPeerPullPaymentRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface AcceptPeerPullPaymentRequest {
+ /**
+ * Transparent identifier of the incoming peer pull payment.
+ */
+ peerPullPaymentIncomingId: string;
+}
+
+```
+
+(exportdbop)=
+### ExportDbOp
+```typescript
+// group: Database Management
+/**
+ * Exoport the wallet database's contents to JSON.
+ */
+export type ExportDbOp = {
+ request: {};
+ response: any;
+};
+
+```
+
+(cleardbop)=
+### ClearDbOp
+```typescript
+/**
+ * Dangerously clear the whole wallet database.
+ */
+export type ClearDbOp = {
+ request: {};
+ response: {};
+};
+
+```
+
+(recycleop)=
+### RecycleOp
+```typescript
+/**
+ * Export a backup, clear the database and re-import it.
+ */
+export type RecycleOp = {
+ request: {};
+ response: {};
+};
+
+```
+
+(runintegrationtestop)=
+### RunIntegrationTestOp
+```typescript
+// group: Testing and Debugging
+/**
+ * Run a simple integration test on a test deployment
+ * of the exchange and merchant.
+ */
+export type RunIntegrationTestOp = {
+ request: IntegrationTestArgs;
+ response: {};
+};
+
+```
+```typescript
+export interface IntegrationTestArgs {
+ exchangeBaseUrl: string;
+ bankBaseUrl: string;
+ bankAccessApiBaseUrl?: string;
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ amountToWithdraw: string;
+ amountToSpend: string;
+}
+
+```
+
+(withdrawtestbalanceop)=
+### WithdrawTestBalanceOp
+```typescript
+/**
+ * Make withdrawal on a test deployment of the exchange
+ * and merchant.
+ */
+export type WithdrawTestBalanceOp = {
+ request: WithdrawTestBalanceRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface WithdrawTestBalanceRequest {
+ amount: string;
+ bankBaseUrl: string;
+ /**
+ * Bank access API base URL. Defaults to the bankBaseUrl.
+ */
+ bankAccessApiBaseUrl?: string;
+ exchangeBaseUrl: string;
+ forcedDenomSel?: ForcedDenomSel;
+}
+
+```
+
+(withdrawtestkudosop)=
+### WithdrawTestkudosOp
+```typescript
+/**
+ * Make a withdrawal of testkudos on test.taler.net.
+ */
+export type WithdrawTestkudosOp = {
+ op: WalletApiOperation.WithdrawTestkudos;
+ request: {};
+ response: {};
+};
+
+```
+```typescript
+// Enum value:
+// WalletApiOperation.WithdrawTestkudos = "withdrawTestkudos"
+
+```
+
+(testpayop)=
+### TestPayOp
+```typescript
+/**
+ * Make a test payment using a test deployment of
+ * the exchange and merchant.
+ */
+export type TestPayOp = {
+ request: TestPayArgs;
+ response: TestPayResult;
+};
+
+```
+```typescript
+export interface TestPayArgs {
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ amount: string;
+ summary: string;
+ forcedCoinSel?: ForcedCoinSel;
+}
+
+```
+```typescript
+export interface TestPayResult {
+ payCoinSelection: PayCoinSelection;
+}
+
+```
+```typescript
+/**
+ * Result of selecting coins, contains the exchange, and selected
+ * coins with their denomination.
+ */
+export interface PayCoinSelection {
+ /**
+ * Amount requested by the merchant.
+ */
+ paymentAmount: AmountJson;
+ /**
+ * Public keys of the coins that were selected.
+ */
+ coinPubs: string[];
+ /**
+ * Amount that each coin contributes.
+ */
+ coinContributions: AmountJson[];
+ /**
+ * How much of the wire fees is the customer paying?
+ */
+ customerWireFees: AmountJson;
+ /**
+ * How much of the deposit fees is the customer paying?
+ */
+ customerDepositFees: AmountJson;
+}
+
+```
+```typescript
+/**
+ * Non-negative financial amount. Fractional values are expressed as multiples
+ * of 1e-8.
+ */
+export interface AmountJson {
+ /**
+ * Value, must be an integer.
+ */
+ readonly value: number;
+ /**
+ * Fraction, must be an integer. Represent 1/1e8 of a unit.
+ */
+ readonly fraction: number;
+ /**
+ * Currency of the amount.
+ */
+ readonly currency: string;
+}
+
+```
+
+(withdrawfakebankop)=
+### WithdrawFakebankOp
+```typescript
+/**
+ * Make a withdrawal from a fakebank, i.e.
+ * a bank where test users can be registered freely
+ * and testing APIs are available.
+ */
+export type WithdrawFakebankOp = {
+ op: WalletApiOperation.WithdrawFakebank;
+ request: WithdrawFakebankRequest;
+ response: {};
+};
+
+```
+```typescript
+// Enum value:
+// WalletApiOperation.WithdrawFakebank = "withdrawFakebank"
+
+```
+```typescript
+export interface WithdrawFakebankRequest {
+ amount: AmountString;
+ exchange: string;
+ bank: string;
+}
+
+```
+
+(getpendingtasksop)=
+### GetPendingTasksOp
+```typescript
+/**
+ * Get wallet-internal pending tasks.
+ */
+export type GetPendingTasksOp = {
+ request: {};
+ response: PendingTasksResponse;
+};
+
+```
+```typescript
+/**
+ * Response returned from the pending operations API.
+ */
+export interface PendingOperationsResponse {
+ /**
+ * List of pending operations.
+ */
+ pendingOperations: PendingTaskInfo[];
+}
+
+```
+```typescript
+/**
+ * Information about a pending operation.
+ */
+export type PendingTaskInfo = PendingTaskInfoCommon &
+ (
+ | PendingExchangeUpdateTask
+ | PendingExchangeCheckRefreshTask
+ | PendingPayTask
+ | PendingProposalDownloadTask
+ | PendingRefreshTask
+ | PendingRefundQueryTask
+ | PendingTipPickupTask
+ | PendingWithdrawTask
+ | PendingRecoupTask
+ | PendingDepositTask
+ | PendingBackupTask
+ );
+
+```
+```typescript
+/**
+ * Fields that are present in every pending operation.
+ */
+export interface PendingTaskInfoCommon {
+ /**
+ * Type of the pending operation.
+ */
+ type: PendingTaskType;
+ /**
+ * Unique identifier for the pending task.
+ */
+ id: string;
+ /**
+ * Set to true if the operation indicates that something is really in progress,
+ * as opposed to some regular scheduled operation that can be tried later.
+ */
+ givesLifeness: boolean;
+ /**
+ * Timestamp when the pending operation should be executed next.
+ */
+ timestampDue: AbsoluteTime;
+ /**
+ * Retry info. Currently used to stop the wallet after any operation
+ * exceeds a number of retries.
+ */
+ retryInfo?: RetryInfo;
+}
+
+```
+```typescript
+export enum PendingTaskType {
+ ExchangeUpdate = "exchange-update",
+ ExchangeCheckRefresh = "exchange-check-refresh",
+ Pay = "pay",
+ ProposalDownload = "proposal-download",
+ Refresh = "refresh",
+ Recoup = "recoup",
+ RefundQuery = "refund-query",
+ TipPickup = "tip-pickup",
+ Withdraw = "withdraw",
+ Deposit = "deposit",
+ Backup = "backup",
+}
+
+```
+```typescript
+export interface RetryInfo {
+ firstTry: AbsoluteTime;
+ nextRetry: AbsoluteTime;
+ retryCounter: number;
+}
+
+```
+```typescript
+export interface RetryPolicy {
+ readonly backoffDelta: Duration;
+ readonly backoffBase: number;
+ readonly maxTimeout: Duration;
+}
+
+```
+```typescript
+/**
+ * The wallet is currently updating information about an exchange.
+ */
+export interface PendingExchangeUpdateTask {
+ type: PendingTaskType.ExchangeUpdate;
+ exchangeBaseUrl: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.ExchangeUpdate = "exchange-update"
+
+```
+```typescript
+/**
+ * The wallet should check whether coins from this exchange
+ * need to be auto-refreshed.
+ */
+export interface PendingExchangeCheckRefreshTask {
+ type: PendingTaskType.ExchangeCheckRefresh;
+ exchangeBaseUrl: string;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.ExchangeCheckRefresh = "exchange-check-refresh"
+
+```
+```typescript
+/**
+ * The wallet is signing coins and then sending them to
+ * the merchant.
+ */
+export interface PendingPayTask {
+ type: PendingTaskType.Pay;
+ proposalId: string;
+ isReplay: boolean;
+ retryInfo?: RetryInfo;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.Pay = "pay"
+
+```
+```typescript
+/**
+ * Status of downloading signed contract terms from a merchant.
+ */
+export interface PendingProposalDownloadTask {
+ type: PendingTaskType.ProposalDownload;
+ merchantBaseUrl: string;
+ proposalTimestamp: TalerProtocolTimestamp;
+ proposalId: string;
+ orderId: string;
+ lastError?: TalerErrorDetail;
+ retryInfo?: RetryInfo;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.ProposalDownload = "proposal-download"
+
+```
+```typescript
+/**
+ * Status of an ongoing withdrawal operation.
+ */
+export interface PendingRefreshTask {
+ type: PendingTaskType.Refresh;
+ lastError?: TalerErrorDetail;
+ refreshGroupId: string;
+ finishedPerCoin: boolean[];
+ retryInfo?: RetryInfo;
+}
+
+```
+```typescript
+/**
+ * The wallet is querying the merchant about whether any refund
+ * permissions are available for a purchase.
+ */
+export interface PendingRefundQueryTask {
+ type: PendingTaskType.RefundQuery;
+ proposalId: string;
+ retryInfo?: RetryInfo;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.RefundQuery = "refund-query"
+
+```
+```typescript
+/**
+ * The wallet is picking up a tip that the user has accepted.
+ */
+export interface PendingTipPickupTask {
+ type: PendingTaskType.TipPickup;
+ tipId: string;
+ merchantBaseUrl: string;
+ merchantTipId: string;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.TipPickup = "tip-pickup"
+
+```
+```typescript
+/**
+ * Status of an ongoing withdrawal operation.
+ */
+export interface PendingWithdrawTask {
+ type: PendingTaskType.Withdraw;
+ lastError: TalerErrorDetail | undefined;
+ retryInfo?: RetryInfo;
+ withdrawalGroupId: string;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.Withdraw = "withdraw"
+
+```
+```typescript
+export interface PendingRecoupTask {
+ type: PendingTaskType.Recoup;
+ recoupGroupId: string;
+ retryInfo?: RetryInfo;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.Recoup = "recoup"
+
+```
+```typescript
+/**
+ * Status of an ongoing deposit operation.
+ */
+export interface PendingDepositTask {
+ type: PendingTaskType.Deposit;
+ lastError: TalerErrorDetail | undefined;
+ retryInfo: RetryInfo | undefined;
+ depositGroupId: string;
+}
+
+```
+```typescript
+export interface PendingBackupTask {
+ type: PendingTaskType.Backup;
+ backupProviderBaseUrl: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+// Enum value:
+// PendingTaskType.Backup = "backup"
+
+```
+
+(dumpcoinsop)=
+### DumpCoinsOp
+```typescript
+/**
+ * Dump all coins of the wallet in a simple JSON format.
+ */
+export type DumpCoinsOp = {
+ request: {};
+ response: CoinDumpJson;
+};
+
+```
+```typescript
+/**
+ * Easy to process format for the public data of coins
+ * managed by the wallet.
+ */
+export interface CoinDumpJson {
+ coins: Array<{
+ /**
+ * The coin's denomination's public key.
+ */
+ denom_pub: DenominationPubKey;
+ /**
+ * Hash of denom_pub.
+ */
+ denom_pub_hash: string;
+ /**
+ * Value of the denomination (without any fees).
+ */
+ denom_value: string;
+ /**
+ * Public key of the coin.
+ */
+ coin_pub: string;
+ /**
+ * Base URL of the exchange for the coin.
+ */
+ exchange_base_url: string;
+ /**
+ * Remaining value on the coin, to the knowledge of
+ * the wallet.
+ */
+ remaining_value: string;
+ /**
+ * Public key of the parent coin.
+ * Only present if this coin was obtained via refreshing.
+ */
+ refresh_parent_coin_pub: string | undefined;
+ /**
+ * Public key of the reserve for this coin.
+ * Only present if this coin was obtained via refreshing.
+ */
+ withdrawal_reserve_pub: string | undefined;
+ /**
+ * Is the coin suspended?
+ * Suspended coins are not considered for payments.
+ */
+ coin_suspended: boolean;
+ /**
+ * Information about the age restriction
+ */
+ ageCommitmentProof: AgeCommitmentProof | undefined;
+ }>;
+}
+
+```
+```typescript
+interface Array<T> extends RelativeIndexable<T> {}
+
+```
+
+(setcoinsuspendedop)=
+### SetCoinSuspendedOp
+```typescript
+/**
+ * Set a coin as (un-)suspended.
+ * Suspended coins won't be used for payments.
+ */
+export type SetCoinSuspendedOp = {
+ request: SetCoinSuspendedRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface SetCoinSuspendedRequest {
+ coinPub: string;
+ suspended: boolean;
+}
+
+```
+
+(forcerefreshop)=
+### ForceRefreshOp
+```typescript
+/**
+ * Force a refresh on coins where it would not
+ * be necessary.
+ */
+export type ForceRefreshOp = {
+ request: ForceRefreshRequest;
+ response: {};
+};
+
+```
+```typescript
+export interface ForceRefreshRequest {
+ coinPubList: string[];
+}
+
+```
+
+## Common Declarations
+```typescript
+export interface OrderShortInfo {
+ /**
+ * Order ID, uniquely identifies the order within a merchant instance
+ */
+ orderId: string;
+ /**
+ * Hash of the contract terms.
+ */
+ contractTermsHash: string;
+ /**
+ * More information about the merchant
+ */
+ merchant: MerchantInfo;
+ /**
+ * Summary of the order, given by the merchant
+ */
+ summary: string;
+ /**
+ * Map from IETF BCP 47 language tags to localized summaries
+ */
+ summary_i18n?: InternationalizedString;
+ /**
+ * List of products that are part of the order
+ */
+ products: Product[] | undefined;
+ /**
+ * Time indicating when the order should be delivered.
+ * May be overwritten by individual products.
+ */
+ delivery_date?: TalerProtocolTimestamp;
+ /**
+ * Delivery location for (all!) products.
+ */
+ delivery_location?: Location;
+ /**
+ * URL of the fulfillment, given by the merchant
+ */
+ fulfillmentUrl?: string;
+ /**
+ * Plain text message that should be shown to the user
+ * when the payment is complete.
+ */
+ fulfillmentMessage?: string;
+ /**
+ * Translations of fulfillmentMessage.
+ */
+ fulfillmentMessage_i18n?: InternationalizedString;
+}
+```
+```typescript
+export interface MerchantInfo {
+ name: string;
+ jurisdiction?: Location;
+ address?: Location;
+ logo?: string;
+ website?: string;
+ email?: string;
+}
+```
+```typescript
+export interface Location {
+ country?: string;
+ country_subdivision?: string;
+ district?: string;
+ town?: string;
+ town_location?: string;
+ post_code?: string;
+ street?: string;
+ building_name?: string;
+ building_number?: string;
+ address_lines?: string[];
+}
+```
+```typescript
+export interface InternationalizedString {
+ [lang_tag: string]: string;
+}
+```
+```typescript
+export interface Product {
+ product_id?: string;
+ description: string;
+ description_i18n?: {
+ [lang_tag: string]: string;
+ };
+ quantity?: number;
+ unit?: string;
+ price?: AmountString;
+ image?: string;
+ taxes?: Tax[];
+ delivery_date?: TalerProtocolTimestamp;
+}
+```
+```typescript
+export interface Tax {
+ name: string;
+ tax: AmountString;
+}
+```
+```typescript
+export interface TalerProtocolTimestamp {
+ readonly t_s: number | "never";
+}
+```
+```typescript
+// Enum value:
+// PendingTaskType.Refresh = "refresh"
+```
+```typescript
+// Enum value:
+// PendingTaskType.Deposit = "deposit"
+```
+```typescript
+export interface ExchangeListItem {
+ exchangeBaseUrl: string;
+ currency: string;
+ paytoUris: string[];
+ tos: ExchangeTos;
+}
+```
+```typescript
+export interface ExchangeTos {
+ acceptedVersion?: string;
+ currentVersion?: string;
+ contentType?: string;
+ content?: string;
+}
+```
+```typescript
+export interface ForcedDenomSel {
+ denoms: {
+ value: AmountString;
+ count: number;
+ }[];
+}
+```
+```typescript
+// Enum value:
+// ProviderPaymentType.InsufficientBalance = "insufficient-balance"
+```
+```typescript
+/**
+ * Contract terms from a merchant.
+ */
+export interface ContractTerms {
+ /**
+ * Hash of the merchant's wire details.
+ */
+ h_wire: string;
+ /**
+ * Hash of the merchant's wire details.
+ */
+ auto_refund?: TalerProtocolDuration;
+ /**
+ * Wire method the merchant wants to use.
+ */
+ wire_method: string;
+ /**
+ * Human-readable short summary of the contract.
+ */
+ summary: string;
+ summary_i18n?: InternationalizedString;
+ /**
+ * Nonce used to ensure freshness.
+ */
+ nonce: string;
+ /**
+ * Total amount payable.
+ */
+ amount: string;
+ /**
+ * Auditors accepted by the merchant.
+ */
+ auditors: AuditorHandle[];
+ /**
+ * Deadline to pay for the contract.
+ */
+ pay_deadline: TalerProtocolTimestamp;
+ /**
+ * Maximum deposit fee covered by the merchant.
+ */
+ max_fee: string;
+ /**
+ * Information about the merchant.
+ */
+ merchant: MerchantInfo;
+ /**
+ * Public key of the merchant.
+ */
+ merchant_pub: string;
+ /**
+ * Time indicating when the order should be delivered.
+ * May be overwritten by individual products.
+ */
+ delivery_date?: TalerProtocolTimestamp;
+ /**
+ * Delivery location for (all!) products.
+ */
+ delivery_location?: Location;
+ /**
+ * List of accepted exchanges.
+ */
+ exchanges: ExchangeHandle[];
+ /**
+ * Products that are sold in this contract.
+ */
+ products?: Product[];
+ /**
+ * Deadline for refunds.
+ */
+ refund_deadline: TalerProtocolTimestamp;
+ /**
+ * Deadline for the wire transfer.
+ */
+ wire_transfer_deadline: TalerProtocolTimestamp;
+ /**
+ * Time when the contract was generated by the merchant.
+ */
+ timestamp: TalerProtocolTimestamp;
+ /**
+ * Order id to uniquely identify the purchase within
+ * one merchant instance.
+ */
+ order_id: string;
+ /**
+ * Base URL of the merchant's backend.
+ */
+ merchant_base_url: string;
+ /**
+ * Fulfillment URL to view the product or
+ * delivery status.
+ */
+ fulfillment_url?: string;
+ /**
+ * URL meant to share the shopping cart.
+ */
+ public_reorder_url?: string;
+ /**
+ * Plain text fulfillment message in the merchant's default language.
+ */
+ fulfillment_message?: string;
+ /**
+ * Internationalized fulfillment messages.
+ */
+ fulfillment_message_i18n?: InternationalizedString;
+ /**
+ * Share of the wire fee that must be settled with one payment.
+ */
+ wire_fee_amortization?: number;
+ /**
+ * Maximum wire fee that the merchant agrees to pay for.
+ */
+ max_wire_fee?: string;
+ minimum_age?: number;
+ /**
+ * Extra data, interpreted by the mechant only.
+ */
+ extra?: any;
+}
+```
+```typescript
+export interface TalerProtocolDuration {
+ readonly d_us: number | "forever";
+}
+```
+```typescript
+export interface AuditorHandle {
+ /**
+ * Official name of the auditor.
+ */
+ name: string;
+ /**
+ * Master public signing key of the auditor.
+ */
+ auditor_pub: string;
+ /**
+ * Base URL of the auditor.
+ */
+ url: string;
+}
+```
+```typescript
+/**
+ * Information about an exchange as stored inside a
+ * merchant's contract terms.
+ */
+export interface ExchangeHandle {
+ /**
+ * Master public signing key of the exchange.
+ */
+ master_pub: string;
+ /**
+ * Base URL of the exchange.
+ */
+ url: string;
+}
+```
+```typescript
+/**
+ * Forced coin selection for deposits/payments.
+ */
+export interface ForcedCoinSel {
+ coins: {
+ value: AmountString;
+ contribution: AmountString;
+ }[];
+}
+```
+```typescript
+// Enum value:
+// ProviderPaymentType.Pending = "pending"
+```
+```typescript
+export interface TalerErrorDetail {
+ code: TalerErrorCode;
+ hint?: string;
+ [x: string]: unknown;
+}
+```
+```typescript
+export interface BackupRecovery {
+ walletRootPriv: string;
+ providers: {
+ url: string;
+ }[];
+}
+```
+```typescript
+export interface AbsoluteTime {
+ /**
+ * Timestamp in milliseconds.
+ */
+ readonly t_ms: number | "never";
+}
+```
+```typescript
+export interface Duration {
+ /**
+ * Duration in milliseconds.
+ */
+ readonly d_ms: number | "forever";
+}
+```
diff --git a/extract-tsdefs/package.json b/extract-tsdefs/package.json
new file mode 100644
index 00000000..cf3ba343
--- /dev/null
+++ b/extract-tsdefs/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "extract-tsdefs",
+ "version": "0.0.1",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "compile": "tsc",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "GPL-3.0",
+ "devDependencies": {
+ "@types/node": "^18.8.1",
+ "@types/prettier": "^2.7.1",
+ "prettier": "^2.7.1",
+ "typescript": "^4.8.4"
+ }
+}
diff --git a/extract-tsdefs/pnpm-lock.yaml b/extract-tsdefs/pnpm-lock.yaml
new file mode 100644
index 00000000..9de1119e
--- /dev/null
+++ b/extract-tsdefs/pnpm-lock.yaml
@@ -0,0 +1,35 @@
+lockfileVersion: 5.4
+
+specifiers:
+ '@types/node': ^18.8.1
+ '@types/prettier': ^2.7.1
+ prettier: ^2.7.1
+ typescript: ^4.8.4
+
+devDependencies:
+ '@types/node': 18.8.1
+ '@types/prettier': 2.7.1
+ prettier: 2.7.1
+ typescript: 4.8.4
+
+packages:
+
+ /@types/node/18.8.1:
+ resolution: {integrity: sha512-vuYaNuEIbOYLTLUAJh50ezEbvxrD43iby+lpUA2aa148Nh5kX/AVO/9m1Ahmbux2iU5uxJTNF9g2Y+31uml7RQ==}
+ dev: true
+
+ /@types/prettier/2.7.1:
+ resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==}
+ dev: true
+
+ /prettier/2.7.1:
+ resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ dev: true
+
+ /typescript/4.8.4:
+ resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+ dev: true
diff --git a/extract-tsdefs/tsconfig.json b/extract-tsdefs/tsconfig.json
new file mode 100644
index 00000000..83ee9e28
--- /dev/null
+++ b/extract-tsdefs/tsconfig.json
@@ -0,0 +1,103 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "resolveJsonModule": true, /* Enable importing .json files. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./dist", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
diff --git a/frags/common-conf-syntax.rst b/frags/common-conf-syntax.rst
new file mode 100644
index 00000000..3d837174
--- /dev/null
+++ b/frags/common-conf-syntax.rst
@@ -0,0 +1,32 @@
+The configuration file is line-oriented.
+Blank lines and whitespace at the beginning and end of a line are ignored.
+Comments start with ``#`` or ``%`` in the first column
+(after any beginning-of-line whitespace) and go to the end of the line.
+
+The file is split into sections.
+Every section begins with “[SECTIONNAME]” and
+contains a number of options of the form “OPTION=VALUE”.
+There may be whitespace around the ``=`` (equal sign).
+Section names and options are *case-insensitive*.
+
+The values, however, are *case-sensitive*.
+In particular, boolean values are one of ``YES`` or ``NO``.
+Values can include whitespace by surrounding
+the entire value with ``"`` (double quote).
+Note, however, that there are no escape characters in such strings;
+all characters between the double quotes (including other double quotes)
+are taken verbatim.
+
+Values that represent filenames can begin with a ``/bin/sh``-like
+variable reference.
+This can be simple, such as ``$TMPDIR/foo``, or complex,
+such as ``${TMPDIR:-${TMP:-/tmp}}/foo``.
+See ``[PATHS]`` (below).
+
+Values that represent a time duration are represented as a series
+of one or more ``NUMBER UNIT`` pairs, e.g. ``60 s``, ``4 weeks 1 day``,
+``5 years 2 minutes``.
+
+Values that represent an amount are in the usual amount syntax:
+``CURRENCY:VALUE.FRACTION``, e.g. ``EUR:1.50``.
+The ``FRACTION`` portion may extend up to 8 places.
diff --git a/frags/install-before-check.rst b/frags/install-before-check.rst
new file mode 100644
index 00000000..2476a383
--- /dev/null
+++ b/frags/install-before-check.rst
@@ -0,0 +1,10 @@
+Please note that unlike most packages, if you want to run the ``make check``
+command, you should run it only *after* having done ``make install``. The
+latter ensures that necessary binaries are copied to the right place.
+
+Gratuitous editorial note by TTN: I think this is a quirk that we should
+fix in the long-term as such weirdness might hide other build issues.
+However, this is probably a minority viewpoint.
+
+In any case, if ``make check`` fails, please consider filing a
+bug report with the Taler `bug tracker <https://bugs.taler.net>`__.
diff --git a/frags/installing-trisquel.rst b/frags/installing-trisquel.rst
new file mode 100644
index 00000000..4ca8bcc5
--- /dev/null
+++ b/frags/installing-trisquel.rst
@@ -0,0 +1,4 @@
+To install the GNU Taler Trisquel packages, first ensure that you have
+the right Trisquel distribution. Packages are currently available for
+Trisquel GNU/Linux 10.0. Simply follow the same instructions provided
+for Ubuntu 20.04 LTS (Focal Fossa).
diff --git a/frags/installing-ubuntu.rst b/frags/installing-ubuntu.rst
index 12831043..458d0b3c 100644
--- a/frags/installing-ubuntu.rst
+++ b/frags/installing-ubuntu.rst
@@ -1,13 +1,13 @@
To install the GNU Taler Ubuntu packages, first ensure that you have
the right Ubuntu distribution. At this time, the packages are built for
-Ubuntu 20.04 LTS (Focal Fossa).
+Ubuntu 22.04 LTS (Jammy Jellyfish).
A typical ``/etc/apt/sources.list.d/taler.list`` file for this setup
would look like this:
.. code-block::
- deb https://deb.taler.net/apt/ubuntu/ focal-fossa main
+ deb https://deb.taler.net/apt/ubuntu/ jammy main
The last line is crucial, as it adds the GNU Taler packages.
@@ -16,7 +16,8 @@ into your keyring and update the package lists:
.. code-block:: console
- # wget -O - https://taler.net/taler-systems.gpg.key | apt-key add -
+ # wget -O /etc/apt/trusted.gpg.d/taler-systems.asc \
+ https://taler.net/taler-systems.gpg.key
# apt update
.. note::
diff --git a/frags/list-of-dependencies.rst b/frags/list-of-dependencies.rst
index f0a183f2..4184e3c2 100644
--- a/frags/list-of-dependencies.rst
+++ b/frags/list-of-dependencies.rst
@@ -1,3 +1,7 @@
+- "Sphinx RTD Theme" Python package aka ``python3-sphinx-rtd-theme``
+ on Debian-based systems (for GNUnet documentation support, can be
+ omitted if GNUnet is configured with ``--disable-documentation``)
+
- libsqlite3 >= 3.16.2
- GNU libunistring >= 0.9.3
@@ -14,8 +18,8 @@
- libjansson >= 2.7
-- Postgres >= 9.6, including libpq
+- PostgreSQL >= 13, including libpq
- GNU libmicrohttpd >= 0.9.71
-- GNUnet >= 0.14.0 (from `source tarball <http://ftpmirror.gnu.org/gnunet/>`__)
+- GNUnet >= 0.16 (from `source tarball <http://ftpmirror.gnu.org/gnunet/>`__)
diff --git a/frags/using-taler-config.rst b/frags/using-taler-config.rst
index c15df84b..9e6df14a 100644
--- a/frags/using-taler-config.rst
+++ b/frags/using-taler-config.rst
@@ -39,10 +39,8 @@ compare:
.. code-block:: console
- $ taler-config -s ACCOUNT-bank \
- -o WIRE_RESPONSE
- $ taler-config -f -s ACCOUNT-bank \
- -o WIRE_RESPONSE
+ $ taler-config --section exchange-offline --option MASTER_PRIV_FILE
+ $ taler-config -f --section exchange-offline --option MASTER_PRIV_FILE
While the configuration file is typically located at
``$HOME/.config/taler.conf``, an alternative location can be specified
diff --git a/index.rst b/index.rst
index c16a9125..3d3b305a 100644
--- a/index.rst
+++ b/index.rst
@@ -63,11 +63,10 @@ Documentation Overview
taler-nfc-guide.rst
taler-merchant-api-tutorial
taler-merchant-pos-terminal
- taler-bank-manual
- taler-backoffice-manual
taler-auditor-manual
taler-developer-manual
taler-wallet
+ wallet/wallet-core
design-documents/index
libeufin/index
global-licensing
diff --git a/libeufin/api-nexus.rst b/libeufin/api-nexus.rst
index 5f3e7a6c..b67a8ba2 100644
--- a/libeufin/api-nexus.rst
+++ b/libeufin/api-nexus.rst
@@ -204,7 +204,7 @@ Test API
**Response**
- The successful case should respond with a ``200 OK`` and a empty JSON body.
+ The successful case should respond with a ``200 OK`` and an empty JSON body.
Bank Accounts
@@ -238,17 +238,19 @@ manages payment initiations of the account and tracks the initiations of payment
Get basic information about the bank account named ``my-acct``.
- interface BankAccountInfoWithBalance {
- // ID number of the database row being the default bank connection
- // of `my-acct`.
- defaultBankConnection: number;
- // Payto://-URI of `my-acct`.
- accountPaytoUri: string;
- // Balance of `my-acct` as it was downloaded from the bank
- // along the last Camt document. A human-readable message
- // will inform the requester, should this value not be found.
- lastSeenBalance: string;
- }
+ .. ts:def:: BankAccountInfoWithBalance
+
+ interface BankAccountInfoWithBalance {
+ // ID number of the database row being the default bank connection
+ // of ``my-acct``.
+ defaultBankConnection: number;
+ // Payto://-URI of ``my-acct``.
+ accountPaytoUri: string;
+ // Balance of ``my-acct`` as it was downloaded from the bank
+ // along the last Camt document. A human-readable message
+ // will inform the requester, should this value not be found.
+ lastSeenBalance: string;
+ }
.. http:post:: {nexusBase}/bank-accounts/{acctid}/payment-initiations/{pmtid}/submit
diff --git a/libeufin/api-sandbox-future.rst b/libeufin/api-sandbox-future.rst
new file mode 100644
index 00000000..06dfc682
--- /dev/null
+++ b/libeufin/api-sandbox-future.rst
@@ -0,0 +1,135 @@
+Future Sandbox API
+##################
+
+Resources.
+^^^^^^^^^^
+
+Sandbox serves the following resources:
+
+ - Demobanks.
+ - Bank accounts.
+ - Subscribers.
+ - Transactions.
+ - Customers.
+ - Reports.
+
+The resources are subject to all CRUD operations, via by two
+types of users: the 'admin' and the customers. The admin has
+rights on all of them.
+
+One of the main differences with the previous versions is the
+removal of the "/admin" initial component. If the administrator
+authenticates for one operation, then this one is of type ``admin``:
+no need for a dedicated and extra URI path component.
+
+For example, mocking transactions in the system was a typical
+/admin-operation, but since transactions themselves are resources
+and any resource is subject to CRUD operations, then mocking one
+becomes just a ``C`` operation on the 'transactions' resources. If
+a test case wants to simplify the authentication - by hard-coding
+the credentials, for example - before mocking one transaction, then
+it can impersonate the administrator.
+
+.. note::
+
+ ``POST``\ ing to an endpoint with a trailing ``$id`` means
+ modification of an existing resource.
+
+Demobanks
+^^^^^^^^^
+
+Demobanks are the main containers of all the other resources.
+They take configuration values, like the currency for example.
+
+.. http:get:: /admin/demobanks
+.. http:get:: /admin/demobanks/$id
+.. http:post:: /admin/demobanks
+.. http:post:: /admin/demobanks/$id
+.. http:delete:: /admin/demobanks/$id
+
+Bank accounts.
+^^^^^^^^^^^^^^
+
+Bank accounts collect money of customers and participate
+in transactions.
+
+The ``$base`` is one demobank, in the form ``/demobanks/$id``.
+That is due because a bank account must inherit some defaults
+from the demobank, like the currency.
+
+.. http:get:: $base/bankaccounts
+.. http:get:: $base/bankaccounts/$id
+.. http:post:: $base/bankaccounts
+.. http:post:: $base/bankaccounts/$id
+.. http:delete:: $base/bankaccounts/$id
+
+Subscribers.
+^^^^^^^^^^^^
+
+Subscribers are (optional) customers identities for protocols
+other than the native one.
+
+A subscriber is not required to have a bank account "soon". Therefore,
+it can be created, and later be linked to one bank account. For this
+reason, the ``$base`` should not mention one bank account.
+
+.. note::
+
+ Creating a subscriber is a time-consuming operation that goes often
+ through slow channels, therefore it should basically never be done
+ more than once.
+
+.. http:get:: $base/subscribers
+.. http:get:: $base/subscribers/$id
+.. http:post:: $base/subscribers
+.. http:post:: $base/subscribers/$id
+.. http:delete:: $base/subscribers/$id
+
+Transactions.
+^^^^^^^^^^^^^
+
+Transactions are money movements between bank accounts.
+
+For convenience, ``$base`` **could** mention one bank account
+to let customers see their transactions without defining "history"
+query parameters: like ``$demobank/bankaccounts/$bankaccountId/transactions``.
+
+At the same time, transactions should be easy to query regardless
+of whose bank account they involve -- for administrative reasons, for
+example. Hence, a "global" URI scheme like ``$demobank/transactions?param=XXX``
+should be offered as well.
+
+.. http:get:: $base/transactions
+.. http:get:: $base/transactions/$id
+.. http:post:: $base/transactions
+.. http:post:: $base/transactions/$id
+.. http:delete:: $base/transactions/$id
+
+Customers.
+^^^^^^^^^^
+
+Customers are persons that **can** have one bank account. As
+with subscribers, ``$base`` should not mention any bank account
+so as to leave more flexibility to assign new bank accounts to
+the same customer.
+
+.. http:get:: $base/customers
+.. http:get:: $base/customers/$id
+.. http:post:: $base/customers
+.. http:post:: $base/customers/$id
+.. http:delete:: $base/customers/$id
+
+Reports.
+^^^^^^^^
+
+Reports are persistent documents witnessing transactions.
+A typical example is a Camt.053 statement. A report lives
+even after a bank account gets deleted or a customer leaves
+the bank, therefore ``$base`` should only mention a demobank
+instance.
+
+.. http:get:: $base/reports
+.. http:get:: $base/reports/$id
+.. http:post:: $base/reports
+.. http:post:: $base/reports/$id
+.. http:delete:: $base/reports/$id
diff --git a/libeufin/api-sandbox.rst b/libeufin/api-sandbox.rst
index b75fcf8d..afbc3513 100644
--- a/libeufin/api-sandbox.rst
+++ b/libeufin/api-sandbox.rst
@@ -2,99 +2,530 @@
.. _sandbox-api:
-Current Sandbox API
-###################
+Sandbox API
+###########
-..
- Current Sandbox endpoints.
+Sandbox emulates a minimal bank, to provide EBICS access to test
+`Taler <https://taler.net>`_. The top-level API defines two main
+groups: `demobanks <Demobanks_>`_ and `legacy <Legacy API_>`_.
+Currently, `error types <error-types_>`_ are common to both groups.
- GET /
- Welcome message.
+Demobanks
+=========
+
+Sandbox is designed to allow multiple *demobanks* being hosted,
+where every demobank can have its own configuration (including
+a different currency). A demobank has a name, although currently
+only one demobank, named ``default``, is supported. Such demobank
+activates the API segment ``/demobanks/default``, under which several
+APIs are then served. The following sections describe all such APIs.
+
+Circuit API
+^^^^^^^^^^^
+
+This API offers to manage a selected group of users who act as businesses
+for a local currency. Policies to disincentivize cashout operations may
+also apply, making therefore this setup a *circuit* within a wider traditional
+currency.
+
+For brevity, the list of response statuses for each endpoint may not be exhaustive.
+
+.. note::
+ This API requires to **disable** ordinary registrations in the
+ configuration, to avoid other APIs from circumventing this registration
+ policy. See ``libeufin-sandbox config --help``.
+
+The following endpoints are served under ``/demobanks/default/circuit-api``.
+
+Accounts
+--------
+
+.. http:post:: /accounts
+
+ Create a new bank account. Only the administrator is allowed.
+
+ **Request:**
+
+ .. ts:def:: CircuitAccountRequest
+
+ interface CircuitAccountRequest {
+ // Username
+ username: string;
+
+ // Password.
+ password: string;
+
+ // Addresses where to send the TAN. If
+ // this field is missing, then the cashout
+ // won't succeed.
+ contact_data: CircuitContactData;
+
+ // Legal subject owning the account.
+ name: string;
+
+ // 'payto' address pointing the bank account
+ // where to send payments, in case the user
+ // wants to convert the local currency back
+ // to fiat.
+ cashout_address: string;
+
+ // IBAN of this bank account, which is therefore
+ // internal to the circuit. Randomly generated,
+ // when it is not given.
+ internal_iban?: string;
+ }
+
+ .. ts:def:: CircuitContactData
+
+ interface CircuitContactData {
+
+ // E-Mail address
+ email?: string;
+
+ // Phone number.
+ phone?: string;
+ }
+
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The account was successfully created.
+ :http:statuscode:`400 Bad request`:
+ Input data was invalid. For example, the client specified a invalid
+ phone number or e-mail address.
+ :http:statuscode:`403 Forbidden`:
+ The response should indicate one of the following reasons.
+
+ * A institutional username was attempted, like ``admin`` or ``bank``.
+ * A non admin user tried the operation.
+
+ :http:statuscode:`409 Conflict`:
+ At least one registration detail was not available,
+ the error message should inform about it.
+
+.. http:delete:: /accounts/$username
+
+ Delete the account whose username is ``$username``. The deletion
+ succeeds only if the balance is *zero*. Only the administrator is
+ allowed.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The account was successfully deleted.
+ :http:statuscode:`403 Forbidden`:
+ The administrator specified a institutional username, like
+ ``admin`` or ``bank``.
+ :http:statuscode:`404 Not found`:
+ The username was not found.
+ :http:statuscode:`412 Precondition failed`:
+ The balance was not zero.
+
+
+.. http:patch:: /accounts/$username
+
+ Allows the administrator and the user to reconfigure
+ the account data of ``$username``.
+
+ **Request:**
+
+ .. ts:def:: CircuitAccountReconfiguration
+
+ interface CircuitAccountReconfiguration {
+
+ // Addresses where to send the TAN.
+ contact_data: CircuitContactData;
+
+ // 'payto' address pointing the bank account
+ // where to send payments, in case the user
+ // wants to convert the local currency back
+ // to fiat.
+ cashout_address: string;
+ }
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ Operation successful.
+
+ :http:statuscode:`404 Not found`:
+ The account pointed by ``$username``
+ was not found.
+
+.. http:patch:: /accounts/$username/auth
+
+ Allows administrators *and* ordinary users to
+ change the account's password.
+
+ **Request:**
+
+ .. ts:def:: AccountPasswordChange
+
+ interface AccountPasswordChange {
+
+ // New password.
+ new_password: string;
+ }
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ Operation successful.
+
+.. http:get:: /accounts
+
+ Obtains a list of all the accounts registered at the bank.
+ It returns only the information that this API handles, without
+ any balance or transactions list. The :doc:`Access API </core/api-bank-access>`
+ offers that. This request is only available to the administrator.
+
+ **Response:**
+
+ `CircuitAccounts <circuit-accounts_>`_
+
+ .. _circuit-accounts:
+
+ .. ts:def:: CircuitAccounts
+
+ interfaces CircuitAccounts {
+ customers: CircuitAccountMinimalData[];
+ }
+
+ .. ts:def:: CircuitAccountMinimalData
+
+ interface CircuitAccountMinimalData {
+ // Username
+ username: string;
+
+ // Legal subject owning the account.
+ name: string;
- ** Camt debug **
+ }
- POST /admin/payments/camt
- give last statement of requesting account
+.. http:get:: /accounts/$username
- ** Bank accounting **
+ Obtains information relative to the account owned by
+ ``$username``. The request is available to the administrator
+ and ``$username`` itself.
- POST /admin/bank-accounts/$accountLabel
- create bank account (no EBICS involved)
+ **Response:**
- GET /admin/bank-accounts
- Give summary of all the bank accounts.
+ .. ts:def:: CircuitAccountData
+
+ interface CircuitAccountData {
+ // Username
+ username: string;
- GET /admin/bank-accounts/$accountLabel
- give general information about a bank account
+ // IBAN hosted at Libeufin Sandbox
+ iban: string;
- ** Transactions **
+ contact_data: CircuitContactData;
- POST /admin/bank-accounts/$accountLabel/simulate-incoming-transaction
- Book one incoming transaction for $accountLabel.
- The debtor (not required to be in the same bank)
- information is taken from the request.
+ // Legal subject owning the account.
+ name: string;
- GET /admin/bank-accounts/$accountLabel/transactions
- Inform about all the transactions of one bank account.
+ // 'payto' address pointing the bank account
+ // where to send cashouts.
+ cashout_address: string;
+ }
- POST /admin/bank-accounts/$accountLabel/generate-transactions
- Generate one incoming and one outgoing transaction
- for one bank account.
+ :http:statuscode:`403 Forbidden`:
+ The user is not allowed.
- ** EBICS subscribers management **
+Cashouts
+--------
- POST /admin/ebics/subscribers
- Create a new EBICS subscriber.
+.. http:post:: /cashouts
- GET /admin/ebics/subscribers
- Give details of all the EBICS subscribers in the bank.
+ Initiates a conversion to fiat currency. The target account
+ is the one specified at registration time. The account to be
+ debited is extracted from the authentication credentials.
+ The bank sends a TAN to the customer to let them confirm the
+ operation. The request is only available to ordinary users, not
+ to the administrator.
- POST /admin/ebics/bank-accounts
- Create a *new* bank account and link it with a existing
- EBICS subscriber.
+ .. note::
+
+ Consult the `cashout rates call <cashout-rates_>`_ to learn
+ about any applicable fee or exchange rate.
- ** EBICS host management **
+ **Request:**
- POST /admin/ebics/hosts/$hostID/rotate-keys
- Change the bank's keys used in EBICS communication.
+ `CashoutRequest <cashout-request_>`_
- POST /admin/ebics/hosts
- Create a new EBICS host.
+ .. ts:def:: TanChannel
- GET /admin/ebics/hosts
- Show details of all the EBICS hosts in the bank.
+ enum TanChannel {
+ SMS = "sms",
+ EMAIL = "email"
+ }
- ** EBICS serving **
+ .. _cashout-request:
- POST /ebicsweb
- Processes a EBICS request.
+ .. ts:def:: CashoutRequest
- ** Taler .. **
+ interface CashoutRequest {
- GET /taler
- Show one taler://-URI to initiate a withdrawal.
+ // Optional subject to associate to the
+ // cashout operation. This data will appear
+ // as the incoming wire transfer subject in
+ // the user's external bank account.
+ subject?: string;
- - Taler integration API.
+ // That is the plain amount that the user specified
+ // to cashout. Its $currency is the circuit currency.
+ amount_debit: Amount;
+
+ // That is the amount that will effectively be
+ // transferred by the bank to the user's bank
+ // account, that is external to the circuit.
+ // It is expressed in the fiat currency and
+ // is calculated after the cashout fee and the
+ // exchange rate. See the /cashout-rates call.
+ amount_credit: Amount;
+
+ // Which channel the TAN should be sent to. If
+ // this field is missing, it defaults to SMS.
+ // The default choice prefers to change the communication
+ // channel respect to the one used to issue this request.
+ tan_channel?: TanChannel;
+ }
+
+ **Response:**
+
+ .. ts:def:: CashoutPending
+
+ interface CashoutPending {
+ // UUID identifying the operation being created
+ // and now waiting for the TAN confirmation.
+ uuid: string;
+ }
+
+ :http:statuscode:`202 Accepted`:
+ The cashout request was correctly created and
+ the TAN authentication now is pending.
+ :http:statuscode:`400 Bad request`:
+ The exchange rate was incorrectly applied.
+ :http:statuscode:`403 Forbidden`:
+ A institutional user (``admin`` or ``bank``) tried the operation.
+ :http:statuscode:`409 Conflict`:
+ The user did not share any contact data where to send the TAN.
+ :http:statuscode:`412 Precondition failed`:
+ The account does not have sufficient funds.
+ :http:statuscode:`503 Service unavailable`:
+ The bank does not support the TAN channel for this operation.
+
+.. http:post:: /cashouts/$cashoutId/abort
+
+ Aborts the ``$cashoutId`` operation. Original author
+ *and* admin are both allowed.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ ``$cashoutId`` was found in the *pending* state
+ and got successfully aborted.
+ :http:statuscode:`404 Not found`:
+ ``$cashoutId`` is not found. Note: that happens
+ also when ``$cashoutId`` got aborted before this request.
+ :http:statuscode:`412 Precondition failed`:
+ ``$cashoutId`` was already confirmed.
+
+.. http:post:: /cashouts/$cashoutId/confirm
+
+ Confirms the ``$cashoutId`` operation by accepting its
+ TAN. The request should still be authenticated with
+ the users credentials. Only the original author is allowed.
+
+ **Request:**
- - Demobank configuration API.
- ToDo.
+ .. ts:def:: CashoutConfirm
+
+ interface CashoutConfirm {
- - Taler access API.
- ToDo.
+ // the TAN that confirms $cashoutId.
+ tan: string;
+ }
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ ``$cashoutId`` was found in the *pending* state and
+ got successfully confirmed.
+ :http:statuscode:`403 Forbidden`:
+ wrong TAN.
+ :http:statuscode:`404 Not found`:
+ ``$cashoutId`` is not found. Note: that happens
+ also when ``$cashoutId`` got aborted before this request.
+ :http:statuscode:`409 Conflict`:
+ A institutional user (``admin`` or ``bank``) tried the operation.
+ :http:statuscode:`412 Precondition failed`:
+ ``$cashoutId`` was already confirmed.
+
+.. _cashout-rates:
+
+.. http:get:: /config
+
+ **Response:**
+
+ .. ts:def:: Config
+
+ interface Config {
+ // Name of this API, always "circuit".
+ name: string;
+ // API version in the form $n:$n:$n
+ version: string;
+ // Contains ratios and fees related to buying
+ // and selling the circuit currency.
+ ratios_and_fees: RatiosAndFees;
+ }
+
+ .. ts:def:: RatiosAndFees
+
+ interface RatiosAndFees {
+ // Exchange rate to buy the circuit currency from fiat.
+ buy_at_ratio: float;
+ // Exchange rate to sell the circuit currency for fiat.
+ sell_at_ratio: float;
+ // Fee to subtract after applying the buy ratio.
+ buy_in_fee: float;
+ // Fee to subtract after applying the sell ratio.
+ sell_out_fee: float;
+ }
+
+
+ Example. Given a circuit currency CC, a fiat currency FC,
+ a *sell_at_ratio* = 0.9 and *sell_out_fee* = 0.03, selling
+ 10 CC would result in the following FC: (10 * 0.9) - 0.03
+ = 8.97 FC. On the other hand, given *buy_at_ratio* = 1.1
+ and *buy_in_fee* = 0.01, a user wanting to spend 10 FC to
+ buy the CC would result in the following CC: (10 * 1.1) -
+ 0.01 = 10.99 CC.
+
+ .. note::
+
+ the terms 'sell out' and 'cashout' may be used interchangeably.
+
+.. http:get:: /cashouts/$cashoutId
+
+ Informs about the status of the ``$cashoutId`` operation.
+ The request is available to the administrator and the original author.
+
+ **Response:**
+
+ `CashoutStatusResponse <cashout-status_>`_
+
+ .. _cashout-status:
+
+ .. ts:def:: CashoutStatus
+
+ interface CashoutStatusResponse {
+
+ status: CashoutStatus;
+ }
+
+ .. ts:def:: CashoutStatus
+
+ enum CashoutStatus {
+
+ // The payment was initiated after a valid
+ // TAN was received by the bank.
+ CONFIRMED = "confirmed",
+
+ // The cashout was created and now waits
+ // for the TAN by the author.
+ PENDING = "pending",
+ }
+
+ **Response:**
+
+ :http:statuscode:`404 Not found`:
+ The cashout operation was not found. That is
+ *also* the case of ``$cashoutId`` being an aborted
+ operation.
+
+Access API
+^^^^^^^^^^
-EBICS.
-^^^^^^
+Every endpoint is served under ``/demobanks/default/access-api``.
+See :doc:`/core/api-bank-access`. This API allows users to access
+their bank accounts and trigger Taler withdrawals.
-Hosts.
-++++++
+Integration API
+^^^^^^^^^^^^^^^
+
+Every endpoint is served under ``/demobanks/default/integration-api``.
+See :doc:`/core/api-bank-integration`. This API handles the communication
+with Taler wallets.
+
+Taler Wire Gateway API
+^^^^^^^^^^^^^^^^^^^^^^
+
+Served under ``/demobanks/default/taler-wire-gateway``. Currently,
+only the :ref:`admin/add-incoming <twg-admin-add-incoming>` endpoint
+is implemented. This endpoint allows testing, but the rest of
+this API does never involve the Sandbox.
+
+EBICS API
+^^^^^^^^^
+
+.. _demobank-create-ebics-subscriber:
+
+.. http:post:: /demobanks/default/ebics/subscribers
+
+ Allows (only) the *admin* user to associate a bank account
+ to a EBICS subscriber. If the latter does not exist, it is
+ created.
+
+ **Request:**
+
+ .. ts:def:: SubscriberRequest
+
+ interface SubscriberRequest {
+
+ // hostID
+ hostID: string;
+
+ // userID
+ userID: string;
+
+ // partnerID
+ partnerID: string;
+
+ // systemID, optional.
+ systemID: string;
+
+ // Label of the bank account to associate with
+ // this subscriber.
+ demobankAccountLabel: string;
+ }
+
+.. note::
+
+ The following endpoints are **not** served under the ``/demobank/default`` segment.
+
+Legacy API
+==========
+
+This was the first API offered by Sandbox. It is used in
+some test cases. One is hosted at the Wallet repository; other
+MAY as well exist.
+
+Except of the main EBICS handler located at "/ebicsweb", all
+the EBICS calls have to authenticate the 'admin' user via
+the HTTP basic auth scheme.
+
+EBICS Hosts
+^^^^^^^^^^^
.. http:post:: /admin/ebics/hosts
- Creates a new Ebics host.
+ Create a new EBICS host.
**Request:**
- .. ts:def:: EbicsHostRequest
+ .. ts:def:: EbicsHostRequest
interface EbicsHostRequest {
@@ -112,7 +543,7 @@ Hosts.
**Response:**
- .. ts:def:: EbicsHostResponse
+ .. ts:def:: EbicsHostResponse
interface EbicsHostResponse {
@@ -128,9 +559,8 @@ Hosts.
meant for tests (as the Sandbox itself is) and no backup will be
produced along this operation.
-
-Subscribers.
-++++++++++++
+EBICS Subscribers
+^^^^^^^^^^^^^^^^^
.. http:post:: /admin/ebics/bank-accounts
@@ -138,12 +568,16 @@ Subscribers.
**Request:**
- .. ts:def:: BankAccountRequest
+ .. ts:def:: BankAccountRequest
- interface BankAccountRequest {
+ interface BankAccountRequest {
// Ebics subscriber
- subscriber: string;
+ subscriber: {
+ userID: string;
+ partnerID: string;
+ systemID: string;
+ };
// IBAN
iban: string;
@@ -156,19 +590,51 @@ Subscribers.
// bank account label
label: string;
+ }
+
+.. http:get:: /admin/ebics/subscribers
+ Shows the list of all the subscribers in the system.
+
+ **Response:**
+
+ .. ts:def:: SubscribersResponse
+
+ interface SubscribersResponse {
+
+ subscribers: Subscriber[]
}
+ .. ts:def:: Subscriber
+
+ interface Subscriber {
+
+ // userID
+ userID: string;
+
+ // partnerID
+ partnerID: string;
+
+ // hostID
+ hostID: string;
+
+ // Label of the bank account
+ // associated with this Ebics subscriber.
+ demobankAccountLabel: string;
+ }
.. http:post:: /admin/ebics/subscribers
- Creates a new Ebics subscriber.
+ Create a new EBICS subscriber without associating
+ a bank account to it. This call is **deprecated**.
+ Follow `this page <https://bugs.gnunet.org/view.php?id=7507>`_
+ for updates over the EBICS management REST design.
**Request:**
- .. ts:def:: SubscriberRequest
+ .. ts:def:: SubscriberRequestDeprecated
- interface SubscriberRequest {
+ interface SubscriberRequestDeprecated {
// hostID
hostID: string;
@@ -179,239 +645,238 @@ Subscribers.
// partnerID
partnerID: string;
- // systemID
+ // systemID, optional.
systemID: string;
}
+Bank accounts
+^^^^^^^^^^^^^
-.. http:get:: /admin/ebics/subscribers
-
- Shows the list of all the subscribers in the system.
-
- **Response:**
+The access to a particular bank account is granted either to the
+owner or to admin, via the HTTP basic auth scheme. A 'owner' is
+a registered customer, who is identified by a username. The
+registration of customers is offered via the :doc:`/core/api-bank-access`.
- .. ts:def:: SubscribersResponse
+.. note::
- interface SubscribersResponse {
+ The current version allows only one bank account per
+ customer, where the bank account name (also called 'label')
+ equals the owner's username.
- subscribers: Subscriber[]
- }
-
- .. ts:def:: Subscriber
+.. http:get:: /admin/bank-accounts
- interface Subscriber {
+ Give summary of all the bank accounts. Only admin allowed.
- // userID
- userID: string;
+ **Response:**
- // partnerID
- partnerID: string;
+ .. ts:def:: AdminBankAccount
- // hostID
- hostID: string;
+ interface AdminBankAccount {
- }
+ // IBAN
+ iban: string;
+ // BIC
+ bic: string;
-Bank accounts.
-^^^^^^^^^^^^^^
+ // human name
+ name: string;
-.. http:get:: /admin/bank-accounts
+ // bank account label
+ label: string;
+ }
- Give summary of all the bank accounts.
.. http:get:: /admin/bank-accounts/$accountLabel
Give information about a bank account.
+ **Response:**
-Main EBICS service.
-^^^^^^^^^^^^^^^^^^^
+ .. ts:def:: AdminBankAccountBalance
-.. http:post:: /ebicsweb
+ interface AdminBankAccountBalance {
+ // Balance in the $currency:$amount format.
+ balance: string;
+ // IBAN of the bank account identified by $accountLabel
+ iban: string;
+ // BIC of the bank account identified by $accountLabel
+ bic: string;
+ // Mentions $accountLabel
+ label: string;
+ }
- Serves all the Ebics requests.
+.. http:post:: /admin/bank-accounts/$accountLabel
+ Create bank account. Existing users without a bank account
+ can request too.
-Transactions.
-^^^^^^^^^^^^^
+ **Request:** :ts:type:`AdminBankAccount`
-JSON.
-+++++
+Transactions
+^^^^^^^^^^^^
.. http:get:: /admin/bank-accounts/$accountLabel/transactions
Inform about all the transactions of one bank account.
-.. http:post:: /admin/bank-accounts/$accountLabel/generate-transactions
+ **Response:**
+
+ .. ts:def:: AdminTransactions
+
+ interface AdminTransactions {
+ payments: AdminTransaction[];
+ }
- Generate one incoming and one outgoing transaction for one bank account.
+ .. ts:def:: AdminTransaction
+
+ interface AdminTransaction {
-.. http:post:: /admin/bank-accounts/$accountLabel/simulate-incoming-transaction
+ // Label of the bank account involved in this payment.
+ accountLabel: string;
- Book one incoming transaction for $accountLabel.
- The debtor (not required to be in the same bank)
- information is taken from the request.
+ // Creditor IBAN
+ creditorIban: string;
+ // Debtor IBAN
+ debtorIban: string;
-Camt.
-+++++
+ // UID given by one financial institute to this payment.
+ // FIXME: clarify whether that can be also assigned by
+ // the other party's institution.
+ accountServicerReference: string;
-.. http:post:: /admin/payments/camt
+ // ID of the Pain.001 that initiated this payment.
+ paymentInformationId: string;
- Return the last statement of the requesting account.
+ // Unstructured remittance information.
+ subject: string;
- **Request**
+ // Date of the payment in the HTTP header format.
+ date: string;
+
+ // The number amount as a string.
+ amount: string;
+
+ // BIC of the creditor IBAN.
+ creditorBic: string;
- .. code-block:: tsref
+ // Legal name of the creditor.
+ creditorName: string;
- interface CamtParams {
+ // BIC of the debtor IBAN.
+ debtorBic: string;
- // label of the bank account being queried.
- bankaccount: string;
+ // Legal name of the debtor.
+ debtorName: string;
- // The Camt type to return. Only '53' is allowed
- // at this moment.
- type: number;
+ // Payment's currency
+ currency: string;
+
+ // Have values 'credit' or 'debit' relative
+ // to the requesting user.
+ creditDebitIndicator: string;
}
- **Response**
+.. http:post:: /admin/bank-accounts/$accountLabel/generate-transactions
+
+ Generate one incoming and one outgoing transaction for the bank account
+ identified by ``$accountLabel``. Only admin allowed.
+
+.. http:post:: /admin/bank-accounts/$accountLabel/simulate-incoming-transaction
+
+ Book one incoming transaction for $accountLabel.
+ The debtor (not required to be in the same bank)
+ information is taken from the request. Only admin allowed.
- The expected Camt.053 document.
+ **Request:**
+
+ .. ts:def:: AdminSimulateTransaction
-Future Sandbox API
-##################
+ interface AdminSimulateTransaction {
-Resources.
-----------
+ // Debtor IBAN.
+ debtorIban: string;
-Sandbox serves the following resources:
+ // Debtor BIC.
+ debtorBic: string;
- - Demobanks.
- - Bank accounts.
- - Subscribers.
- - Transactions.
- - Customers.
- - Reports.
-
-The resources are subject to all CRUD operations, via by two
-types of users: the 'admin' and the customers. The admin has
-rights on all of them.
+ // Debtor name.
+ debtorName: string;
-One of the main differences with the previous versions is the
-removal of the "/admin" initial component. If the administrator
-authenticates for one operation, then this one is of type `admin`:
-no need for a dedicate and extra URI part.
+ // Amount number (without currency) as a string.
+ amount: string;
-For example, mocking transactions in the system was a typical
-/admin-operation, but since transactions themselves are resources
-and any resource is subject to CRUD operations, then mocking one
-becomes just a `C` operation on the 'transactions' resources. If
-a test case wants to simplify the authentication - by hard-coding
-the credentials, for example - before mocking one transaction, then
-it can impersonate the administrator.
+ // Payment subject.
+ subject: string;
+ }
-.. note::
- ``POST``-ing to a endpoint with a trailing ``$id`` means
- modification of a existing resource.
+.. http:post:: /admin/payments/camt
-Demobanks
-^^^^^^^^^
+ Return the last camt.053 document from the requesting account.
-Demobanks are the main containers of all the other resources.
-They take configuration values, like the currency for example.
+ **Request**
-.. http:get:: /admin/demobanks
-.. http:get:: /admin/demobanks/$id
-.. http:post:: /admin/demobanks
-.. http:post:: /admin/demobanks/$id
-.. http:delete:: /admin/demobanks/$id
+ .. code-block:: tsref
-Bank accounts.
-^^^^^^^^^^^^^^
+ interface CamtParams {
-Bank accounts collect money of customers and participate
-in transactions.
+ // label of the bank account being queried.
+ bankaccount: string;
-The ``$base`` is one demobank, in the form ``/demobanks/$id``.
-That is due because a bank account must inherit some defaults
-from the demobank, like the currency.
+ // The Camt type to return. Only '53' is allowed
+ // at this moment.
+ type: number;
+ }
-.. http:get:: $base/bankaccounts
-.. http:get:: $base/bankaccounts/$id
-.. http:post:: $base/bankaccounts
-.. http:post:: $base/bankaccounts/$id
-.. http:delete:: $base/bankaccounts/$id
+ **Response**
-Subscribers.
-^^^^^^^^^^^^
+ The last Camt.053 document related to the bank account
+ mentioned in the request body.
-Subscribers are (optional) customers identities for protocols
-other than the native one.
-A subscriber is not required to have a bank account `soon`. Therefore,
-it can be created, and later be linked to one bank account. For this
-reason, the ``$base`` should not mention one bank account.
+.. _error-types:
-.. note::
+======
+Errors
+======
- Creating a subscriber is a time-consuming operation that goes often
- through slow channels, therefore it should basically never be done
- more than once.
+The JSON type coming along a non 2xx response is the following:
-.. http:get:: $base/subscribers
-.. http:get:: $base/subscribers/$id
-.. http:post:: $base/subscribers
-.. http:post:: $base/subscribers/$id
-.. http:delete:: $base/subscribers/$id
+.. ts:def:: SandboxError
-Transactions.
-^^^^^^^^^^^^^
+ interface SandboxError {
+ error: SandboxErrorDetail;
+ }
-Transactions are money movements between bank accounts.
+.. ts:def:: SandboxErrorDetail
-For convenience, ``$base`` **could** mention one bank account
-to let customers see their transactions without defining "history"
-query parameters: like ``$demobank/bankaccounts/$bankaccountId/transactions``.
+ interface SandboxErrorDetail {
-At the same time, transactions should be easy to query regardless
-of whose bank account they involve -- for administrative reasons, for
-example. Hence, a "global" URI scheme like ``$demobank/transactions?param=XXX``
-should be offered as well.
+ // String enum classifying the error.
+ type: ErrorType;
-.. http:get:: $base/transactions
-.. http:get:: $base/transactions/$id
-.. http:post:: $base/transactions
-.. http:post:: $base/transactions/$id
-.. http:delete:: $base/transactions/$id
+ // Human-readable error description.
+ description: string;
+ }
-Customers.
-^^^^^^^^^^
+.. ts:def:: ErrorType
+
+ enum ErrorType {
+ /**
+ * This error can be related to a business operation,
+ * a non-existent object requested by the client, or
+ * even when the bank itself fails.
+ */
+ SandboxError = "sandbox-error",
-Customers are persons that **can** have one bank account. As
-with subscribers, ``$base`` should not mention any bank account
-so as to leave more flexibility to assign new bank accounts to
-the same customer.
-
-.. http:get:: $base/customers
-.. http:get:: $base/customers/$id
-.. http:post:: $base/customers
-.. http:post:: $base/customers/$id
-.. http:delete:: $base/customers/$id
-
-Reports.
-^^^^^^^^
-
-Reports are persistent documents witnessing transactions.
-A typical example is a Camt.053 statement. A report lives
-even after a bank account gets deleted or a customer leaves
-the bank, therefore ``$base`` should only mention a demobank
-instance.
-
-.. http:get:: $base/reports
-.. http:get:: $base/reports/$id
-.. http:post:: $base/reports
-.. http:post:: $base/reports/$id
-.. http:delete:: $base/reports/$id
+ /**
+ * It is the error type thrown by helper functions
+ * from the Util library. Those are used by both
+ * Sandbox and Nexus, therefore the actual meaning
+ * must be carried by the error 'message' field.
+ */
+ UtilError = "util-error"
+ }
diff --git a/libeufin/demo-deployment-gv.rst b/libeufin/demo-deployment-gv.rst
deleted file mode 100644
index 49462579..00000000
--- a/libeufin/demo-deployment-gv.rst
+++ /dev/null
@@ -1,49 +0,0 @@
-.. target audience: operator, developer
-
-Deploying Taler with libEufin
-#############################
-
-.. contents:: Table of Contents
-
-This guide shows the steps to set up libEufin as the bank
-for Taler. At this stage, only three constant bank accounts
-are allowed, and the withdrawal can be triggered via the
-command line interface.
-
-This document is more a set of notes for internal use than
-a actual document for system administrators.
-
-Deployment on Gv
-----------------
-
-After having pulled the latest code of deployment.git:
-
-.. code-block:: console
-
- # The step below destroys data!
- $ rm -fr ~/taler-data/*
- # For the step below, the environment must be called 'int'.
- $ taler-deployment bootstrap
- $ taler-deployment build
- # The step below destroys data!
- $ taler-deployment-prepare-with-eufin resetDb
- $ taler-deployment-restart-with-eufin
-
-At this point, the file ~/libeufin_admin_password should exist
-and contain the password for the 'admin' user of the Sandbox. This
-user is *also* the one that can withdraw with Taler.
-
-Withdraw with Taler
--------------------
-
-.. code-block:: console
-
- $ http --auth admin:$password $hostname/eufin/sandbox/taler
-
-The previous step should have returned a taler://-URI that
-can be passed to the wallet:
-
-
-.. code-block:: console
-
- $ taler-wallet-cli handle-uri $taler_uri
diff --git a/libeufin/ebics.rst b/libeufin/ebics.rst
index 058804e1..7c1fa2ae 100644
--- a/libeufin/ebics.rst
+++ b/libeufin/ebics.rst
@@ -409,13 +409,14 @@ Formats
ISO 20022
---------
-ISO 20022 is XML-based and defines the message format for many finance-related activities.
+`ISO 20022 <https://www.iso20022.org/>`_
+is XML-based and defines the message format for many finance-related activities.
ISO 20022 messages are identified by a message identifier in the following format:
<business area> . <message identifier> . <variant> . <version>
-Some financial instututions (such as the Deutsche Kreditwirtschaft) may decided to use
+Some financial instututions (such as the Deutsche Kreditwirtschaft) have decided to use
a subset of elements/attributes in a message, this is what the ``<variant>`` part is for.
"Standard" ISO20022 have variant ``001``.
diff --git a/libeufin/index.rst b/libeufin/index.rst
index 0e184862..29cd7a0e 100644
--- a/libeufin/index.rst
+++ b/libeufin/index.rst
@@ -18,3 +18,4 @@ LibEuFin is a project providing free software tooling for European FinTech.
frontend
nexus-tutorial
demo-deployment-gv
+ int-deployment-gv
diff --git a/libeufin/int-deployment-gv.rst b/libeufin/int-deployment-gv.rst
new file mode 100644
index 00000000..b3a12935
--- /dev/null
+++ b/libeufin/int-deployment-gv.rst
@@ -0,0 +1,71 @@
+.. target audience: operator, developer
+
+Deploying Taler with libEufin
+#############################
+
+.. contents:: Table of Contents
+
+This document shows how to compile and run all
+the Taler services (including libEuFin).
+
+Deployment on Gv
+----------------
+
+After having pulled the latest code of deployment.git:
+
+.. code-block:: console
+
+ $ python3 deployment/bin/WIP/taler-local bootstrap
+ $ python3 deployment/bin/WIP/taler-local build
+ $ python3 deployment/bin/WIP/taler-local prepare
+
+The last step offers also a way to specify the database
+name -- only the PostgreSQL at this time -- and the hostname
+of the reverse proxy. See ``--help`` for more information.
+
+At this point, the services can be launched, and will be
+served at ``$hostname/$service``. The following command
+will launch all the services and show what ``$service``
+can be:
+
+.. code-block:: console
+
+ $ python3 deployment/bin/WIP/taler-local launch
+
+.. note::
+
+ In this context, one reverse proxy gets launched
+ along all the Taler services and will dispatch any request
+ to ``$service`` to the respective Unix domain socket.
+
+Withdraw with Taler
+-------------------
+
+The following command withdraws a fixed amount of 5 units of currency
+to the CLI wallet:
+
+.. code-block:: console
+
+ $ python3 deployment/bin/WIP/taler-local withdraw
+
+The requests will be addressed to the default Sandbox, unless
+``--bank-host`` (and ``--bank-proto``) were given; see ``--help``
+for more information.
+
+.. note::
+
+ To test phone wallets, the option ``--with-qr-code``
+ can be specified. That will print a QR code on screen, and
+ wait for the user's input before confirming the payment to
+ the Exchange.
+
+Taler Internal deployment
+-------------------------
+
+One deployment is currently hosted at ``int.taler.net``.
+
+Withdraw:
+
+.. code-block:: console
+
+ $ python3 deployment/bin/WIP/taler-local withdraw --bank-host int.taler.net --bank-proto https
diff --git a/libeufin/iso20022.rst b/libeufin/iso20022.rst
index a4c0bf7b..7b1fed8c 100644
--- a/libeufin/iso20022.rst
+++ b/libeufin/iso20022.rst
@@ -24,13 +24,14 @@ The specifics of this mapping are:
of message elements in the same schema.
-Why does LibEuFin not use ISO 20022?
-====================================
+How does LibEuFin leverage ISO 20022?
+=====================================
-While LibEuFin can ingest ISO 20022 messages (camt.05x, pain.002) and generate
-them (pain.001), it does not use ISO 20022 in its API and internal data model.
+While LibEuFin can communicate ISO 20022 messages (camt.05x, pain.001) to
+other parties, it does **not** model its own authored API and internal data
+after the ISO 20022 standard.
-Reasons for not using ISO 20022 directly are:
+Objections to ISO20022:
1. Impedance mismatch. ISO 20022 messages do not map well to query/response
APIs.
diff --git a/libeufin/nexus-tutorial.rst b/libeufin/nexus-tutorial.rst
index 51e0cd28..482b0f96 100644
--- a/libeufin/nexus-tutorial.rst
+++ b/libeufin/nexus-tutorial.rst
@@ -134,6 +134,9 @@ If you don't have access to a real bank account with an EBICS API, you can set
up the sandbox. The sandbox is a simplistic and incomplete implementation of a
core banking system with EBICS access to bank accounts.
+The sandbox uses HTTP Basic auth, with username ``admin``.
+Choose a password and set env var ``LIBEUFIN_SANDBOX_ADMIN_PASSWORD`` to it.
+
The sandbox relies on a database, which you must specify using a JDBC
connection URI with the ``LIBEUFIN_SANDBOX_DB_CONNECTION`` environment
variable, before invoking any commands.
@@ -141,6 +144,7 @@ If this variable is not set, ``libeufin-sandbox`` complains and exits:
.. code-block:: console
+ $ export LIBEUFIN_SANDBOX_ADMIN_PASSWORD=secret
$ libeufin-sandbox serve
DB connection string not found/valid in the env variable LIBEUFIN_SANDBOX_DB_CONNECTION.
The following two examples are valid connection strings:
@@ -149,99 +153,336 @@ If this variable is not set, ``libeufin-sandbox`` complains and exits:
Only *SQLite* and *PostgreSQL (via TCP)* are supported right now.
-For the following commands, the sandbox service must be running.
-The sandbox service is started with the following command:
+Before being usable, a Sandbox needs to be configured. This is done
+by creating the ``default`` demobank. A demobank is a set of configuration
+values associated to one name; in the example below, we'll create one
+named ``default``, that is mandatory to have.
+
+.. note::
+
+ By design, many demobanks can be hosted by one running Sandbox,
+ although at the time of writing only the ``default`` one is supported.
+
+A default demobank having the EUR currency is created with the following command:
+
+.. code-block:: console
+
+ $ export LIBEUFIN_SANDBOX_DB_CONNECTION=jdbc:sqlite:/tmp/libeufintestdb
+ $ libeufin-sandbox config --currency EUR default
+
+In order to use Taler, a default exchange needs to be configured.
+
+.. code-block:: console
+
+ $ export LIBEUFIN_SANDBOX_DB_CONNECTION=jdbc:sqlite:/tmp/libeufintestdb
+ $ libeufin-sandbox default-exchange --demobank default $exchange_base_url $exchange_payto_address
+
+The sandbox service can now be started with the following command:
.. code-block:: console
+ $ export LIBEUFIN_SANDBOX_ADMIN_PASSWORD=secret
$ export LIBEUFIN_SANDBOX_DB_CONNECTION=jdbc:sqlite:/tmp/libeufintestdb
$ libeufin-sandbox serve --port 5016
+The instructions below hook Nginx to the Sandbox:
+
+.. code-block:: nginx
+
+ redirect / to /demobanks/default;
+ rewrite ^/$ https://$host/demobanks/default;
+ location / {
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Proto "https";
+ proxy_set_header X-Forwarded-Prefix "/";
+ proxy_pass http://localhost:5016/;
+ }
+
To reset the state of the sandbox, delete the database.
For invocations of the LibEuFin command-line interface tool (``libeufin-cli``),
-the following environment variable must be set to the URL of the sandbox
-service:
+the following environment variables must be set to the authentication
+information, and the URL of the sandbox service:
.. code-block:: console
- $ export LIBEUFIN_SANDBOX_URL=http://localhost:5016/
+ $ export LIBEUFIN_SANDBOX_USERNAME=admin
+ $ export LIBEUFIN_SANDBOX_PASSWORD=secret
+ $ export LIBEUFIN_SANDBOX_URL=http://localhost:5016
+Note that the password is the same as for ``LIBEUFIN_SANDBOX_ADMIN_PASSWORD``.
Verify that the sandbox is running:
.. code-block:: console
$ libeufin-cli sandbox check
- {
- "name" : "libeufin-sandbox",
- "version" : "0.0.0-dev.0"
- }
+ Hello, this is the Sandbox
Now an EBICS host can be created:
.. code-block:: console
$ libeufin-cli sandbox ebicshost create --host-id testhost
+ $ libeufin-cli sandbox ebicshost list
+ {
+ "ebicsHosts" : [ "testhost" ]
+ }
Note that most ``libeufin-cli`` subcommands will ask for input interactively if
the respective value is not specified on the command line.
-Next, create an EBICS subscriber (identified by the partner ID and user ID) for the host:
+Next, register a user. For the ``libeufin-cli sandbox demobank register``
+command, the ``LIBEUFIN_SANDBOX_USERNAME`` and ``LIBEUFIN_SANDBOX_PASSWORD``
+are assumed to be ``jrluser`` and ``easy``, respectively.
+
+.. note::
+
+ All the following commands address the ``default`` demobank, see ``libeufin-cli sandbox demobank --help``.
.. code-block:: console
- $ libeufin-cli sandbox ebicssubscriber create \
- --host-id testhost --partner-id partner01 --user-id user01
+ $ export LIBEUFIN_SANDBOX_USERNAME=jrluser
+ $ export LIBEUFIN_SANDBOX_PASSWORD=easy
+ $ libeufin-cli sandbox demobank register
-Create a bank account for the subscriber and add a sample transaction:
+Check the balance of the user just created:
.. code-block:: console
- $ libeufin-cli sandbox ebicsbankaccount create \
- --currency EUR \
- --iban DE18500105172929531888 \
- --bic INGDDEFFXXX \
- --person-name "Jane Normal" \
- --account-name "testacct01" \
- --ebics-host-id testhost \
- --ebics-user-id user01 \
- --ebics-partner-id partner01
+ $ libeufin-cli sandbox \
+ demobank info --bank-account jrluser
+
+With a user registered, we can now create an EBICS subscriber (identified by
+the partner ID and user ID) for the host. This command requires admin
+privileges, so ``LIBEUFIN_SANDBOX_USERNAME`` and ``LIBEUFIN_SANDBOX_PASSWORD``
+are reset back to ``admin`` and ``secret``, respectively.
-The account name ``testacct01`` is the unique identifier of the account within
+.. The plan is to replace the ‘sandbox ebicssubscriber list’ command
+ below with a ‘sandbox demobank show-ebicssubscriber’ command.
+ Need to implement it first!
+
+.. code-block:: console
+
+ $ export LIBEUFIN_SANDBOX_USERNAME=admin
+ $ export LIBEUFIN_SANDBOX_PASSWORD=secret
+ $ libeufin-cli sandbox \
+ demobank new-ebicssubscriber \
+ --host-id testhost \
+ --partner-id partner01 \
+ --user-id user02 \
+ --bank-account jrluser
+ $ libeufin-cli sandbox ebicssubscriber list
+ {
+ "subscribers" : [ {
+ "hostID" : "testhost",
+ "partnerID" : "partner01",
+ "userID" : "user02",
+ "systemID" : null,
+ "demobankAccountLabel" : "jrluser"
+ } ]
+ }
+
+The ``libeufin-cli sandbox demobank new-ebicssubscriber`` command
+also creates an associated bank account. You can see it with command:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount list
+ [ {
+ "label" : "bank",
+ "name" : "Bank account owner's name",
+ "iban" : "DE895351",
+ "bic" : "SANDBOXX"
+ }, {
+ "label" : "jrluser",
+ "name" : "Bank account owner's name",
+ "iban" : "DE724881",
+ "bic" : "SANDBOXX"
+ } ]
+
+The account name ``jrluser`` is the unique identifier of the account within
the sandbox. The EBICS parameters identify the subscriber that should have
access to the bank account via EBICS.
-To populate the account with some test transactions, run the following command
-(note that we use the ``bankaccount`` subcommand, because there is no need to rely
-on EBICS):
+The account already has one transaction, the "Sign-up bonus" from the bank.
+Note that in the following examples we transition to using the ``bankaccount``
+subcommand, because there is no need to rely on EBICS:
.. code-block:: console
- $ libeufin-cli sandbox bankaccount generate-transactions testacct01
+ $ libeufin-cli sandbox bankaccount transactions jrluser
+ {
+ "payments" : [ {
+ "accountLabel" : "jrluser",
+ "creditorIban" : "DE724881",
+ "creditorBic" : "SANDBOXX",
+ "creditorName" : "Unknown",
+ "debtorIban" : "DE895351",
+ "debtorBic" : "SANDBOXX",
+ "debtorName" : "The Bank",
+ "amount" : "100",
+ "currency" : "EUR",
+ "subject" : "Sign-up bonus",
+ "date" : "Tue, 22 Feb 2022 00:04:15 GMT",
+ "creditDebitIndicator" : "credit",
+ "accountServicerReference" : "2NG75I0O",
+ "paymentInformationId" : null
+ } ]
+ }
+
+To populate the account with some more transactions, run the following command:
.. code-block:: console
- $ libeufin-cli sandbox bankaccount simulate-incoming-transaction testacct01 \
+ $ libeufin-cli sandbox bankaccount generate-transactions jrluser
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount simulate-incoming-transaction jrluser \
--debtor-iban DE06500105174526623718 \
--debtor-bic INGDDEFFXXX \
--debtor-name "Joe Foo" \
--subject "Hello World" \
--amount 10.50
-Payments to a sandbox bank account can be listed as follows:
+Now the list of transactions has grown by several entries:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount transactions jrluser
+ {
+ "payments" : [ {
+ "accountLabel" : "jrluser",
+ "creditorIban" : "DE724881",
+ "creditorBic" : "SANDBOXX",
+ "creditorName" : "Unknown",
+ "debtorIban" : "DE895351",
+ "debtorBic" : "SANDBOXX",
+ "debtorName" : "The Bank",
+ "amount" : "100",
+ "currency" : "EUR",
+ "subject" : "Sign-up bonus",
+ "date" : "Tue, 22 Feb 2022 00:04:15 GMT",
+ "creditDebitIndicator" : "credit",
+ "accountServicerReference" : "2NG75I0O",
+ "paymentInformationId" : null
+ }, {
+ "accountLabel" : "jrluser",
+ "creditorIban" : "DE724881",
+ "creditorBic" : "SANDBOXX",
+ "creditorName" : "Creditor Name",
+ "debtorIban" : "DE64500105178797276788",
+ "debtorBic" : "DEUTDEBB101",
+ "debtorName" : "Max Mustermann",
+ "amount" : "22",
+ "currency" : "EUR",
+ "subject" : "sample transaction GSF7S5LC",
+ "date" : "Tue, 22 Feb 2022 01:26:18 GMT",
+ "creditDebitIndicator" : "credit",
+ "accountServicerReference" : "GSF7S5LC",
+ "paymentInformationId" : null
+ }, {
+ "accountLabel" : "jrluser",
+ "creditorIban" : "DE64500105178797276788",
+ "creditorBic" : "DEUTDEBB101",
+ "creditorName" : "Max Mustermann",
+ "debtorIban" : "DE724881",
+ "debtorBic" : "SANDBOXX",
+ "debtorName" : "Debitor Name",
+ "amount" : "10",
+ "currency" : "EUR",
+ "subject" : "sample transaction 1WUP303Q",
+ "date" : "Tue, 22 Feb 2022 01:26:18 GMT",
+ "creditDebitIndicator" : "debit",
+ "accountServicerReference" : "1WUP303Q",
+ "paymentInformationId" : null
+ }, {
+ "accountLabel" : "jrluser",
+ "creditorIban" : "DE724881",
+ "creditorBic" : "SANDBOXX",
+ "creditorName" : "Creditor Name",
+ "debtorIban" : "DE06500105174526623718",
+ "debtorBic" : "INGDDEFFXXX",
+ "debtorName" : "Joe Foo",
+ "amount" : "10.50",
+ "currency" : "EUR",
+ "subject" : "Hello World",
+ "date" : "Tue, 22 Feb 2022 01:26:41 GMT",
+ "creditDebitIndicator" : "credit",
+ "accountServicerReference" : "sandbox-ALQP8TXKJWRVKMAH",
+ "paymentInformationId" : null
+ } ]
+ }
+
+Lastly, you will want to schedule some tasks to run
+periodically in the background.
+This enables the Nexus to interact in (almost) real-time with the bank.
.. code-block:: console
- $ libeufin-cli sandbox bankaccount transactions testacct01
+ $ libeufin-cli accounts task-schedule jrluser \
+ --task-type submit \
+ --task-name submit-payments-each-second \
+ --task-cronspec "* * *"
+ $ libeufin-cli accounts task-schedule jrluser \
+ --task-type fetch \
+ --task-name fetch-reports-each-second \
+ --task-cronspec "* * *" \
+ --task-param-level report \
+ --task-param-range-type latest
+
+The first task submits payments and the second fetches reports.
+Both are specified to run every second (cronspec ``"* * *"``).
+To reduce the frequency to every five seconds, use the cronspec ``"*/5 * *"``
+(remember to quote, to protect from shell filename expansion).
.. note::
The sandbox is intended as a testing tool and thus not stable.
+Disable registrations (experimental)
+------------------------------------
+
+Demobanks can disable/enable registrations for new users.
+The responsible options are
+``--with-registrations`` / ``--without-registrations``.
+
For more information on the available commands, use the built-in ``--help`` flag.
The full functionality of the sandbox is available via
the :ref:`Sandbox API <sandbox-api>`.
+Register merchants for local currencies (experimental)
+------------------------------------------------------
+
+This feature allows to register merchants to participate
+in a local currency circuit. After having installed the
+Sandbox with the steps described above, the administrator
+can now register new merchant accounts with the following
+command. The main difference with ordinary registrations
+is that this API offers to associate a `cash-out` address
+to the account, so that merchants can convert the local
+currency back to their fiat bank account.
+
+.. note::
+ Disable ordinary registrations as described in the previous section,
+ to make sure that only the administrator can add new accounts.
+
+.. code-block:: console
+
+ $ export LIBEUFIN_SANDBOX_USERNAME=admin
+ $ export LIBEUFIN_SANDBOX_PASSWORD=$password
+ $ export LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD=$circuit-account-password
+ $ libeufin-cli sandbox demobank circuit-register \
+ --username=example-shop \
+ --email=example-shop@example.com \
+ --cashout-address=payto://iban/EXAMPLESHOP-FIAT-IBAN \
+ --name="Example Shop"
+
+
+If the command exited successfully, the new user should be
+able to login with the ``$circuit-account-username`` and
+``$circuit-account-password`` credentials. Run ``libeufin-cli sandbox demobank circuit-register --help``, for additional information about the other options.
Connect Nexus with an EBICS account
===================================
@@ -255,7 +496,12 @@ Only *SQLite* (e.g. ``jdbc:sqlite:/tmp/libeufintestdb``) and *PostgreSQL (via TC
(e.g. ``jdbc:postgresql://localhost:$port/libeufintestdb?user=$username&password=$password``)
are supported right now.
-Use the following command to run the nexus service:
+Use the following command to run the Nexus service:
+
+ Neuxs defaults to *not* storing the messages that it downloads
+ from the bank. To revert this behaviour, export also the environment
+ variable ``LIBEUFIN_NEXUS_KEEP_BANK_MESSAGES`` to ``yes`` before
+ running the following command.
.. code-block:: console
@@ -264,8 +510,8 @@ Use the following command to run the nexus service:
This assumes that the PostgreSQL service with a database
called ``libeufindb`` listens on port 5433
-for requests from the nexus service.
-The nexus service itself listens on port 5017.
+for requests from the Nexus service.
+The Nexus service itself listens on port 5017.
Note that you must have the ``LIBEUFIN_NEXUS_DB_CONNECTION``
environment variable set for most uses of the libeufin-nexus
command.
@@ -290,14 +536,22 @@ to be defined in the environment: ``LIBEUFIN_NEXUS_URL``, ``LIBEUFIN_NEXUS_USERN
and ``LIBEUFIN_NEXUS_PASSWORD``. In this example, ``LIBEUFIN_NEXUS_USERNAME`` should be
set to ``foo``, and ``LIBEUFIN_NEXUS_PASSWORD`` to the value given for its password
in the previous step (with the ``libeufin-nexus superuser`` command). The
-``LIBEUFIN_NEXUS_URL`` could be given as ``http://localhost:5017/``.
+``LIBEUFIN_NEXUS_URL`` could be given as ``http://localhost:5017``.
-Next, we create a EBICS *bank connection* that nexus can use to communicate with the bank.
+Next, we create a EBICS *bank connection* that Nexus can use to communicate with the bank.
.. note::
For the sandbox setup in this guide, the ``EBICS_BASE_URL``
should be ``http://localhost:5016/ebicsweb``.
+ This is the value of environment variable
+ ``LIBEUFIN_SANDBOX_URL`` suffixed with ``/ebicsweb``,
+ since the sandbox will be providing EBICS services.
+
+ Similarly, ``EBICS_HOST_ID`` should be ``testhost``,
+ and ``EBICS_PARTNER_ID`` should be ``partner01``.
+ For ``EBICS_USER_ID`` we will use ``user02`` (for account ``jrluser``),
+ and for ``CONNECTION_NAME``, we will use ``conn01``.
.. code-block:: console
@@ -310,7 +564,10 @@ Next, we create a EBICS *bank connection* that nexus can use to communicate with
--ebics-user-id $EBICS_USER_ID \
$CONNECTION_NAME
-If this step executes correctly, Nexus will have created all the cryptographic
+If this step executes correctly
+(use ``libeufin-cli connections list-connections``
+and ``libeufin-cli connections show-connection`` to check),
+Nexus will have created all the cryptographic
material that is needed on the client side; in this EBICS example, it created
the signature and identification keys. It is therefore advisable to *make
a backup copy* of such keys.
@@ -325,7 +582,7 @@ a backup copy* of such keys.
$CONNECTION_NAME
At this point, Nexus needs to both communicate its keys to the bank, and
-download the bank's keys. This syncronization happens through the INI, HIA, and
+download the bank's keys. This synchronization happens through the INI, HIA, and
finally, HPB message types.
After the electronic synchronization, the subscriber must confirm their keys
@@ -394,7 +651,7 @@ list of transactions, and making a payment.
Request history of transactions
===============================
-The LibEuFin nexus keeps a local copy of the bank account's transaction
+The LibEuFin Nexus keeps a local copy of the bank account's transaction
history. Before querying transactions locally, it is necessary
to request transactions for the bank account via the bank connection.
@@ -449,18 +706,18 @@ in the next step, to **send the payment instructions to the bank**:
.. code-block:: console
- $ libeufin-cli accounts submit-payment \
+ $ libeufin-cli accounts submit-payments \
--payment-uuid $UUID \
$LOCAL_ACCOUNT_NAME
Automatic scheduling
====================
-With an EBICS bank connection, the LibEuFin nexus needs to regularly query for
+With an EBICS bank connection, the LibEuFin Nexus needs to regularly query for
new transactions and (re-)submit prepared payments.
It is possible to schedule these tasks via an external task scheduler such as
-cron(8). However, the nexus also has an internal task scheduling mechanism for
+cron(8). However, the Nexus also has an internal task scheduling mechanism for
accounts.
diff --git a/manpages/TDM.el b/manpages/TDM.el
new file mode 100644
index 00000000..a58fb1c8
--- /dev/null
+++ b/manpages/TDM.el
@@ -0,0 +1,172 @@
+;;; TDM.el --- editing Taler docs.git/manpages/* -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021, 2022 Taler Systems SA
+;;
+;; This file is part of GNU TALER.
+;;
+;; TALER is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU Affero General Public License as
+;; published by the Free Software Foundation; either version 2.1,
+;; or (at your option) any later version.
+;;
+;; TALER is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU Affero General Public License for more details.
+;;
+;; You should have received a copy of the GNU Affero General Public
+;; License along with TALER; see the file COPYING. If not, see
+;; <http://www.gnu.org/licenses/>.
+;;
+;; Author: Thien-Thi Nguyen <ttn@gnu.org>
+
+;;; Commentary:
+
+;; This library currently provides two commands: ‘TDM-convert-options’
+;; and ‘TDM-recursive-help’.
+;;
+;; * ‘TDM-convert-options’
+;; The intended workflow is simple:
+;; - Create a new file from template.
+;; - Do the <FOO> substitutions / deletions as necessary.
+;; - Split the window, one half for Synopsis, one for Description.
+;; - Do ‘COMMAND --help’ and copy the portion of its output that
+;; describes the options into the buffer in the Synopsis section.
+;; + Place point at bol of an option and call ‘TDM-convert-options’.
+;; (Personally, I locally bind this to ‘C-c C-o’.)
+;; - Set mark at point and move point to the end of the
+;; description text (for that option).
+;; - Kill the region (with ‘C-w’).
+;; - Switch to the other window, find a suitable place, and
+;; yank (with ‘C-y’) the text there.
+;; - Edit the description as necessary.
+;; - Loop from "+" until finished.
+;;
+;; At the end, you may have to delete some extra blank lines
+;; in the Synopsis section.
+;;
+;; ‘TDM-convert-options’ takes a prefix arg, which inhibits the
+;; deletion of the original (--help output) text. This can be
+;; useful if you don't trust (yet :-D) the conversion process.
+;;
+;; There are a couple TODO items, which point to situations that
+;; have not yet arisen in practice, but that might theoretically
+;; bother us in the future.
+;;
+;; * ‘TDM-recursive-help’
+;; This command is intended for libeufin programs, specifically
+;; libeufin-sandbox, libeufin-nexus, and libeufin-cli. However,
+;; it should work with any Java program that uses clikt, or any
+;; Python program that uses click, for its command-line handling.
+;;
+;; You can obtain the --help output (recursively) in a buffer
+;; and write it to a file for further analysis / processing.
+
+;;; Code:
+
+(require 'cl-lib)
+
+(defun TDM-parse-option-triple ()
+ "Return a triple formed by parsing option text at point.
+The triple has the form (ARG SHORT LONG), where each element
+can be either a string or ‘nil’.
+
+Signal error if parsing fails.
+Leave point after the end of the parsed text."
+ (cl-flet ((peer (regexp)
+ (re-search-forward regexp (line-end-position) t)))
+ (cond
+ ;; Full form: (ARG SHORT LONG).
+ ((peer " *-\\(.\\), --\\([^=\n]+\\)=\\(\\S +\\)")
+ (mapcar #'match-string-no-properties (list 3 1 2)))
+ ;; No argument: (nil SHORT LONG).
+ ((peer " *-\\(.\\), --\\(\\S +\\)")
+ (list nil
+ (match-string-no-properties 1)
+ (match-string-no-properties 2)))
+ ;; TODO: Handle other combinations.
+ (t
+ (error "Could not parse option text")))))
+
+(defun TDM-insert-options (arg short long)
+ "Insert formatted options, twice (on two lines).
+The first is for the Synopsis section, the second for Description."
+ (unless (zerop (current-column))
+ (open-line 1)
+ (forward-line 1))
+ (cl-flet ((ins (short-space sep)
+ (cond
+ ;; Both available.
+ ((and short long)
+ (insert "**-" short "**")
+ (when arg
+ (insert short-space "*" arg "*"))
+ (insert sep)
+ (insert "**--" long (if arg "=" "") "**")
+ (when arg
+ (insert "\\ \\ *" arg "*")))
+ ;; TODO: Handle other combinations.
+ (t
+ (error "Could not handle (%S %S %S)"
+ arg short long)))))
+ ;; First line.
+ (insert "[")
+ (ins " " " | ")
+ (insert "]\n")
+ ;; Second line. Leave point at its beginning.
+ (save-excursion
+ (ins " " " \\| ")
+ ;; Add a newline only if necessary.
+ (unless (eolp)
+ (insert "\n")))))
+
+(defun TDM-convert-options (&optional keep-orig)
+ "Grok the options at point and insert formatted ones.
+If successful, delete the parsed options as well.
+Prefix arg KEEP-ORIG means don't delete them."
+ (interactive "P")
+ (let* ((p (point))
+ (triple (TDM-parse-option-triple))
+ (q (point)))
+ (apply #'TDM-insert-options triple)
+ (unless keep-orig
+ (save-excursion
+ (delete-region p q)))))
+
+(defun TDM-recursive-help (command)
+ "Call COMMAND --help and recurse on its subcommands.
+Subcommands are identified by \"Commands:\" in column 0
+in the output.
+
+Collect the output in a new buffer *COMMAND --help*,
+with one page per --help output."
+ (interactive "sCommand: ")
+ (let ((out (get-buffer-create (format "*%s --help*" command))))
+ (with-current-buffer out
+ (erase-buffer))
+ (cl-labels
+ ;; visit command
+ ((v (c) (with-temp-buffer
+ (apply #'call-process (car c) nil t nil
+ (append (cdr c) (list "--help")))
+ (goto-char (point-min))
+ (when (re-search-forward "^Commands:\n" nil t)
+ (while (looking-at "[ ][ ]\\(\\S +\\)")
+ (let ((sub (match-string 1)))
+ (v (append c (list sub))))
+ (forward-line 1)
+ (while (looking-at "[ ][ ][ ]")
+ (forward-line 1))))
+ (let ((s (buffer-string)))
+ (message "c: %s" c)
+ (with-current-buffer out
+ (goto-char (point-min))
+ (insert "\f\n")
+ (insert "$ " (substring (format "%s" c) 1 -1) "\n")
+ (insert s "\n"))))))
+ (v (list command)))
+ (switch-to-buffer out)))
+
+(provide 'TDM)
+
+;;; TDM.el ends here
diff --git a/manpages/TEMPLATE-1 b/manpages/TEMPLATE-1
new file mode 100644
index 00000000..40debc7e
--- /dev/null
+++ b/manpages/TEMPLATE-1
@@ -0,0 +1,48 @@
+<COMMAND>(1)
+############
+
+.. only:: html
+
+ Name
+ ====
+
+ **<COMMAND>** - <ONE-LINE-DESCRIPTION>
+
+
+Synopsis
+========
+
+**<COMMAND>**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-v** | **--version**]
+
+
+Description
+===========
+
+**<COMMAND>** is a command-line tool to <ONE-LINE-DESCRIPTION>.
+
+Its options are as follows:
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the <PROGRAM>
+ to operate from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-v** \| **–version**
+ Print version information.
+
+
+See Also
+========
+
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/libeufin-cli.1.rst b/manpages/libeufin-cli.1.rst
new file mode 100644
index 00000000..548f2ac6
--- /dev/null
+++ b/manpages/libeufin-cli.1.rst
@@ -0,0 +1,1006 @@
+libeufin-cli(1)
+###############
+
+.. only:: html
+
+ Name
+ ====
+
+ **libeufin-cli** - Interact with LibEuFin Sandbox and Nexus
+
+
+Synopsis
+========
+
+**libeufin-cli**
+[**-h** | **--help**]
+[**--version**]
+COMMAND [ARGS...]
+
+Commands: accounts, connections, facades, permissions, sandbox, users
+
+
+Description
+===========
+
+**libeufin-cli** is the user interface program to interact
+with **libeufin-sandbox** and **libeufin-nexus** when they are
+operating as HTTP servers, listening on localhost ports.
+Normally, you invoke them with their respective ``serve`` commands,
+and in a separate shell, use **libeufin-cli** to send requests
+and receive responses from them.
+
+The interaction model is as follows:
+
+- (Optionally) Start the sandbox.
+
+- Start the nexus.
+
+- Use **libeufin-cli** to interact with them.
+ You can manage users and permissions, bank accounts, "facades",
+ transactions, and connections between the various systems.
+
+For **libeufin-cli** to be able to communicate with **libeufin-sandbox**,
+the following environment variables need to be set:
+
+``LIBEUFIN_SANDBOX_USERNAME``
+ This should normally be ``admin``.
+
+``LIBEUFIN_SANDBOX_PASSWORD``
+ This is the same password chosen when the sandbox was started.
+
+``LIBEUFIN_SANDBOX_URL``
+ This is ``http://localhost:PORT``, where ``PORT`` is the
+ same port chosen when the sandbox was started.
+ This URL can also be specified with the ``--sandbox-url URL``
+ option to the ``sandbox`` command (see below).
+ If both are given, the ``--sandbox-url`` option takes precedence.
+
+For **libeufin-cli** to be able to communicate with **libeufin-nexus**,
+the following environment variables need to be set:
+
+``LIBEUFIN_NEXUS_USERNAME``
+ For some operations (such as ``users create``), this must be the
+ same username chosen by the ``libeufin-nexus superuser`` command.
+
+``LIBEUFIN_NEXUS_PASSWORD``
+ This is the password associated with the username.
+
+``LIBEUFIN_NEXUS_URL``
+ This is ``http://localhost:PORT``, where ``PORT`` is the
+ same port chosen when the nexus was started.
+
+Of the six commands, the ``sandbox`` command talks to the sandbox,
+while the other five commands talk to the nexus.
+The following sections describe each command and their subcommands in detail.
+
+
+sandbox
+-------
+
+The ``libeufin-cli sandbox`` command is for managing the sandbox process
+started by ``libeufin-sandbox serve``.
+It takes one option, ``--sandbox-url URL``, to be specified before
+the subcommands and their arguments.
+This URL can also be specified with the ``LIBEUFIN_SANDBOX_URL``
+environment variable (see above).
+If both are given, the ``--sandbox-url`` option takes precedence.
+
+The following subcommands are available: bankaccount, check, demobank,
+ebicsbankaccount, ebicshost, ebicssubscriber.
+
+The first command to use is ``check``,
+followed by ``ebicshost``, ``ebicssubscriber``,
+and ``ebicsbankaccount``, to configure the basic system.
+
+You can then use the ``bankaccount`` command to generate (simulated)
+transaction traffic.
+
+After that, the ``demobank`` command and its subcommands
+provide the same access to the API that Nexus itself uses.
+You normally do not need to use those commands directly.
+
+
+sandbox check
+-------------
+
+The ``check`` command attempts a simple aliveness check by
+contacting the sandbox and displaying its returned message.
+If all goes well, you should see something like:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox check
+ Hello, this is the Sandbox
+
+
+sandbox ebicshost
+-----------------
+
+The ``ebicshost`` command manages EBICS hosts.
+It has two subcommands: create and list.
+
+sandbox ebicshost create
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``ebicshost create`` command creates a EBICS host.
+It takes one option, ``--host-id ID``, where ``ID`` is used to
+identify that host for all future commands.
+
+sandbox ebicshost list
+^^^^^^^^^^^^^^^^^^^^^^
+
+The ``ebicshost list`` command lists the hosts in the system.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox ebicshost create --host-id testhost
+ $ libeufin-cli sandbox ebicshost list
+ {
+ "ebicsHosts" : [ "testhost" ]
+ }
+
+Here, the ``ID`` is ``testhost``.
+
+
+sandbox ebicssubscriber
+-----------------------
+
+The ``ebicssubscriber`` command manages EBICS subscribers.
+It has two subcommands: create and list.
+
+sandbox ebicssubscriber create
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+NB: This command is deprecated and will be removed at some point.
+See ``sandbox demobank new-ebicssubscriber`` (below) for its replacement.
+
+The ``ebicssubscriber create`` command creates a EBICS subscriber.
+It takes three options, all of which are required:
+
+::
+
+ --host-id TEXT Ebics host ID
+ --partner-id TEXT Ebics partner ID
+ --user-id TEXT Ebics user ID
+
+The host ID should be the same one specified in ``ebicshost create``
+(see above).
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox ebicssubscriber create \
+ --host-id testhost \
+ --partner-id partner01 \
+ --user-id user01
+
+Note that ``testhost`` is the same as in the ``ebicshost create``
+example (see above).
+
+sandbox ebicssubscriber list
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``ebicssubscriber list`` command lists the EBICS subscribers
+in the system.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox ebicssubscriber list
+ {
+ "subscribers" : [ {
+ "hostID" : "testhost",
+ "partnerID" : "partner01",
+ "userID" : "user01",
+ "systemID" : null,
+ "demobankAccountLabel" : "not associated yet"
+ } ]
+ }
+
+Note that the output reflects the subscriber created in
+the ``ebicssubscriber create`` example (see above).
+
+
+sandbox ebicsbankaccount
+------------------------
+
+The ``ebicsbankaccount`` command manages EBICS bank accounts.
+It has one subcommand: create.
+(To list accounts, see ``sandbox bankaccount`` below.)
+
+sandbox ebicsbankaccount
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``ebicsbankaccount create`` command takes several options, all required:
+
+::
+
+ --iban TEXT IBAN
+ --bic TEXT BIC
+ --person-name TEXT bank account owner name
+ --account-name TEXT label of this bank account
+ --ebics-user-id TEXT user ID of the Ebics subscriber
+ --ebics-host-id TEXT host ID of the Ebics subscriber
+ --ebics-partner-id TEXT partner ID of the Ebics subscriber
+
+At this time, although the ``--person-name`` option is required,
+the sandbox does not remember the option value.
+(When queried, it displays "Bank account owner's name" instead.
+See ``sandbox bankaccount`` below.)
+For the sandbox, the important value is the ``--account-name`` option.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox ebicsbankaccount create \
+ --iban DE18500105172929531888 \
+ --bic INGDDEFFXXX \
+ --person-name "Jane Normal" \
+ --account-name testacct01 \
+ --ebics-host-id testhost \
+ --ebics-user-id user01 \
+ --ebics-partner-id partner01
+
+Note that ``testhost`` is the same as in the ``ebicshost create``
+example, and that ``user01`` and ``partner01`` are the same as in the
+``ebicssubscriber create`` example (see above).
+
+
+sandbox bankaccount
+-------------------
+
+The ``bankaccount`` command manages bank accounts.
+It has several subcommands: list, generate-transactions,
+simulate-incoming-transaction, transactions.
+
+sandbox bankaccount list
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``bankaccount list`` command lists the bank accounts in the system.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount list
+ [ {
+ "label" : "bank",
+ "name" : "Bank account owner's name",
+ "iban" : "DE370758",
+ "bic" : "SANDBOXX"
+ }, {
+ "label" : "testacct01",
+ "name" : "Bank account owner's name",
+ "iban" : "DE18500105172929531888",
+ "bic" : "INGDDEFFXXX"
+ } ]
+
+Note that ``testacct01``, ``DE18500105172929531888``, and ``INGDDEFFXXX``
+are the same as specified in the ``ebicsbankaccount create`` example
+(see above).
+
+sandbox bankaccount generate-transactions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The remaining ``bankaccount`` commands deal with transactions
+to and from the bank accounts.
+
+The ``bankaccount generate-transactions`` command generates
+test transactions.
+It takes one arg, the account label.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount generate-transactions testacct01
+
+Note that ``testacct01`` is the account label shown in the ``bankaccount
+list`` example (see above).
+
+sandbox bankaccount simulate-incoming-transaction
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``bankaccount simulate-incoming-transaction`` books an
+incoming payment in the sandbox.
+It takes one arg, the account label, and several options.
+
+::
+
+ --debtor-iban TEXT IBAN sending the payment
+ --debtor-bic TEXT BIC sending the payment
+ --debtor-name TEXT name of the person who is sending the payment
+ --amount TEXT amount, no currency
+ --subject TEXT payment subject
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount simulate-incoming-transaction
+ testacct01 \
+ --debtor-iban DE06500105174526623718 \
+ --debtor-bic INGDDEFFXXX \
+ --debtor-name "Joe Foo" \
+ --subject "Hello World" \
+ --amount 10.50
+
+Note that ``testacct01`` is the same as in previous examples (see above),
+and that ``10.50`` is in ``X.Y`` format.
+
+sandbox bankaccount transactions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``bankaccount transactions`` command lists transactions.
+It takes one arg, the account label.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount transactions testacct01
+ {
+ "payments" : [ {
+ "accountLabel" : "testacct01",
+ "creditorIban" : "DE18500105172929531888",
+ "creditorBic" : "INGDDEFFXXX",
+ "creditorName" : "Creditor Name",
+ "debtorIban" : "DE64500105178797276788",
+ "debtorBic" : "DEUTDEBB101",
+ "debtorName" : "Max Mustermann",
+ "amount" : "5",
+ "currency" : "EUR",
+ "subject" : "sample transaction DILWBJHL",
+ "date" : "Wed, 26 Jan 2022 09:03:44 GMT",
+ "creditDebitIndicator" : "credit",
+ "accountServicerReference" : "DILWBJHL",
+ "paymentInformationId" : null
+ }, {
+ "accountLabel" : "testacct01",
+ "creditorIban" : "DE64500105178797276788",
+ "creditorBic" : "DEUTDEBB101",
+ "creditorName" : "Max Mustermann",
+ "debtorIban" : "DE18500105172929531888",
+ "debtorBic" : "INGDDEFFXXX",
+ "debtorName" : "Debitor Name",
+ "amount" : "12",
+ "currency" : "EUR",
+ "subject" : "sample transaction N7JSY17B",
+ "date" : "Wed, 26 Jan 2022 09:03:44 GMT",
+ "creditDebitIndicator" : "debit",
+ "accountServicerReference" : "N7JSY17B",
+ "paymentInformationId" : null
+ }, {
+ "accountLabel" : "testacct01",
+ "creditorIban" : "DE18500105172929531888",
+ "creditorBic" : "INGDDEFFXXX",
+ "creditorName" : "Creditor Name",
+ "debtorIban" : "DE06500105174526623718",
+ "debtorBic" : "INGDDEFFXXX",
+ "debtorName" : "Joe Foo",
+ "amount" : "10.50",
+ "currency" : "EUR",
+ "subject" : "Hello World",
+ "date" : "Wed, 26 Jan 2022 09:04:31 GMT",
+ "creditDebitIndicator" : "credit",
+ "accountServicerReference" : "sandbox-6UI2J3636J9EESXO",
+ "paymentInformationId" : null
+ } ]
+ }
+
+Note that ``testacct01`` is the same as in previous examples (see above),
+and that the generated transactions from previous examples are listed,
+as well.
+
+
+sandbox demobank
+----------------
+
+The ``demobank`` command provides an interface to the Access API,
+which includes three commands: register, info, new-transaction.
+There is also a fourth ``demobank`` command, new-ebicssubscriber,
+that does not use the Access API.
+
+For all ``demobank`` commands, the sandbox URL *must* specify which
+*one* bank the command applies to in the base URL.
+Note that this URL cannot be used with other sandbox commands.
+In other words:
+
+``--sandbox-url http://localhost:5016/demobanks/default``
+ This base URL can be used for commands:
+
+ - sandbox demobank register
+ - sandbox demobank info
+ - sandbox demobank new-ebicssubscriber
+ - sandbox demobank new-transaction
+
+ It specifies the ``default`` demobank.
+
+``--sandbox-url http://localhost:5016``
+ This base URL can be used for all other sandbox commands.
+
+In the following examples, the base URL will be explicitly shown with
+the ``--sandbox-url`` option for the ``demobank`` commands.
+
+sandbox demobank register
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``demobank register`` command registers a new bank account.
+Note that the username will be both the username to login at
+the bank and the bank account label.
+It takes the username and password from the
+``LIBEUFIN_SANDBOX_USERNAME`` and ``LIBEUFIN_SANDBOX_PASSWORD``
+environment variables.
+The username *need not be* ``admin``.
+
+For example:
+
+.. code-block:: console
+
+ $ export LIBEUFIN_SANDBOX_USERNAME=jrluser
+ $ export LIBEUFIN_SANDBOX_PASSWORD=easy
+ $ libeufin-cli sandbox \
+ --sandbox-url http://localhost:5016/demobanks/default \
+ demobank register
+
+sandbox demobank info
+^^^^^^^^^^^^^^^^^^^^^
+
+The ``demobank info`` command returns basic information on a bank account.
+It takes option ``--bank-account NAME``.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox \
+ --sandbox-url http://localhost:5016/demobanks/default \
+ demobank info --bank-account jrluser
+ {
+ "balance" : {
+ "amount" : "EUR:100",
+ "credit_debit_indicator" : "credit"
+ },
+ "paytoUri" : "payto://iban/SANDBOXX/DE948559?receiver-name=admin"
+ }
+
+Note that ``jrluser`` is the same username / bank account name
+as in the ``register`` example (see above).
+
+sandbox demobank new-ebicssubscriber
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``demobank new-ebicssubscriber`` command associates a new Ebics
+subscriber to an existing bank account.
+It takes several options, all required:
+
+::
+
+ --host-id TEXT Ebics host ID
+ --partner-id TEXT Ebics partner ID
+ --user-id TEXT Ebics user ID
+ --bank-account TEXT Label of the bank account to associate
+ with this Ebics subscriber
+
+For example:
+
+.. code-block: console
+
+ $ libeufin-cli sandbox \
+ --sandbox-url http://localhost:5016/demobanks/default \
+ demobank new-ebicssubscriber \
+ --host-id testhost \
+ --partner-id partner01 \
+ --user-id user02 \
+ --bank-account jrluser
+
+Note that ``testhost`` is the same as in the ``ebicshost create``
+example, and that ``partner01`` is the same as in the
+``ebicssubscriber create`` example (see above).
+The ``user02`` is new.
+The ``--bank-account jrluser`` is the same as in the
+``info`` example (see above).
+You can see the effect of the ``new-ebicssubscriber`` command
+with the ``sandbox ebicssubscriber list``
+and ``sandbox bankaccount list`` commands.
+
+sandbox demobank new-transaction
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``demobank new-transaction`` command initiates a new transaction.
+It takes several options, all required:
+
+::
+
+ --bank-account TEXT Label of the bank account to be
+ debited for the transaction
+ --payto-with-subject TEXT Payto address including the
+ subject as a query parameter
+ --amount CUR:X.Y Amount to transfer
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli sandbox \
+ --sandbox-url http://localhost:5016/demobanks/default \
+ demobank new-transaction \
+ --bank-account jrluser \
+ --payto-with-subject 'payto://FIXME/?message=1kg+coffee' \
+ --amount EUR:10.50
+ FIXME: Any output?
+
+Note that ``--bank-account jrluser`` is the same as in the
+``info`` and ``new-ebicssubscriber`` command examples (see above).
+
+
+connections
+-----------
+
+The ``connections`` set of commands handle connections between
+the bank(s) and Nexus.
+It has several subcommands:
+new-ebics-connection,
+list-connections,
+show-connection,
+delete-connection,
+export-backup,
+restore-backup,
+get-key-letter,
+connect,
+list-offered-bank-accounts,
+download-bank-accounts,
+import-bank-account.
+
+Generally, you will create a connection, save its critical key information
+to a safe location, then use the connection to find and import a bank
+account, for further use.
+
+Several commands take a ``CONNECTION_NAME`` argument.
+In the following examples, we use ``conn01`` for that.
+Also, for demonstration purposes, we use the sandbox EBICS services,
+so the ``EBICS_URL`` follows the previous examples in the ``sandbox``
+command, as the value of environment variable ``LIBEUFIN_SANDBOX_URL``
+suffixed with ``/ebicsweb``, i.e., ``http://localhost:5016/ebicsweb``.
+
+connections new-ebics-connection
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``connections new-ebics-connection`` command creates a new connection
+between an EBICS service and the Nexus.
+It takes one arg, the ``CONNECTION_NAME``, and four required options:
+
+::
+
+ --ebics-url TEXT EBICS URL
+ --host-id TEXT Host ID
+ --partner-id TEXT Partner ID
+ --ebics-user-id TEXT Ebics user ID
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli connections new-ebics-connection \
+ --ebics-url http://localhost:5016/ebicsweb \
+ --host-id testhost \
+ --partner-id partner01 \
+ --ebics-user-id user02 \
+ conn01
+
+Note that the ``testhost``, ``partner01``, and ``user02`` are the same as
+in the ``sandbox demobank new-ebicssubscriber`` command (see above).
+
+connections list-connections
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``connections list-connections`` command lists connections in Nexus.
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli connections list-connections
+ {
+ "bankConnections" : [ {
+ "name" : "conn01",
+ "type" : "ebics"
+ } ]
+ }
+
+The name of the connection is ``conn01`` as in the
+``connections new-ebics-connection`` example (see above).
+
+connections show-connection
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``connections show-connection`` command displays information
+about a specific connection.
+It takes one argument, the ``CONNECTION_NAME``.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli connections show-connection conn01
+ {
+ "type" : "ebics",
+ "owner" : "foo",
+ "ready" : true,
+ "details" : {
+ "ebicsUrl" : "http://localhost:5016/ebicsweb",
+ "ebicsHostId" : "testhost",
+ "partnerId" : "partner01",
+ "userId" : "user02"
+ }
+ }
+
+The details are the same as in the ``connections new-ebics-connections``
+example (see above).
+
+connections delete-connection
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``connections delete-connection`` command deletes a bank connection.
+It takes one argument, the ``CONNECTION_NAME``.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli connections delete-connection conn01
+
+connections export-backup
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``connections export-backup`` command writes key and signature
+information to a backup file (which you should take care to store
+in a secure location).
+It takes one argument, the ``CONNECTION_NAME`` and two required options:
+
+::
+
+ --passphrase TEXT Passphrase for locking the backup
+ --output-file TEXT Where to store the backup
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli connections export-backup \
+ --passphrase secret \
+ --output-file sig-and-key-info \
+ conn01
+ FIXME: Output?
+
+See: https://bugs.gnunet.org/view.php?id=7180
+
+connections restore-backup
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+connections get-key-letter
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``connections get-key-letter`` command creates a PDF file
+that contains key information.
+It takes two arguments, ``CONNECTION_NAME`` and ``OUTPUT_FILE``.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli connections get-key-letter conn01 key-letter.pdf
+
+This creates file ``key-letter.pdf`` in the current working directory.
+
+connections connect
+^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+connections list-offered-bank-accounts
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+connections download-bank-accounts
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+connections import-bank-account
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+
+users
+-----
+
+The ``libeufin-cli users`` command manages users authorized to
+operate Nexus.
+It has several subcommands: self, list, create, change-password.
+The ``create`` and ``change-password`` commands can only be issued
+by the superuser
+(as configured by the ``libeufin-nexus superuser`` command),
+while the ``self`` and ``list`` commands can be issued by any user.
+
+In the following ``users`` examples, we assume that previously
+the command ``libeufin-nexus superuser foo`` was issued, and
+that the current username and password are for that user
+(via environment variables ``LIBEUFIN_NEXUS_USERNAME`` and
+``LIBEUFIN_NEXUS_PASSWORD``, see above).
+
+users self
+^^^^^^^^^^
+
+The ``users self`` command displays information for the current user.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli users self
+ {
+ "username" : "foo",
+ "superuser" : true
+ }
+
+users list
+^^^^^^^^^^
+
+The ``users list`` command displays information for all the
+users authorized to operate Nexus.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli users list
+ {
+ "users" : [ {
+ "username" : "foo",
+ "superuser" : true
+ } ]
+ }
+
+In this example, there is only one user, the superuser ``foo``.
+
+users create
+^^^^^^^^^^^^
+
+The ``users create`` command creates a normal (non-superuser) user.
+It takes one argument, the ``USERNAME`` and one option,
+``--password TEXT``.
+If you omit the option, **libeufin-cli** will prompt you for the password.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli users create --password easy jrluser
+ {
+ "message" : "New user 'jrluser' registered"
+ }
+ $ libeufin-cli users list
+ {
+ "users" : [ {
+ "username" : "foo",
+ "superuser" : true
+ }, {
+ "username" : "jrluser",
+ "superuser" : false
+ } ]
+ }
+
+In this example, we create the same user as for the sandbox examples
+(see above).
+Note that the system now includes two users.
+
+users change-password
+^^^^^^^^^^^^^^^^^^^^^
+
+The ``users change-password`` command changes the password for a user.
+It takes one argument, the ``USERNAME`` and one option,
+``--new-password TEXT``.
+If you omit the option, **libeufin-cli** will prompt you for the password.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli users change-password --new-password hard jrluser
+ {
+ "message" : "Password successfully changed"
+ }
+
+
+permissions
+-----------
+
+The ``libeufin-cli permissions`` command manages permissions
+for operations on Nexus.
+It has three subcommands: list, grant, revoke.
+All three commands can only be issued by the superuser.
+
+permissions list
+^^^^^^^^^^^^^^^^
+
+The ``permissions list`` command lists the granted permissions.
+At the beginning of a session, there are none:
+
+.. code-block:: console
+
+ $ libeufin-cli permissions list
+ {
+ "permissions" : [ ]
+ }
+
+
+permissions grant
+^^^^^^^^^^^^^^^^^
+
+The ``permissions grant`` command adds a permission to the list
+of granted permissions.
+It takes five arguments: ``SUBJECT_TYPE``, ``SUBJECT_ID``,
+``RESOURCE_TYPE``, ``RESOURCE_ID``, ``PERMISSION_NAME``.
+
+FIXME: The subject type and id, resource type and id, are ...
+
+The ``PERMISSION_NAME`` is one of the following:
+
+- ``facade.talerwiregateway.history``
+- ``facade.talerwiregateway.transfer``
+- ``facade.anastasis.history``
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli permissions grant \
+ some-subject-type some-subject-id \
+ some-resource-type some-resource-id \
+ facade.anastasis.history
+ { }
+ $ libeufin-cli permissions list
+ {
+ "permissions" : [ {
+ "subjectType" : "some-subject-type",
+ "subjectId" : "some-subject-id",
+ "resourceType" : "some-resource-type",
+ "resourceId" : "some-resource-id",
+ "permissionName" : "facade.anastasis.history"
+ } ]
+ }
+
+permissions revoke
+^^^^^^^^^^^^^^^^^^
+
+The ``permissions revoke`` command does the opposite of the
+``permissions grant`` command.
+It takes the same arguments as the ``permissions grant`` command:
+``SUBJECT_TYPE``, ``SUBJECT_ID``, ``RESOURCE_TYPE``, ``RESOURCE_ID``,
+``PERMISSION_NAME``.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-cli permissions revoke \
+ some-subject-type some-subject-id \
+ some-resource-type some-resource-id \
+ facade.anastasis.history
+ { }
+ $ libeufin-cli permissions list
+ {
+ "permissions" : [ ]
+ }
+
+This example undoes the effect of the previous (``permissions grant``) example.
+
+
+accounts
+--------
+
+WRITEME
+
+accounts transactions
+^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts task-status
+^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts task-schedule
+^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts task-delete
+^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts submit-payments
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts show-payment
+^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts show
+^^^^^^^^^^^^^
+
+WRITEME
+
+accounts prepare-payment
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts list-tasks
+^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts list-payments
+^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts fetch-transactions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+accounts delete-payment
+^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+
+facades
+-------
+
+WRITEME
+
+facades new-taler-wire-gateway-facade
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+facades new-anastasis-facade
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+WRITEME
+
+facades list
+^^^^^^^^^^^^
+
+WRITEME
+
+
+See Also
+========
+
+.. TODO: libeufin-sandbox(1), libeufin-cli(1).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/libeufin-nexus.1.rst b/manpages/libeufin-nexus.1.rst
new file mode 100644
index 00000000..792c1de6
--- /dev/null
+++ b/manpages/libeufin-nexus.1.rst
@@ -0,0 +1,150 @@
+libeufin-nexus(1)
+#################
+
+.. only:: html
+
+ Name
+ ====
+
+ **libeufin-nexus** - Service to interface to various bank access APIs
+
+
+Synopsis
+========
+
+**libeufin-nexus**
+[**-h** | **--help**]
+[**--version**]
+COMMAND [ARGS...]
+
+Commands: serve, superuser, parse-camt, reset-tables
+
+
+Description
+===========
+
+**libeufin-nexus** is a program that provides a service to interface to
+various bank access APIs, using JSON as the response format.
+It maintains state in its own private database.
+You interact with it through HTTP
+requests either over the network or via a Unix domain socket.
+Related program **libeufin-cli** is the preferred front end
+for that mode of operation.
+There is also a mode where **libeufin-nexus** accepts commands directly,
+useful for doing administrative tasks.
+
+Its options are as follows:
+
+**-h** \| **--help**
+ Print short help on options.
+
+**–version**
+ Print version information.
+
+The interaction model is as follows:
+
+- Configure the nexus with command ``superuser``.
+
+- Start the HTTP server with command ``serve``.
+ Let this run in a shell, writing logs to stderr.
+
+- Interact with **libeufin-nexus**.
+
+- When finished, interrupt the ``serve`` process and clean up with command
+ ``reset-tables``.
+
+The following sections describe each command in detail.
+
+
+superuser
+---------
+
+This command adds a superuser, or changes the password.
+It takes argument ``USERNAME``.
+Option ``--password TEXT`` specifies the password.
+If omitted, **libeufin-nexus** will query interactively for it.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-nexus superuser joe
+
+This creates superuser ``joe`` and interactively queries for the password.
+
+
+parse-camt
+----------
+
+This command parses a camt file and displays the result to stdout.
+It takes argument ``FILENAME``, which names a file in CAMT format.
+Parsing may also display log information to stderr.
+The normal log level is ``DEBUG``.
+To change it, use ``--log-level LEVEL``, where ``LEVEL`` is one of:
+``ERROR``, ``WARN``, ``INFO``, ``DEBUG``, ``TRACE``.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-nexus parse-camt camt53-gls-style-0.xml
+ {
+ "transactions" : [ {
+ "amount" : "EUR:2.35",
+ "creditDebitIndicator" : "DBIT",
+ ...
+ } ]
+ }
+
+
+serve
+-----
+
+This command starts the HTTP server, listening on port 5001.
+To use a different port, use option ``--port INT``.
+To listen, instead, on a Unix domain socket,
+use option ``--with-unix-socket PATH``.
+When both ``--port`` and ``--with-unix-socket`` are given,
+``--with-unix-socket`` takes precedence.
+
+The process runs in the foreground, writing its logs to standard error.
+The normal log level is ``DEBUG``.
+To change it, use ``--log-level LEVEL``, where ``LEVEL`` is one of:
+``ERROR``, ``WARN``, ``INFO``, ``DEBUG``, ``TRACE``.
+
+Before invoking ``serve``, the following environment variable needs to be set:
+
+``LIBEUFIN_NEXUS_DB_CONNECTION``
+ This specifies the database **libeufin-nexus** uses to maintain state.
+ Currently, both Sqlite and PostgreSQL are supported.
+ (Only one needs to be specified.)
+ Examples:
+
+ - ``jdbc:sqlite:/tmp/libeufin-nexus.db``
+ - ``jdbc:postgresql://localhost:5432/libeufindb?user=Foo&password=secret``
+
+Normally, the ``serve`` command runs until interrupted.
+When run in a shell, you can use ``Control-C`` for that.
+
+
+reset-tables
+------------
+
+This command drops all the tables in the internal database.
+(The next time the tables are needed, **libeufin-nexus** creates them
+again, automatically.)
+
+It should only be used when the nexus is quiescent.
+
+
+See Also
+========
+
+.. TODO: libeufin-sandbox(1), libeufin-cli(1).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/libeufin-sandbox.1.rst b/manpages/libeufin-sandbox.1.rst
new file mode 100644
index 00000000..fc1c7f3d
--- /dev/null
+++ b/manpages/libeufin-sandbox.1.rst
@@ -0,0 +1,218 @@
+libeufin-sandbox(1)
+###################
+
+.. only:: html
+
+ Name
+ ====
+
+ **libeufin-sandbox** - Simulate a banking system core
+ with EBICS access to bank accounts
+
+
+Synopsis
+========
+
+**libeufin-sandbox**
+[**-h** | **--help**]
+[**--version**]
+COMMAND [ARGS...]
+
+Commands: serve, reset-tables, config, make-transaction, camt053tick
+default-exchange
+
+
+Description
+===========
+
+**libeufin-sandbox** is a program to simulate a banking system core
+with EBICS access to bank accounts.
+It maintains state in its own private database.
+You interact with it through HTTP
+requests either over the network or via a Unix domain socket.
+Related program **libeufin-cli** is the preferred front end
+for that mode of operation.
+There is also a mode where **libeufin-sandbox** accepts commands directly,
+useful for configuring one or more "demobank" (simulated bank) instances.
+
+Its options are as follows:
+
+**-h** \| **--help**
+ Print short help on options.
+
+**–version**
+ Print version information.
+
+The interaction model is as follows:
+
+.. @MS Is the order of the first two steps correct?
+ Or are some of the commands to be used AFTER ‘serve’ starts?
+ Or is it a mix? (I believe it is a mix, but am not sure.)
+
+- Configure the sandbox with commands ``config``, ``default-exchange``,
+ ``make-transaction``, and ``camt053tick``.
+
+- Start the HTTP server with command ``serve``.
+ Let this run in a shell, writing logs to stderr.
+
+- Point program **libeufin-nexus** at the sandbox.
+
+- Interact with **libeufin-nexus**.
+
+- When finished, interrupt the ``serve`` process and clean up with command
+ ``reset-tables``.
+
+The following sections describe each command in detail.
+
+
+config
+------
+
+This command takes argument ``NAME`` and creates a demobank with that name.
+
+Option ``--currency CUR`` (default: ``EUR``) specifes another currency.
+Option ``--bank-debt-limit N`` (default: 1000000) specifies that
+the bank debt limit should be N (units of currency).
+Similarly, option ``--users-debt-limit N`` (default: 1000) specifies
+that the users debt limit should be N (units of currency).
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-sandbox config default
+
+This creates the demobank ``default`` with currency ``EUR``,
+bank debt limit 1000000, users debt limit 1000,
+and allows registrations.
+
+
+default-exchange
+----------------
+
+This command sets the exchange that a demobank will suggest to wallets.
+(Wallets are of course free to disregard the suggestion and choose
+another exchange.)
+It requires two arguments, ``EXCHANGE-BASEURL`` and ``EXCHANGE-PAYTO``.
+The option ``--demobank NAME`` (default: ``default``) specifies
+which demobank this setting affects.
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-sandbox default-exchange \
+ --demobank bank01 \
+ https://exchange.example.com/ \
+ payto://iban/CH9300762011623852957
+
+This sets the default exchange for demobank ``bank01``.
+It is an error if the demobank does not exist.
+
+
+make-transaction
+----------------
+
+This is a "legacy" command, useful in a previous iteration of LibEuFin
+and for internal testing.
+It creates and records a wire transfer transaction in the database.
+
+It takes two arguments and several required options.
+The arguments are ``AMOUNT``, in ``CUR:X.Y`` format;
+and ``SUBJECT``, a short textual description of the transaction.
+The options are: ``--credit-account LABEL`` and ``--debit-account LABEL``,
+where each LABEL names a bank account for receiving and issuing,
+respectively, the wire transfer.
+The option ``--demobank NAME`` (default: ``default``) specifies
+in which demobank the wire transfer occurs.
+
+.. note::
+
+ If you have not yet called ``config``, this command creates
+ a demobank named ``default`` on its first use. The currency,
+ and bank debt limit have the same defaults as for the ``config``
+ command. The users debt limit, however, defaults to 10000.
+
+FIXME: How to achieve the same result with **libeufin-cli**?
+
+
+camt053tick
+-----------
+
+This command advances the internal time step that the demobank
+uses to group transactions for reporting.
+(Successive transactions will be inserted in a new Camt.053 report.)
+
+For example:
+
+.. code-block:: console
+
+ $ libeufin-sandbox camt053tick
+
+FIXME: How to achieve the same result with **libeufin-cli**?
+
+
+serve
+-----
+
+This command starts the HTTP server, listening on port 5000.
+To use a different port, use option ``--port INT``.
+To listen, instead, on a Unix domain socket,
+use option ``--with-unix-socket PATH``.
+When both ``--port`` and ``--with-unix-socket`` are given,
+``--with-unix-socket`` takes precedence.
+
+.. note::
+
+ If you have not yet called ``config``, this command creates
+ a demobank named ``default`` on its first use. The currency,
+ and bank debt limit have the same defaults as for the ``config``
+ command. The users debt limit, however, defaults to 10000.
+
+The process runs in the foreground, writing its logs to standard error.
+The normal log level is ``DEBUG``.
+To change it, use ``--log-level LEVEL``, where ``LEVEL`` is one of:
+``ERROR``, ``WARN``, ``INFO``, ``DEBUG``, ``TRACE``.
+
+Before invoking ``serve``, the following environment variables need to be set:
+
+``LIBEUFIN_SANDBOX_ADMIN_PASSWORD``
+ The password required for later use by **libeufin-cli**.
+ For testing purposes, you can use option ``--no-auth`` to disable
+ this requirement.
+ (In that case, this environment variable need not be set.)
+
+``LIBEUFIN_SANDBOX_DB_CONNECTION``
+ This specifies the database **libeufin-sandbox** uses to maintain state.
+ Currently, both Sqlite and PostgreSQL are supported.
+ (Only one needs to be specified.)
+ Examples:
+
+ - ``jdbc:sqlite:/tmp/libeufin-sandbox.db``
+ - ``jdbc:postgresql://localhost:5432/libeufindb?user=Foo&password=secret``
+
+Normally, the ``serve`` command runs until interrupted.
+When run in a shell, you can use ``Control-C`` for that.
+
+
+reset-tables
+------------
+
+This command drops all the tables in the internal database.
+(The next time the tables are needed, **libeufin-sandbox** creates them
+again, automatically.)
+
+It should only be used when the sandbox is quiescent.
+
+
+See Also
+========
+
+.. TODO: libeufin-nexus(1), libeufin-cli(1).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/sync-config.1.rst b/manpages/sync-config.1.rst
new file mode 100644
index 00000000..c7dd9eab
--- /dev/null
+++ b/manpages/sync-config.1.rst
@@ -0,0 +1,101 @@
+sync-config(1)
+##############
+
+.. only:: html
+
+ Name
+ ====
+
+ **sync-config** - manipulate Sync configuration file
+
+Synopsis
+========
+
+**sync-config**
+[**-b** *backend* | **--supported-backend=**\ \ *backend*]
+[**-c** *filename* | **--config=**\ \ *filename*]
+[**-f** | **--filename**]
+[**-F** | **--full**]
+[**-h** | **--help**]
+[**-L** *loglevel* | **--loglevel=**\ \ *loglevel*]
+[**-l** *filename* | **--logfile=**\ ‌\ *filename*]
+[**-o** *option* | **--option=**\ \ *option*]
+[**-r** | **--rewrite**]
+[**-S** | **--list-sections**]
+[**-s** *section* | **--section=**\ \ *section*]
+[**-V** *value* | **--value=**\ \ *value*]
+[**-v** | **--version**]
+
+
+Description
+===========
+
+**sync-config** can be used to read or modify Sync configuration files.
+
+**-b** *BACKEND* \| **--supported-backend=**\ \ *BACKEND*
+ Tests whether the specified *BACKEND* is supported by the current installation.
+ The backend must match the name of a plugin, i.e. "namestore_postgres" for
+ the PostgreSQL database backend of the "NAMESTORE" service. If *BACKEND* is
+ supported, sync-config will return a status code of 0 (success), otherwise
+ 77 (unsupported). When this option is specified, no other options may be
+ specified. Specifying this option together with other options will cause
+ sync-config to return a status code of 1 (error).
+
+**-c** *FILENAME* \| **--config=**\ \ *FILENAME*
+ Use the configuration file *FILENAME*.
+
+**-f** \| **--filename**
+ Try to perform expansions as if the option values represent filenames (will
+ also be applied even if the option is not really a filename).
+
+**-F** \| **--full**
+ Write the full configuration file, not just the differences to the defaults.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ \ *LOGLEVEL*
+ Use *LOGLEVEL* for logging.
+ Valid values are ``DEBUG``, ``INFO``, ``WARNING``, and ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-o** *OPTION* \| **--option=**\ \ *OPTION*
+ Which configuration option should be accessed or edited. Required to set a
+ value. If not given, all values of a given section will be printed in the
+ format "OPTION = VALUE".
+
+**-r** \| **--rewrite**
+ Write the configuration file even if nothing changed. Will remove all comments!
+
+**-S** \| **--list-sections**
+ List available configuration sections for use with ``--section``.
+
+**-s** *SECTION* \| **--section=**\ \ *SECTION*
+ Which configuration section should be accessed or edited.
+ Required option.
+
+**-V** *VALUE* \| **--value=**\ \ *VALUE*
+ Configuration value to store in the given section under the given option.
+ Must only be given together with ``-s`` and ``-o`` options.
+
+ Note:
+ Changing the configuration file with ``-V`` will remove comments
+ and may reorder sections and remove ``@INLINE@`` directives.
+
+**-v** \| **--version**
+ Print GNU Taler version number.
+
+
+See Also
+========
+
+sync-dbinit(1), sync-httpd(1), sync.conf(5).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/sync-dbinit.1.rst b/manpages/sync-dbinit.1.rst
new file mode 100644
index 00000000..63cc7ce7
--- /dev/null
+++ b/manpages/sync-dbinit.1.rst
@@ -0,0 +1,65 @@
+sync-dbinit(1)
+##############
+
+.. only:: html
+
+ Name
+ ====
+
+ **sync-dbinit** - initialize the Sync database
+
+
+Synopsis
+========
+
+**sync-dbinit**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-g** | **--garbagecollect**]
+[**-h** | **--help**]
+[**-L** *LOGLEVEL* | **--log=**\ \ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ \ *FILENAME*]
+[**-r** | **--reset**]
+[**-v** | **--version**]
+
+
+Description
+===========
+
+**sync-dbinit** is a command-line tool to initialize the Sync database.
+
+Its options are as follows:
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the Sync commands
+ to operate from *FILENAME*.
+
+**-g** \| **--garbagecollect**
+ Remove state data from database.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--log=**\ \ *LOGLEVEL*
+ Configure logging to use *LOGLEVEL*.
+
+**-l** *FILENAME* \| **--logfile=**\ \ *FILENAME*
+ Configure logging to write logs to *FILENAME*.
+
+**-r** \| **--reset**
+ Reset database. (**DANGEROUS**: All existing data is lost!)
+
+**-v** \| **–version**
+ Print version information.
+
+
+See Also
+========
+
+sync-config(1), sync-httpd(1), sync.conf(5).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/sync-httpd.1.rst b/manpages/sync-httpd.1.rst
new file mode 100644
index 00000000..c09ccb14
--- /dev/null
+++ b/manpages/sync-httpd.1.rst
@@ -0,0 +1,78 @@
+sync-httpd(1)
+#############
+
+.. only:: html
+
+ Name
+ ====
+
+ **sync-httpd** - provide the Sync HTTP interface
+
+
+Synopsis
+========
+
+**sync-httpd**
+[**-A** *USERNAME:PASSWORD* | **--auth=**\ \ *USERNAME:PASSWORD*]
+[**-C** | **--connection-close**]
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-k** *KEYFILE* | **--key=**\ \ *KEYFILE*]
+[**-L** *LOGLEVEL* | **--log=**\ \ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ \ *FILENAME*]
+[**-p** *KEYFILEPASSPHRASE* | **--pass=**\ \ *KEYFILEPASSPHRASE*]
+[**-t** *CERTTYPE* | **--type=**\ \ *CERTTYPE*]
+[**-v** | **--version**]
+
+
+Description
+===========
+
+**sync-httpd** is a command-line tool to provide the Sync HTTP interface.
+
+Its options are as follows:
+
+**-A** *USERNAME:PASSWORD* \| **--auth=**\ \ *USERNAME:PASSWORD*
+ Use the given *USERNAME* and *PASSWORD* for client authentication.
+
+**-C** \| **--connection-close**
+ Force HTTP connections to be closed after each request.
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the Sync commands
+ to operate from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-k** *KEYFILE* \| **--key=**\ \ *KEYFILE*
+ Consult *KEYFILE* for the private TLS key for TLS client authentication.
+
+**-L** *LOGLEVEL* \| **--log=**\ \ *LOGLEVEL*
+ Configure logging to use *LOGLEVEL*.
+
+**-l** *FILENAME* \| **--logfile=**\ \ *FILENAME*
+ Configure logging to write logs to *FILENAME*.
+
+**-p** *KEYFILEPASSPHRASE* \| **--pass=**\ \ *KEYFILEPASSPHRASE*
+ Use *KEYFILEPASSPHRASE* to decrypt the TLS client private key file.
+
+**-t** *CERTTYPE* \| **--type=**\ \ *CERTTYPE*
+ Use *CERTTYPE* as the type of the TLS client certificate.
+ If unspecified, defaults to PEM.
+
+**-v** \| **–version**
+ Print version information.
+
+
+See Also
+========
+
+sync-config(1), sync-dbinit(1), sync.conf(5).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/sync.conf.5.rst b/manpages/sync.conf.5.rst
new file mode 100644
index 00000000..f2a0e6a9
--- /dev/null
+++ b/manpages/sync.conf.5.rst
@@ -0,0 +1,92 @@
+sync.conf(5)
+############
+
+.. only:: html
+
+ Name
+ ====
+
+ **sync.conf** - Sync configuration file
+
+
+Description
+===========
+
+.. include:: ../frags/common-conf-syntax.rst
+
+Files containing default values for many of the options described below
+are installed under ``$PREFIX/share/sync/config.d/``.
+The configuration file given with **-c** to Sync binaries
+overrides these defaults.
+
+A configuration file may include another, by using the ``@INLINE@`` directive,
+for example, in ``main.conf``, you could write ``@INLINE@ sub.conf`` to
+include the entirety of ``sub.conf`` at that point in ``main.conf``.
+
+Be extra careful when using ``sync-config -V VALUE`` to change configuration
+values: it will destroy all uses of ``@INLINE@`` and furthermore remove all
+comments from the configuration file!
+
+
+GLOBAL OPTIONS
+--------------
+
+The following options are from the “[sync]” section.
+This is normally the only section in a sync.conf file.
+
+SERVE
+ This can either be ``tcp`` or ``unix``.
+
+PORT
+ Port on which the HTTP server listens, e.g. 9967.
+ Only used if ``SERVE`` is ``tcp``.
+
+BIND_TO
+ Which IP address should we bind to? E.g. ``127.0.0.1`` or ``::1``
+ for loopback. Can also be given as a hostname. We will bind to
+ the wildcard (dual-stack) if left empty.
+ Only used if ``SERVE`` is ``tcp``.
+
+UNIXPATH
+ Which unix domain path should we bind to?
+ Only used if ``SERVE`` is ``unix``.
+
+UNIXPATH_MODE = 660
+ What should be the file access permissions for ``UNIXPATH``?
+ Only used if ``SERVE`` is ``unix``.
+
+DB
+ Plugin to use for the database, e.g. “postgres”.
+
+ANNUAL_FEE
+ Annual fee for an account.
+ This is in the usual amount syntax, e.g. ``TESTKUDOS:0.1``.
+
+INSURANCE
+ Insurance provided against loss, e.g. ``TESTKUDOS:0.0``.
+
+UPLOAD_LIMIT_MB
+ Upload limit per backup, in megabytes, e.g. ``16``.
+
+FULFILLMENT_URL
+ Fulfillment URL of the SYNC service itself.
+
+PAYMENT_BACKEND_URL
+ Base URL of our payment backend.
+
+API_KEY
+ API key to pass when accessing the merchant backend.
+ This is a secret value.
+
+
+SEE ALSO
+========
+
+sync-dbinit(1), sync-httpd(1), sync-config(1).
+
+
+BUGS
+====
+
+Report bugs by using https://bugs.taler.net/ or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-auditor-offline.1.rst b/manpages/taler-auditor-offline.1.rst
index 58a99066..24785082 100644
--- a/manpages/taler-auditor-offline.1.rst
+++ b/manpages/taler-auditor-offline.1.rst
@@ -24,7 +24,7 @@ Description
===========
**taler-auditor-offline** is a command-line tool to be used by an auditor to
-sign that he is aware of certain keys being used by a exchange. Using this
+sign that he is aware of certain keys being used by an exchange. Using this
signature, the auditor affirms that he will verify that the exchange is
properly accounting for coins of those denominations. The tool takes a list
of subcommands as arguments which are then processed sequentially.
diff --git a/manpages/taler-auditor.1.rst b/manpages/taler-auditor.1.rst
index 62cbf69e..9a5fc52b 100644
--- a/manpages/taler-auditor.1.rst
+++ b/manpages/taler-auditor.1.rst
@@ -15,6 +15,7 @@ Synopsis
[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
[**-h** | **--help**]
[**-i**_|_**--internal**]
+[**-I**_|_**--ignore-not-found**]
[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
[**-m** *MASTER_KEY* | **--exchange-key=**\ ‌\ *MASTER_KEY*]
@@ -46,6 +47,10 @@ matches the exchange’s database. Its options are as follows.
Run additional checks that can only performed on the exchange-internal
database and not the "safe" replicated database at the auditor.
+**-I** \| **--ignore-not-found**
+ Do not fail if the bank says that the exchange bank account does not (yet) exist.
+ Keep trying.
+
**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
``WARNING``, ``ERROR``.
diff --git a/manpages/taler-config.1.rst b/manpages/taler-config.1.rst
index 103c95ef..14a8959b 100644
--- a/manpages/taler-config.1.rst
+++ b/manpages/taler-config.1.rst
@@ -35,7 +35,7 @@ Description
**-b** *BACKEND* \| **--supported-backend=**\ \ *BACKEND*
Tests whether the specified *BACKEND* is supported by the current installation.
The backend must match the name of a plugin, i.e. "namestore_postgres" for
- the Postgres database backend of the "NAMESTORE" service. If *BACKEND* is
+ the PostgreSQL database backend of the "NAMESTORE" service. If *BACKEND* is
supported, taler-config will return a status code of 0 (success), otherwise
77 (unsupported). When this option is specified, no other options may be
specified. Specifying this option together with other options will cause
diff --git a/manpages/taler-exchange-aggregator.1.rst b/manpages/taler-exchange-aggregator.1.rst
index 4a15c227..87a5cd74 100644
--- a/manpages/taler-exchange-aggregator.1.rst
+++ b/manpages/taler-exchange-aggregator.1.rst
@@ -19,6 +19,8 @@ Synopsis
[**-T** *USEC* | **--timetravel**\ \ *USEC*]
[**-t** | **--test**]
[**-v** | **--version**]
+[**-y**_|_**--kyc-off**]
+
Description
===========
@@ -56,6 +58,10 @@ The aggregator uses a special table to lock shards it is working on. If an aggre
**-v** \| **--version**
Print version information.
+**-y** \| **--kyc-off**
+ Run without KYC checks. Talk with your regulator before using this option.
+
+
See Also
========
diff --git a/manpages/taler-exchange-drain.1.rst b/manpages/taler-exchange-drain.1.rst
new file mode 100644
index 00000000..ade7c00e
--- /dev/null
+++ b/manpages/taler-exchange-drain.1.rst
@@ -0,0 +1,56 @@
+taler-exchange-drain(1)
+#########################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-exchange-drain** - drain profits from exchange escrow account
+
+Synopsis
+========
+
+**taler-exchange-drain**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-v** | **--version**]
+
+Description
+===========
+
+**taler-exchange-drain** is used to trigger a wire transfer from the exchange's escrow account to a normal (non-escrowed) bank account of the exchange. The entire drain process is necessary to ensure that the auditor is aware of the
+balance changes arising from an exchange making profits from fees.
+
+To use it, you must first create an upload a 'drain' command using **taler-exchange-offline**. Afterwards this command should be run to actually queue the drain. The actual drain will then be executed by **taler-exchange-transfer**.
+
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the exchange to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-v** \| **--version**
+ Print version information.
+
+See Also
+========
+
+taler-exchange-transfer(1), taler-exchange-offline(1), taler.conf(5).
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net/ or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-exchange-expire.1.rst b/manpages/taler-exchange-expire.1.rst
new file mode 100644
index 00000000..24540ec4
--- /dev/null
+++ b/manpages/taler-exchange-expire.1.rst
@@ -0,0 +1,66 @@
+taler-exchange-expire(1)
+#########################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-exchange-expire** - refund expired purses
+
+Synopsis
+========
+
+**taler-exchange-expire**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-T** *USEC* | **--timetravel=**\ \ *USEC*]
+[**-t** | **--test**]
+[**-v** | **--version**]
+
+Description
+===========
+
+**taler-exchange-expire** is a command-line tool to run refund
+money in purses that were not merged before their expiration time.
+This allows the wallet to recover the funds deposited into the
+purse using a refresh operation.
+
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the exchange to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-T** *USEC* \| **--timetravel=**\ \ *USEC*
+ Modify the system time by *USEC* microseconds.
+ *USEC* may be prefixed with ``+`` or ``-`` (e.g. ``-T +300``).
+ This option is intended for debugging/testing only.
+
+**-t** \| **--test**
+ Run in test mode and exit when idle.
+
+**-v** \| **--version**
+ Print version information.
+
+See Also
+========
+
+taler-exchange-router(1), taler-exchange-httpd(1), taler.conf(5).
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net/ or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-exchange-httpd.1.rst b/manpages/taler-exchange-httpd.1.rst
index 0050aeeb..4c6d984b 100644
--- a/manpages/taler-exchange-httpd.1.rst
+++ b/manpages/taler-exchange-httpd.1.rst
@@ -20,6 +20,7 @@ Synopsis
[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
[**-n** *N* | **--num-threads=**\ \ *N*]
+[**-r**|**--allow-reuse-address**]
[**-T** *USEC* | **--timetravel=**\ \ *USEC*]
[**-t** *SECONDS* | **--timeout=**\ ‌\ *SECONDS*]
[**-v** | **--version**]
@@ -75,8 +76,10 @@ Its options are as follows:
**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
Send logging output to *FILENAME*.
-**-n** *N* \| **--num-threads=**\ \ *N*
- Use *N* threads in the thread pool.
+**-r** \| **--allow-reuse-address**
+ Allow the exchange to re-use the listen port even if another service
+ is already using it. Useful if multiple processes are used to increase
+ processing capacity.
**-T** *USEC* \| **--timetravel=**\ \ *USEC*
Modify the system time by *USEC* microseconds.
diff --git a/manpages/taler-exchange-kyc-tester.1.rst b/manpages/taler-exchange-kyc-tester.1.rst
new file mode 100644
index 00000000..2c3d5f4c
--- /dev/null
+++ b/manpages/taler-exchange-kyc-tester.1.rst
@@ -0,0 +1,85 @@
+taler-exchange-kyc-tester(1)
+############################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-exchange-kyc-tester** - tool to test interaction with KYC provider
+
+Synopsis
+========
+
+**taler-exchange-kyc-tester**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-i** *SECTION_NAME* | **--initiate=**\ ‌\ *SECTION_NAME*]
+[**-u** *ID* | **--user=**\ ‌\ *ID*]
+[**-U** *ID* | **--legitimization=**\ ‌\ *ID*]
+[**-P** | **--print-payto-hash**]
+[**-p** *HASH* | **--payto-hash=**\ ‌\ *HASH*]
+[**-r** *NUMBER* | **--rowid=**\ ‌\ *NUMBER*]
+[**-v** | **--version**]
+[**-w** | **--run-webservice**]
+
+
+Description
+===========
+
+**taler-exchange-kyc-tester** is used to test the interaction between a Taler exchange and a KYC service. The tool can be used to manually trigger the various steps of a KYC process and to observe the interaction with the respective KYC service. It is supposted to help test the configuration of the integration, and *not* required at all during production.
+
+To use it, you must first provide a configuration file with at least one KYC service configured. Some other exchange-specific options, like the PORT for the HTTP service and the BASE_URL under which the Taler exchange will run are also required. You should be able to use exactly the same configuration file that one would usually give to a Taler exchange. Starting with this, the tool allows the simulation of a KYC process. Note that it will not write any information to the database.
+
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the exchange to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-i** *SECTION_NAME* \| **--initiate=**\ ‌\ *SECTION_NAME*
+ Start a fresh KYC process using the KYC provider configured in SECTION_NAME of the configuration. This will output the HTTP URL where the user has to begin the KYC process to the command-line. This is usually the first thing to do when using this tool. Outputs the KYC-logic specific user and legitimization IDs, or NULL if not used by the KYC-logic at the initiation stage.
+
+**-u** *ID* \| **--user=**\ ‌\ *ID*
+ Run the process with ID for the user identifier at the KYC provider. Not useful in conjunction with **-i** as that option will override whatever value is provided here.
+
+**-U** *ID* \| **--legitimization=**\ ‌\ *ID*
+ Run the process with ID for the legitimization process identifier at the KYC provider. Not useful in conjunction with **-i** as that option will override whatever value is provided here.
+
+**-p** *HASH* \| **--payto-hash=**\ ‌\ *HASH*
+ Run the process with HASH as the hash of the payto://-URI that identifies the account or wallet triggering the KYC requirement. If not given, a fresh random value is used. Rarely useful.
+
+**-P** \| **--print-payto-hash**
+ Print the HASH of the payto://-URI used for the KYC simulation this time. Useful if the hash is needed for a subsequent use in conjunction with **-p**.
+
+**-r** *NUMBER* \| **--rowid=**\ ‌\ *NUMBER*
+ Run the process with NUMBER as the database row for the legitimization operation. Rarely useful, except maybe for debugging. Defaults to 42.
+
+**-v** \| **--version**
+ Print version information.
+
+**-w** \| **--run-webservice**
+ Run a simulated Taler exchange HTTP service on the configured port with the ``/kyc-proof/`` and ``/kyc-webhook/`` endpoints.
+
+
+See Also
+========
+
+taler-exchange-httpd(1), taler.conf(5).
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net/ or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-exchange-offline.1.rst b/manpages/taler-exchange-offline.1.rst
index 4641b416..f9a380ad 100644
--- a/manpages/taler-exchange-offline.1.rst
+++ b/manpages/taler-exchange-offline.1.rst
@@ -142,6 +142,47 @@ It outputs the signatures over *all* denomination and signing keys
present in the input, in a format suitable for the ``upload`` subcommand.
+extensions
+----------
+
+This subcommand is responsible for the management of available extensions in
+the exchange.
+
+It consumes the output of the ``download`` subcommand, either from ``stdin`` or
+directly.
+
+It provides the sub-subcommand ``extensions show`` to show the configuration
+for extensions and the ``extensions sign`` command to sign the current
+configuration of extensions, in a format suitable for the ``upload``
+subcommand.
+
+Note that an extension on the exchange will only become activated at runtime
+*after* the extension's configurations has been signed by the offline tool with
+the signing key and the signed configuration been uploaded to the exchange.
+
+drain
+-----
+
+This subcommand allows an exchange operator to transfer the
+profits made from transaction fees to a regular (non-escrowed) bank
+account. Using this command, draining profits from the
+escrow account can be done in such a way that the auditor
+is aware of the special transaction and does not flag the
+resulting balance as fundamentally problematic. Note that
+the drained amounts must still total up to less than the fees
+earned by the exchange.
+
+Arguments to the ``drain`` command are the amount to be drained (in the usual
+Taler amount format), the section of the exchange configuration specifying the
+account to be debited (this argument is currently ignored, and the account is
+purely derived from the wire method and the account being set for debiting),
+and finally the payto://-URI to wire the funds to.
+
+Note that to actually wire the funds, the exchange administrator must run
+**taler-exchange-drain** manually and confirm the operation after the
+``upload`` was completed.
+
+
revoke-denomination
-------------------
@@ -218,12 +259,12 @@ enable-account
--------------
This subcommand
-informs an exchange that it should advertize a bank account as belonging to
+informs an exchange that it should advertise a bank account as belonging to
the exchange on its ``/wire`` endpoint. Note that this does *not* ensure that
the exchange will use this bank account for incoming or outgoing wire
transfers! For this, the **taler-exchange-transfer** and
**taler-exchange-wirewatch** tools must be configured. Furthermore, the bank
-account information advertized could theoretically differ from that which
+account information advertised could theoretically differ from that which
these tool actually use, for example if the public bank account is only a
front for the actual internal business accounts.
@@ -255,13 +296,13 @@ format suitable for the ``upload`` subcommand.
wire-fee
--------
-This subcommand informs an exchange about the desired wire fee (and closing fee)
-structure for particular wire method and a calendar year (!). The tool does not
+This subcommand informs an exchange about the desired wire fee structure (that is, wire, and closing fees)
+for particular wire method and a calendar year (!). The tool does not
permit changing wire fees during a calendar year. Also, once the wire fee has been
set for a calendar year, it cannot be changed.
The subcommand takes the year, wire-method (see RFC 8905, examples include
-``x-taler-bank`` or ``iban``), wire fee and closing fee as arguments.
+``x-taler-bank`` or ``iban``), wire fee, and closing fee as arguments.
Instead of a year, the string ``now`` can be given for the current year
(this is mostly useful for test cases). The wire-method should follow the
GANA registry as given in RFC 8905. The fees must be given in the usual
@@ -273,6 +314,26 @@ It outputs the signature affirming the wire fees, in a format suitable for the
``upload`` subcommand.
+global-fee
+----------
+
+This subcommand informs an exchange about the desired global fee structure and
+related global configuration options for a calendar year (!). The tool does
+not permit changing global fees during a calendar year. Also, once the global
+fee structure has been set for a calendar year, it cannot be changed.
+
+The subcommand takes the year, history fee, account fee, purse fee,
+purse timeout, history expiration and the (free) purse (per)
+account limit as arguments. Instead of a year, the string ``now`` can be
+given for the current year (this is mostly useful for test cases). The fees
+must be given in the usual Taler format of ``CURRENCY:NUMBER.FRACTION``.
+
+The subcommand takes no inputs from ``stdin`` or other subcommands.
+
+It outputs the signature affirming the global fees, in a format suitable for
+the ``upload`` subcommand.
+
+
enable-partner
--------------
diff --git a/manpages/taler-exchange-router.1.rst b/manpages/taler-exchange-router.1.rst
new file mode 100644
index 00000000..e24d2d45
--- /dev/null
+++ b/manpages/taler-exchange-router.1.rst
@@ -0,0 +1,67 @@
+taler-exchange-router(1)
+#########################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-exchange-router** - route purse payments to partner exchanges
+
+Synopsis
+========
+
+**taler-exchange-router**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-T** *USEC* | **--timetravel=**\ \ *USEC*]
+[**-t** | **--test**]
+[**-v** | **--version**]
+
+Description
+===========
+
+**taler-exchange-router** is a NOT YET IMPLEMENTED command-line
+tool to route P2P payments to partner exchanges via wad transfers.
+This will be needed if wallet-to-wallet payments are made between
+wallets where the recipient has performed the KYC at a different
+exchange than the sender. This is currently not supported.
+
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the exchange to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-T** *USEC* \| **--timetravel=**\ \ *USEC*
+ Modify the system time by *USEC* microseconds.
+ *USEC* may be prefixed with ``+`` or ``-`` (e.g. ``-T +300``).
+ This option is intended for debugging/testing only.
+
+**-t** \| **--test**
+ Run in test mode and exit when idle.
+
+**-v** \| **--version**
+ Print version information.
+
+See Also
+========
+
+taler-exchange-expire(1), taler-exchange-httpd(1), taler.conf(5).
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net/ or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-exchange-secmod-cs.1.rst b/manpages/taler-exchange-secmod-cs.1.rst
new file mode 100644
index 00000000..b75ce311
--- /dev/null
+++ b/manpages/taler-exchange-secmod-cs.1.rst
@@ -0,0 +1,76 @@
+taler-exchange-secmod-cs(1)
+###########################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-exchange-secmod-cs** - handle private Clause-Schnorr key operations for a Taler exchange
+
+
+Synopsis
+========
+
+**taler-exchange-secmod-cs**
+[**-c** *FILENAME* | **--config=**\ \ *FILENAME*]
+[**-h** | **--help**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-p** *N* | ,**--parallelism=**\ \ *N*]
+[**-T** *USEC* | **--timetravel=**\ \ *USEC*]
+[**-t** *TIMESTAMP* | **--time=**\ \ *TIMESTAMP*]
+[**-v** | **--version**]
+
+
+Description
+===========
+
+**taler-exchange-secmod-cs** is a command-line tool to
+handle private Clause-Schnorr key operations for a Taler exchange.
+
+FIXME: More details.
+
+Its options are as follows:
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the merchant to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**p** *N* \| **--parallelism=**\ \ *N*
+ Run with *N* worker threads.
+
+**-T** *USEC* \| **--timetravel=**\ \ *USEC*
+ Modify the system time by *USEC* microseconds.
+ *USEC* may be prefixed with ``+`` or ``-`` (e.g. ``-T +300``).
+ This option is intended for debugging/testing only.
+
+**-t** *TIMESTAMP* \| **--time=**\ \ *TIMESTAMP*
+ Pretend it is *TIMESTAMP* for the update.
+ *TIMESTAMP* is a human-readable string (e.g., ``YYYY-MM-DD HH:MM:SS``).
+
+**-v** \| **--version**
+ Print version information.
+
+
+See Also
+========
+
+taler-exchange-httpd(1).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-exchange-wirewatch.1.rst b/manpages/taler-exchange-wirewatch.1.rst
index 08bb6014..00f40d8e 100644
--- a/manpages/taler-exchange-wirewatch.1.rst
+++ b/manpages/taler-exchange-wirewatch.1.rst
@@ -14,6 +14,7 @@ Synopsis
**taler-exchange-wirewatch**
[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
[**-h** | **--help**]
+[**-I**_|_**--ignore-not-found**]
[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
[**-r** | **--reset**]
@@ -36,6 +37,10 @@ Its options are as follows:
**-h** \| **--help**
Print short help on options.
+**-I** \| **--ignore-not-found**
+ Do not fail if the bank says that the exchange bank account does not (yet) exist.
+ Keep trying.
+
**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
``WARNING``, ``ERROR``.
diff --git a/manpages/taler-fakebank-run.1.rst b/manpages/taler-fakebank-run.1.rst
index eb608343..1a23cf24 100644
--- a/manpages/taler-fakebank-run.1.rst
+++ b/manpages/taler-fakebank-run.1.rst
@@ -17,6 +17,7 @@ Synopsis
[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
[**-n** *N* | **--num-threads=**\ \ *N*]
+[**-s** *AMOUNT* | **--signup-bonus=**\ \ *AMOUNT*]
[**-v** | **--version**]
Description
@@ -24,6 +25,8 @@ Description
**taler-fakebank-run** is a command-line tool to run a Taler "bank" API (HTTP REST service). The program is useful to provide the bank functionality for benchmarking or testing when LibEuFin is unavailable or otherwise unsuitable.
+It should be noted that the fakebank will keep a configured number of transactions in memory. If the number of transactions exceeds the configured memory limit, the fakebank will simply discard and overwrite the old entries. At that point, any requests involving such overwritten transactions will fail. Users of the fakebank are responsible for ensuring that the transaction history kept in memory is long enough for the purpose the bank is used for. The default limit is 128k entries.
+
Its options are as follows:
**-C** \| **--connection-close**
@@ -47,6 +50,9 @@ Its options are as follows:
**-n** *N* \| **--num-threads=**\ \ *N*
Use *N* threads in the thread pool.
+**-s** *AMOUNT* \| **--signup-bonus=**\ \ *AMOUNT*
+ Credit newly registered accounts with a balance of *AMOUNT*. Unlike other banks, this initial balance will be created out of thin air and not via a wire transfer from some bank-internal account.
+
**-v** \| **--version**
Print version information.
diff --git a/manpages/taler-helper-auditor-purses.1.rst b/manpages/taler-helper-auditor-purses.1.rst
new file mode 100644
index 00000000..09d23676
--- /dev/null
+++ b/manpages/taler-helper-auditor-purses.1.rst
@@ -0,0 +1,75 @@
+taler-helper-auditor-purses(1)
+##############################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-helper-auditor-purses** - Audit Taler exchange purse handling
+
+
+Synopsis
+========
+
+**taler-helper-auditor-purses**
+[**-c** *FILENAME* | **--config=**\ \ *FILENAME*]
+[**-h** | **--help**]
+[**i** | **--internal**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-m** *KEY* | **--exchange-key=**\ \ *KEY*]
+[**-T** *USEC* | **--timetravel=**\ \ *USEC*]
+[**-v** | **--version**]
+
+
+Description
+===========
+
+**taler-helper-auditor-purses** is a command-line tool to
+audit Taler exchange purse handling.
+
+FIXME: More detail.
+
+Its options are as follows:
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the auditor to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-i** \| **--internal**
+ Perform checks only applicable for exchange-internal audits.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-m** *KEY* \| **--exchange-key=**\ \ *KEY*
+ Use *KEY* (Crockford base32 encoded) as the public key of the exchange.
+
+**-T** *USEC* \| **--timetravel=**\ \ *USEC*
+ Modify the system time by *USEC* microseconds.
+ *USEC* may be prefixed with ``+`` or ``-`` (e.g. ``-T +300``).
+ This option is intended for debugging/testing only.
+
+**-v** \| **--version**
+ Print version information.
+
+
+See Also
+========
+
+taler-auditor(1), taler.conf(5).
+
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-merchant-httpd.1.rst b/manpages/taler-merchant-httpd.1.rst
index 5ea47c12..cf9192f9 100644
--- a/manpages/taler-merchant-httpd.1.rst
+++ b/manpages/taler-merchant-httpd.1.rst
@@ -13,6 +13,7 @@ Synopsis
========
**taler-merchant-httpd**
+[**-a**_|_**--auth**]
[**-C** | **--connection-close**]
[**-c** *FILENAME* | **--config=**\ \ *FILENAME*]
[**-h** | **--help**]
@@ -33,9 +34,9 @@ Options
=======
**-a** *TOKEN* \| **--auth=**\ \ *TOKEN*
- Use TOKEN for initial access control to the merchant backend. The value
+ Use TOKEN for initial access control to the merchant backend. TOKEN must start with the "secret-token:" prefix, as per RFC 8959. The value
given in TOKEN must appear in backoffice requests to the default instance
- of the merchant, i.e. "Authorization: Bearer secret-token:TOKEN" to obtain
+ of the merchant, i.e. "Authorization: Bearer TOKEN" to obtain
access to the merchant backend. Note that setting a passphrase for the
default instance by any means will block future access via TOKEN. This
is basically a way to reset the passphrase protecting access. TOKEN
diff --git a/manpages/taler.conf.5.rst b/manpages/taler.conf.5.rst
index 7d7f5b86..1c70879d 100644
--- a/manpages/taler.conf.5.rst
+++ b/manpages/taler.conf.5.rst
@@ -12,37 +12,7 @@ taler.conf(5)
Description
===========
-The configuration file is line-oriented.
-Blank lines and whitespace at the beginning and end of a line are ignored.
-Comments start with ``#`` or ``%`` in the first column
-(after any beginning-of-line whitespace) and go to the end of the line.
-
-The file is split into sections.
-Every section begins with “[SECTIONNAME]” and
-contains a number of options of the form “OPTION=VALUE”.
-There may be whitespace around the ``=`` (equal sign).
-Section names and options are *case-insensitive*.
-
-The values, however, are *case-sensitive*.
-In particular, boolean values are one of ``YES`` or ``NO``.
-Values can include whitespace by surrounding
-the entire value with ``"`` (double quote).
-Note, however, that there are no escape characters in such strings;
-all characters between the double quotes (including other double quotes)
-are taken verbatim.
-
-Values that represent filenames can begin with a ``/bin/sh``-like
-variable reference.
-This can be simple, such as ``$TMPDIR/foo``, or complex,
-such as ``${TMPDIR:-${TMP:-/tmp}}/foo``.
-See ``[PATHS]`` (below).
-
-Values that represent a time duration are represented as a series of one or
-more ``NUMBER UNIT`` pairs, e.g. ``60 s``, ``4 weeks 1 day``, ``5 years 2 minutes``.
-
-Values that represent an amount are in the usual amount syntax:
-``CURRENCY:VALUE.FRACTION``, e.g. ``EUR:1.50``.
-The ``FRACTION`` portion may extend up to 8 places.
+.. include:: ../frags/common-conf-syntax.rst
Files containing default values for many of the options described below
are installed under ``$TALER_PREFIX/share/taler/config.d/``.
@@ -114,6 +84,7 @@ MASTER_PRIV_FILE
BASE_URL
The base URL under which the exchange can be reached.
Added to wire transfers to enable tracking by merchants.
+ Used by the KYC logic when interacting with OAuth 2.0.
AGGREGATOR_IDLE_SLEEP_INTERVAL
For how long should the taler-exchange-aggregator sleep when it is idle
@@ -140,6 +111,9 @@ SIGNKEY_LEGAL_DURATION
MAX_KEYS_CACHING
For how long should clients cache ``/keys`` responses at most?
+MAX_REQUESTS
+ How many requests should the HTTP server process at most before committing suicide?
+
TERMS_DIR
Directory where the terms of service of the exchange operator can be fund.
The directory must contain sub-directories for every supported language,
@@ -174,19 +148,39 @@ PRIVACY_DIR
PRIVACY_ETAG
Works the same as ``TERMS_ETAG``, just for the privacy policy.
-KYC_MODE
- Set to "NONE" to disable KYC for this exchange (but check with your lawyer first).
- Set to "OAUTH2" to use OAuth2 for KYC.
+
+EXCHANGE KYC PROVIDER OPTIONS
+-----------------------------
+
+The following options must be in the section "[kyc-provider-XXX]" sections.
+
+COST
+ Relative cost of the KYC provider, non-negative number.
+LOGIC
+ API type of the KYC provider.
+USER_TYPE
+ Type of user this provider is for, either INDIVIDUAL or BUSINESS.
+PROVIDED_CHECKS
+ List of checks performed by this provider. Space-separated names of checks, must match check names in legitimization rules.
EXCHANGE KYC OAUTH2 OPTIONS
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The following options must be in the section "[kyc-provider-XXX]" sections with "LOGIC = oauth2".
+
+
+KYC_OAUTH2_VALIDITY
+ Duration (e.g. "12 months") of the validity of the performed KYC check. Can be "forever".
-The following options must be in the section "[exchange-kyc-oauth2]".
+KYC_OAUTH2_AUTH_URL
+ URL of the OAuth2 endpoint to be used for KYC checks. Requires KYC_ENABLED to be "OAUTH2". Example: "http://localhost:8888/oauth/v2/login" (or "/token")
+KYC_OAUTH2_LOGIN_URL
+ URL of the OAuth2 endpoint to be used for KYC checks. Requires KYC_ENABLED to be "OAUTH2". Example: "http://localhost:8888/oauth/v2/login"
-KYC_OAUTH2_URL
- URL of the OAuth2 endpoint to be used for KYC checks. Requires KYC_ENABLED to be "OAUTH2".
+KYC_OAUTH2_INFO_URL
+ URL of the endpoint where the OAuth 2.0 token can be used to download the user's details. Requires KYC_ENABLED to be "OAUTH2". Example: "http://localhost:8888/api/user/me"
KYC_OAUTH2_CLIENT_ID
Client ID of the exchange when it talks to the KYC OAuth2 endpoint. Requires KYC_ENABLED to be "OAUTH2".
@@ -194,6 +188,29 @@ KYC_OAUTH2_CLIENT_ID
KYC_OAUTH2_CLIENT_SECRET
Client secret of the exchange to use when talking to the KYC Oauth2 endpoint. Requires KYC_ENABLED to be "OAUTH2".
+KYC_OAUTH2_POST_URL
+ URL to which the exchange will redirect the client's browser after successful authorization/login for the KYC process.
+
+
+EXCHANGE KYC KYCAID OPTIONS
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The following options must be in the section "[kyc-provider-XXX]" sections with "LOGIC = kycaid".
+
+
+KYC_KYCAID_VALIDITY
+ Duration (e.g. "12 months") of the validity of the performed KYC check. Can be "forever".
+
+KYC_KYCAID_AUTH_TOKEN
+ Authentication token to access the KYC service.
+
+KYC_KYCAID_FORM_ID
+ ID that specifies the form to use for the KYC process.
+
+KYC_KYCAID_POST_URL
+ URL to which the exchange will redirect the client's browser after successful authorization/login for the KYC process.
+
+
EXCHANGE OFFLINE SIGNING OPTIONS
--------------------------------
@@ -250,6 +267,34 @@ Note that the **taler-exchange-secmod-rsa** also evaluates the ``[coin_*]``
configuration sections described below.
+EXCHANGE CS CRYPTO HELPER OPTIONS
+---------------------------------
+
+The following options must be in the section "[taler-exchange-secmod-cs]".
+
+LOOKAHEAD_SIGN
+ How long do we generate denomination and signing keys ahead of time?
+
+OVERLAP_DURATION
+ How much should validity periods for coins overlap?
+ Should be long enough to avoid problems with
+ wallets picking one key and then due to network latency
+ another key being valid. The ``DURATION_WITHDRAW`` period
+ must be longer than this value.
+
+SM_PRIV_KEY
+ Where should the security module store its long-term private key?
+
+KEY_DIR
+ Where should the security module store the private keys it manages?
+
+UNIXPATH
+ On which path should the security module listen for signing requests?
+
+Note that the **taler-exchange-secmod-cs** also evaluates the ``[coin_*]``
+configuration sections described below.
+
+
EXCHANGE EDDSA CRYPTO HELPER OPTIONS
------------------------------------
@@ -291,6 +336,14 @@ IDLE_RESERVE_EXPIRATION_TIME
LEGAL_RESERVE_EXPIRATION_TIME
After what time do we forget about (drained) reserves during garbage collection?
+AGGREGATOR_SHIFT
+ Delay between a deposit being eligible for aggregation and
+ the aggregator actually triggering.
+
+DEFAULT_PURSE_LIMIT
+ Number of concurrent purses that a reserve may have active
+ if it is paid to be opened for a year.
+
EXCHANGE POSTGRES BACKEND DATABASE OPTIONS
------------------------------------------
@@ -393,8 +446,14 @@ FEE_REFUND
What fee is charged for refunds? When a coin is refunded, the deposit
fee is returned. Instead, the refund fee is charged to the customer.
+CIPHER
+ What cryptosystem should be used? Must be set to either "CS" or "RSA".
+ The respective crypto-helper will then generate the keys for this
+ denomination.
+
RSA_KEYSIZE
- What is the RSA keysize modulos (in bits)?
+ What is the RSA keysize modulos (in bits)? Only used if "CIPHER=RSA".
+
MERCHANT OPTIONS
----------------
@@ -508,6 +567,18 @@ CONFIG
"taler" database. Testcases use “talercheck”.
+Bank Options
+------------
+
+The following options must be in section "[bank]" for the taler-fakebank-run(1) command. They are not used by the exchange or LibEuFin!
+
+HTTP_PORT
+ On which TCP port should the (fake)bank offer its REST API.
+RAM_LIMIT
+ This gives the number of transactions to keep in memory. Older transactions will be overwritten and history requests for overwritten transactions will fail.
+
+
+
SEE ALSO
========
diff --git a/merchant-spec/public-orders-get.ts b/merchant-spec/public-orders-get.ts
index 4a98aab4..8a5df17e 100644
--- a/merchant-spec/public-orders-get.ts
+++ b/merchant-spec/public-orders-get.ts
@@ -122,7 +122,7 @@ function respForbidden(req: Req): Resp {
}
function respAlreadyPaid(req: Req, alreadyPaidOrd: MerchantOrderInfo): Resp {
- // This could be called with an empty fulfillment URL, but that doens't
+ // This could be called with an empty fulfillment URL, but that doesn't
// really make sense for the client's perspective.
if (req.accept === "html") {
return {
diff --git a/python-guidelines.rst b/python-guidelines.rst
index f08b563f..8a644ced 100644
--- a/python-guidelines.rst
+++ b/python-guidelines.rst
@@ -47,7 +47,7 @@ Formatting
----------
* We follow `pep8 <https://www.python.org/dev/peps/pep-0008/>`__.
-* Code should be auto-formatted wich `black <https://github.com/psf/black>`__.
+* Code should be auto-formatted with `black <https://github.com/psf/black>`__.
Distro Packaging
diff --git a/taler-auditor-manual.rst b/taler-auditor-manual.rst
index 58dc9463..e7aecb84 100644
--- a/taler-auditor-manual.rst
+++ b/taler-auditor-manual.rst
@@ -65,7 +65,7 @@ all, the goal is to detect nerfarious activity of the exchange operator,
which cannot be effectively done on a machine controlled by the exchange
operator.
-For this, every auditor needs to operate a Postgres database. The data
+For this, every auditor needs to operate a PostgreSQL database. The data
collected will include sensitive information about Taler users, including
withdrawals made by consumers and income received by merchants. As a result,
the auditor is expected to provide high confidentiality for the database. In
@@ -118,7 +118,7 @@ third parties to verify one's own work.
The Taler software stack for an auditor consists of the following
components:
-- DBMS: Postgres
+- DBMS: PostgreSQL
The auditor requires a DBMS to store a local copy of the transaction history for
the Taler exchange, as well as for its own internal bookkeeping and checkpointing.
@@ -128,7 +128,7 @@ components:
concern that must be addressed manually. The software only verifies the content
of a well-formed exchange database (well-formed with respect to SQL).
For now, the GNU Taler reference implementation
- only supports Postgres, but the code could be easily extended to
+ only supports PostgreSQL, but the code could be easily extended to
support another DBMS.
- The auditor Web service
@@ -183,6 +183,8 @@ Installing from source
Please install the following packages before proceeding with the
exchange compilation.
+- Python3 module ``jinja2``
+
.. include:: frags/list-of-dependencies.rst
- GNU Taler exchange (from `download directory <http://ftpmirror.gnu.org/taler/>`__,
@@ -199,6 +201,8 @@ the exchange (which includes the code for the auditor).
.. include:: frags/installing-taler-exchange.rst
+.. include:: frags/install-before-check.rst
+
Installing the GNU Taler binary packages on Debian
--------------------------------------------------
@@ -264,7 +268,7 @@ Additionally, there are two canonical system users of relevance (which your
distribution would typically create for you):
* www-data --- runs the HTTPS frontend (usually nginx or Apache)
- * postgres --- runs the Postgres database
+ * postgres --- runs the PostgreSQL database
Databases and users
@@ -581,7 +585,7 @@ as illustrated in the following figure:
.. image:: replication.png
-First, the exchange should use standard Postgres replication features to
+First, the exchange should use standard PostgreSQL replication features to
enable the auditor to obtain a full copy of the exchange's database.
Second, the auditor should make a "trusted" local copy, ensuring that it
never replicates malicious changes using ``taler-auditor-sync``. Both
@@ -591,7 +595,7 @@ We note that as a result of these steps, the auditor will have three
databases: its own production primary database (as configured in
``auditordb-postgres``), its on production copy of the exchange's database
(``exchangedb-postgress``), and a third, untrusted "ingres" copy of the
-exchange database. The untrusted database should run as a separate Postgres
+exchange database. The untrusted database should run as a separate PostgreSQL
instance and is only accessed via ``taler-auditor-sync`` and the replication
mechanism driven by the exchange operator.
@@ -603,7 +607,7 @@ Ingres operation should be done using the ``auditor-ingres`` user --- or
depending on the setup parts of the operation may be done by the ``postgres``
user directly.
-The full copy can be obtained in various ways with Postgres. It is
+The full copy can be obtained in various ways with PostgreSQL. It is
possible to use log shipping with streaming replication as described
in https://www.postgresql.org/docs/13/warm-standby.html, or to use
logical replication, as described in
@@ -612,7 +616,7 @@ that asynchronous replication should suffice.
The resulting auditor database should be treated as read-only on the auditor
side. The ``taler-exchange-dbinit`` tool can be used to setup the schema, or
-the schema can be replicated using Postgres's standard mechanisms. The same
+the schema can be replicated using PostgreSQL's standard mechanisms. The same
applies for schema upgrades: if logical replication is used (which does not
replicate schema changes), ``taler-exchange-dbinit`` can be used to migrate
the schema(s) in both the ingres and production copies of the exchange's
@@ -629,14 +633,14 @@ to perform database replication. This is done using:
$ echo "CREATE PUBLICATION $NAME FOR ALL TABLES;" | psql taler-exchange
The exchange must share the password of the publication with the auditor. A
-good ``$NAME`` relates to the auditor's buisness unit name. A secure tunnel
+good ``$NAME`` relates to the auditor's business unit name. A secure tunnel
must be setup between the exchange and the auditor, for example using SSH or
Wireguard.
It is also necessary to edit ``main.cf`` of the exchange and on the auditor
side to enable logical replication. If an exchange has multiple auditors, it
should setup multiple ``egress`` accounts. The exchange must ensure that
-the following lines are in the ``main.cf`` Postgres configuaration (the port
+the following lines are in the ``main.cf`` PostgreSQL configuration (the port
may differ) to enable replication over the network:
.. code-block::
@@ -646,7 +650,7 @@ may differ) to enable replication over the network:
wal_level= logical
Equally, the auditor must configure logical replication in the ``main.cf``
-Postgres configuaration:
+PostgreSQL configuration:
.. code-block::
@@ -673,7 +677,7 @@ system must subscribe:
$ echo "CREATE PUBLICATION $NAME FOR ALL TABLES;" | psql taler-exchange
-For details, we refer to the Postgres manual.
+For details, we refer to the PostgreSQL manual.
.. note::
@@ -682,9 +686,9 @@ For details, we refer to the Postgres manual.
``DROP`` operations on the tables. Hence, the auditor cannot rely upon the
exchange's primary copy to respect schema constraints, especially as we
have to presume that the exchange could act maliciously. Furthermore, it
- is unclear to what degree Postgres database replication mechanisms are
+ is unclear to what degree PostgreSQL database replication mechanisms are
robust against a malicious master database. Thus, the auditor should
- isolate its primary copy of the exchange database, including the Postgres
+ isolate its primary copy of the exchange database, including the PostgreSQL
process, from its actual operational data.
@@ -704,7 +708,7 @@ While ``taler-auditor-sync`` could in theory be run directly against the
exchange's production system, this is likely a bad idea due to the high
latency from the network between auditor and exchange operator. Thus, we
recommend first making an "untrusted" ingress copy of the exchange's
-production database using standard Postgres tooling, and then using
+production database using standard PostgreSQL tooling, and then using
``taler-auditor-sync`` to create a second "safe" copy. The "safe" copy used
by the production system should also run under a different UID.
diff --git a/taler-backoffice-manual.rst b/taler-backoffice-manual.rst
deleted file mode 100644
index 3042845d..00000000
--- a/taler-backoffice-manual.rst
+++ /dev/null
@@ -1,135 +0,0 @@
-.. _Top:
-
-Back-office Web service manual
-###############################
-
-.. _Introduction:
-
-Introduction
-============
-
-The back-office Web service allows a merchant to check on the status of
-their Taler transactions. Merchants can check if and when they were paid
-by a wire transfer for coins deposited at the exchange. Also, given a
-wire transfer they have received, they can ask which Taler transactions
-correspond to the wire transfer.
-
-This manual guides merchant system administrators through the
-installation and configuration of this Web service.
-
-.. _Installation:
-
-Installation
-============
-
-The back-office Web service code is available at
-``git://taler.net/backoffice``. The application can be installed in a
-GNU-ish fashion.
-
-.. code-block:: console
-
- # Get the code:
- $ git clone git://taler.net/backoffice
-
- # Bootstrap and configure
- $ cd backoffice
- $ ./bootstrap
- # This step will check if the system is ready to
- # allow the installation.
- $ ./configure --prefix=<PREFIX>
- $ make install
-
-Note that to make the application work ``<PREFIX>/bin`` must be included
-in the ``$PATH``, and ``<PREFIX>/lib/python3.7/site-packages/`` in
-``$PYTHONPATH``.
-
-.. _Configuration:
-
-Configuration
-=============
-
-The following information must be provided in the configuration: on
-which address the backend should serve the Web site, which currency is
-used, and which merchant backend this Web service will work with.
-
-The merchant backend is an important agent, as it is responsible for
-returning information about transactions and wire transfers. The
-backoffice Web service is merely a frontend for it. A separate manual
-(available at https://docs.taler.net/merchant/backend/html/manual.html)
-describes the installation and configuration of the merchant backend.
-
-Assuming the reader is familiar with configuration in Taler (if not,
-read:
-https://docs.taler.net/exchange/html/taler-exchange.html#Configuration-format),
-a working configuration example is the following one:
-
-.. code-block:: ini
-
- [taler]
- # will be EUR, USD, or whatever currency the merchant
- # works with.
- currency = KUDOS
-
- # each back-office Web service is associated with a "frontend
- # name": this name instructs the application which configuration
- # section is going to be read. Thus <name> is the "frontend name"
- # and must be specified on the command line via the "--frontend"
- # option. See the 'Run' chapter for more details on launching the
- # application.
- [backoffice-<name>]
-
- # This option sets the way the backoffice communicates
- # when it is instructed to operate via UWSGI. Therefore,
- # <how> can be: TCP or UNIX. If TCP is given, then the
- # additional UWSGI_PORT option becomes necessary.
- uwsgi_serve = <how>
-
- # those options will be read if the Web site is served via
- # WSGI.
- uwsgi_unixpath_mode = 660
- uwsgi_unixpath = /path/to/backoffice-<name>.uwsgi
- uwsgi_unixmode = 666
-
- # If UWSGI_SERVE were set as 'TCP', then the following option
- # would have been necessary. It instructs the backoffice service
- # about which TCP port should be listened on, to communicate over
- # UWSGI.
- # uwsgi_port = 8080
-
- # this option will be read if the Web site is served via
- # HTTP.
- http_port = 5959
-
- # specifies which merchant backend is going to be used.
- backend = http://backend.test.taler.net/
-
- # Informally speaking, each instance points to both a private
- # key that can sign proposals and a bank account that can receive
- # wire transfers by some exchange.
-
- # Here, <instance_i> is just a string (with no spaces) that will
- # make the referenced instance be traceable by the back-office Web
- # application.
-
- instances = <instance_1> <instance_2> ..
-
-.. _Launching-the-backoffice:
-
-Launching the backoffice
-========================
-
-The following example shows how to run the Web service.
-
-.. code-block:: console
-
- # Such invocation will work only if the configuration contains
- # a section called "[backoffice-myshop]" which looks like the
- # example above.
-
- # As of serving, the Web site will be available via HTTP, at the
- # port specified in the configuration option "http_port", at localhost.
-
- $ taler-merchant-backoffice --frontend myshop serve-http
-
-Other options, such as those to serve the site via WSGI, are explained
-in the man page and can be listed using the ``--help`` argument.
diff --git a/taler-bank-manual.rst b/taler-bank-manual.rst
deleted file mode 100644
index 6e282ba7..00000000
--- a/taler-bank-manual.rst
+++ /dev/null
@@ -1,71 +0,0 @@
-GNU Taler bank manual
-#####################
-
-Introduction
-============
-
-About GNU Taler
----------------
-
-GNU Taler is an open protocol for an electronic payment system with a
-free software reference implementation. GNU Taler offers secure, fast
-and easy payment processing using well understood cryptographic
-techniques. GNU Taler allows customers to remain anonymous, while
-ensuring that merchants can be held accountable by governments. Hence,
-GNU Taler is compatible with anti-money-laundering (AML) and
-know-your-customer (KYC) regulation, as well as data protection
-regulation (such as GDPR).
-
-About this manual
------------------
-
-This manual documents how the demonstrator bank interoperates with the
-other GNU Taler components. The demonstrator bank implements a simple
-closed banking system for the purpose of illustrating how GNU Taler
-works in the Taler demo. It could also be used as a starting point for a
-local/regional currency. Finally, “real” banks might use it as a
-reference implementation for a tight integration with the GNU Taler
-wallet.
-
-Headless Testing API Reference
-==============================
-
-The demonstrator bank offers the following APIs to allow automated testing. These APIs should
-be switched off during a production deployment.
-
-
-.. _bank-register:
-.. http:post:: /register
-
- This API provides programmatic user registration at the bank.
-
- **Request** The body of this request must have the format of a
- `BankRegistrationRequest`.
-
- **Response**
-
- :http:statuscode:`200 OK`:
- The new user has been correctly registered.
- :http:statuscode:`409 Conflict`:
- The username requested by the client is not available anymore.
- :http:statuscode:`400 Bad request`:
- Unacceptable characters were given for the username. See
- https://docs.djangoproject.com/en/2.2/ref/contrib/auth/#django.contrib.auth.models.User.username
- for the accepted character set.
-
-**Details**
-
-.. ts:def:: BankRegistrationRequest
-
- interface BankRegistrationRequest {
-
- // Username to use for registration; max length is 150 chars.
- username: string;
-
- // Password to associate with the username. Any characters and
- // any length are valid; next releases will enforce a minimum length
- // and a safer characters choice.
- password: string;
- }
-
-
diff --git a/taler-developer-manual.rst b/taler-developer-manual.rst
index fdb2a81c..c348a1d5 100644
--- a/taler-developer-manual.rst
+++ b/taler-developer-manual.rst
@@ -37,6 +37,37 @@ Developer's Manual
Fundamentals
============
+Testing Tools
+-------------
+
+For full ``make check`` support, install these programs:
+
+- `jq <https://github.com/stedolan/jq>`__
+- `curl <http://curl.haxx.se>`__
+- `faketime <https://github.com/wolfcw/libfaketime>`__
+
+The ``make check`` should be able to function without them, but
+their presence permits some tests to run that would otherwise be skipped.
+
+Manual Testing Database Reset
+-----------------------------
+
+Sometimes ``make check`` will fail with some kind of database (SQL)
+error, perhaps with a message like ``OBJECT does not exist`` in the
+``test-suite.log`` file, where ``OBJECT`` is the name of a table or function.
+In that case, it may be necessary to reset the ``talercheck`` database
+with the commands:
+
+.. code-block:: console
+
+ $ dropdb talercheck
+ $ createdb talercheck
+
+This is because, at the moment, there is no support for
+doing these steps automatically in the ``make check`` flow.
+
+(If ``make check`` still fails after the reset, file a bug report as usual.)
+
Bug Tracking
------------
@@ -149,6 +180,70 @@ all developers should probably subscribe to the low-volume Taler mailinglist.
There are separate low-volume mailinglists for gnunet-developers (@gnu.org)
and for libmicrohttpd (@gnu.org).
+What to put in bootstrap
+------------------------
+
+Each repository has a ``bootstrap`` script, which contains commands for the
+developer to run after a repository checkout (i.e., after ``git clone`` or
+``git pull``).
+Typically, this updates and initializes submodules, prepares the tool chain,
+and runs ``autoreconf``.
+The last step generates the ``configure`` script, whether for immediate use or
+for inclusion in the distribution tarball.
+
+One common submodule is ``contrib/gana``, which pulls from the
+`GNUnet GANA repository <https://git.gnunet.org/gana.git/>`__.
+For example, in the
+`Taler exchange repository <https://git.taler.net/exchange.git>`__,
+the bootstrap script eventually runs the ``git submodule update`` command
+early on, and later runs script ``./contrib/gana.sh``, which in turn
+runs script ``./contrib/gana-update.sh``, which performs various tasks,
+such as generating the file ``src/include/taler_signatures.h``.
+
+Thus, to update that file, you need to:
+
+- (in GANA repo) Find a suitable (unused) name and number for the Signature
+ Purposes database.
+
+- Add it to GANA, in ``gnunet-signatures/registry.rec``.
+ (You can check for uniqueness with the ``recfix`` utility.)
+
+- Commit the change, and push it to the GANA Git repo.
+
+- (in Taler Repo) Change directory to ``contrib/gana`` and run
+ command ``git pull`` there.
+
+- Bootstrap, configure, do ``make install``, ``make check``, etc.
+ (Basically, make sure the change does not break anything.)
+
+- Commit the submodule change, and push it to the Taler exchange Git repo.
+
+A similar procedure is required for other databases in GANA.
+See file ``README`` in the various directories for specific instructions.
+
+
+Debian and Ubuntu Repositories
+==============================
+
+We package our software for Debian and Ubuntu.
+
+Nightly Repositories
+--------------------
+
+To try the latest, unstable and untested versions of packages,
+you can add the nightly package sources.
+
+.. code-block:: shell-session
+
+ # For Ubuntu (focal-fossa)
+ $ echo "deb https://deb.taler.net/apt-nightly focal-taler-nightly main" > /etc/apt/sources.list.d/taler.list
+
+ # For Debian (bullseye)
+ $ echo "deb https://deb.taler.net/apt-nightly bullseye-taler-nightly main" > /etc/apt/sources.list.d/taler.list
+
+ # Both: Install signing key for nightly packages
+ $ wget -O - https://taler.net/taler-systems-nightly.gpg.key | apt-key add -
+
Language-Specific Guidelines
============================
@@ -189,29 +284,63 @@ serve Taler on the Internet:
- ``taler-internal``: serves ``*.int.taler.net``, and does *NOT* get
automatically built.
-The following two users are *never* automatically built, and they both
-serve ``*.demo.taler.net``. At any given time, only one is active and
-serves the HTTP requests from the outside; the other one can so be
-compiled without any downtime. If the compilation succeeds, the inactive
-user can be switched to become active (see next section), and vice versa.
-
-- ``demo-blue``
-- ``demo-green``
+- ``demo``: serves ``*.demo.taler.net``. Never automatically built.
Demo Upgrade Procedure
======================
+#. Login as the ``demo`` user on ``gv.taler.net``.
+#. Pull the latest ``deployment.git`` code.
+#. Navigate to the ``deployment.git/docker/demo`` directory.
+#. Refer to the README, or the smaller cheat sheet below.
+
+The deployment is based on rootless Docker, managed by the
+SystemD unit in userspace: ``docker.service``. The running daemon
+is reached by every Docker command at the address held into the
+``DOCKER_HOST`` environment variable. Normally, it points to
+``unix:///run/user/$(id -u)/docker.sock``. Such variable is automatically
+exported by ``~/.bashrc``.
+
+.. note::
+
+ Should the rootless Docker be installed, run the following command
+ or consult the `official documentation <https://docs.docker.com/engine/security/rootless/>`_.
+
+ .. code-block:: console
+
+ $ curl -fsSL https://get.docker.com/rootless | sh
+
Upgrading the ``demo`` environment should be done with care, and ideally be
coordinated on the mailing list before. It is our goal for ``demo`` to always
run a "working version" that is compatible with various published wallets.
-
-Before deploying on ``demo``, the same version of all components **must**
-be deployed *and* tested on ``int``.
-
Please use the :doc:`demo upgrade checklist <checklist-demo-upgrade>` to make
sure everything is working.
+Nginx is already configured to reach the services as exported by
+Docker Compose.
+
+
+Cheat sheet
+-----------
+
+All commands run from deployment.git/docker/demo.
+
+.. code-block:: console
+
+ # Start services.
+ $ docker-compose start --remove-orphans -d
+
+ # Stop services.
+ $ docker-compose stop
+
+ # Build base image (without tags-file builds master)
+ $ ./build_base.sh images/base/Dockerfile [tags-file]
+ # Build all the services based on the latest base image
+ $ docker-compose build
+
+ # View live logs of the daemonized services.
+ $ docker-compose logs
Tagging components
------------------
@@ -228,100 +357,6 @@ All Taler components must be tagged with git before they are deployed on the
SS = serial
-Environment Layout
-------------------
-
-Environments have the following layout:
-
-.. code-block:: none
-
- $HOME/
- deployment (deployment.git checkout)
- envcfg.py (configuration of the Taler environment)
- activate (bash file, sourced to set environment variables)
- logs/ (log files)
- local/ (locally installed software)
- sources/ (sources repos of locally build components)
- sockets/ (unix domain sockets of running components)
- taler-data (on-disk state, public and private keys)
- .config/taler.conf (main Taler configuration file)
-
-On ``demo-blue`` and ``demo-green``, ``taler-data`` is a symlink pointing to ``$HOME/demo/shared-data``
-instead of a directory.
-
-
-Using envcfg.py
----------------
-
-The ``$HOME/envcfg.py`` file contains (1) the name of the environment and (2) the version
-of all components we build (in the form of a git rev).
-
-The ``envcfg.py`` for demo looks like this:
-
-.. code-block:: python
-
- env = "demo"
- tag = "demo-2019-10-05-01:
- tag_gnunet = tag
- tag_libmicrohttpd = tag
- tag_exchange = tag
- tag_merchant = tag
- tag_bank = tag
- tag_twister = tag
- tag_landing = tag
- tag_donations = tag
- tag_blog = tag
- tag_survey = tag
- tag_backoffice = tag
- tag_sync = tag
-
-Currently only the variables ``env`` and ``tag_${component}`` are used.
-
-When deploying to ``demo``, the ``envcfg.py`` should be committed to ``deployment.git/envcfg/envcfg-demo-YYYY-MM-DD-SS.py``.
-
-
-Bootstrapping an Environment
-----------------------------
-
-.. code-block:: console
-
- $ git clone https://git.taler.net/deployment.git ~/deployment
- $ cp ~/deployment/envcfg/$ENVCFGFILE ~/envcfg.py
- $ ./deployment/bin/taler-deployment bootstrap
- $ source ~/activate
- $ taler-deployment build
- $ taler-deployment-prepare
- $ taler-deployment-start
- $ taler-deployment-arm -I # check everything works
- # The following command sets up the 'blog' and 'donations' instances.
- $ taler-config-instances
-
-
-Upgrading an Existing Environment
----------------------------------
-
-.. code-block:: console
-
- $ rm -rf ~/sources ~/local
- $ git -C ~/deployment pull
- $ cp ~/deployment/envcfg/$ENVCFGFILE ~/envcfg.py
- $ taler-deployment bootstrap
- $ taler-deployment build
- $ taler-deployment-prepare
- $ taler-deployment-restart
- $ taler-deployment-arm -I # check everything works
-
-Switching Demo Colors
----------------------
-
-As the ``demo`` user, to switch to color ``${COLOR}``,
-run the following script from ``deployment/bin``:
-
-.. code-block:: console
-
- $ taler-deployment switch-demo
-
-
Environments and Builders on taler.net
======================================
@@ -353,12 +388,46 @@ Best Practices:
Then make sure there is a WORKER defined in master.cfg like: ``worker.Worker("<username>", "<password>")``
+Test builder
+------------
+
+This builder (``test-builder``) compiles and starts every Taler component.
+The associated worker is run by the ``taler-test`` Gv user, via the SystemD
+unit ``buildbot-worker-taler``. The following commands start/stop/restart
+the worker:
+
+.. code-block::
+
+ systemctl --user start buildbot-worker-taler
+ systemctl --user stop buildbot-worker-taler
+ systemctl --user restart buildbot-worker-taler
+
+.. note::
+ the mentioned unit file can be found at ``deployment.git/systemd-services/``
+
+Wallet builder
+--------------
+
+This builder (``wallet-builder``) compiles every Taler component
+and runs the wallet integration tests. The associated worker is
+run by the ``walletbuilder`` Gv user, via the SystemD unit ``buildbot-worker-wallet``.
+The following commands start/stop/restart the worker:
+
+.. code-block::
+
+ systemctl --user start buildbot-worker-wallet
+ systemctl --user stop buildbot-worker-wallet
+ systemctl --user restart buildbot-worker-wallet
+
+.. note::
+ the mentioned unit file can be found at ``deployment.git/systemd-services/``
+
Documentation Builder
---------------------
All the Taler documentation is built by the user ``docbuilder`` that
runs a Buildbot worker. The following commands set the ``docbuilder`` up,
-starting with a empty home directory.
+starting with an empty home directory.
.. code-block:: console
@@ -380,7 +449,7 @@ Website Builder
Taler Websites, ``www.taler.net`` and ``stage.taler.net``, are built by the
user ``taler-websites`` by the means of a Buildbot worker. The following
-commands set the ``taler-websites`` up, starting with a empty home directory.
+commands set the ``taler-websites`` up, starting with an empty home directory.
.. code-block:: console
@@ -417,47 +486,6 @@ by Buildbot.
The results are then published at ``https://lcov.taler.net/``.
-Service Checker
----------------
-
-The user ``demo-checker`` runs periodic checks to see if all the
-``*.demo.taler.net`` services are up and running. It is driven by
-Buildbot, and can be bootstrapped as follows.
-
-.. code-block:: console
-
- # Log-in as the 'demo-checker' user
-
- $ cd $HOME
- $ git clone git://git.taler.net/deployment
- $ ./deployment/bootstrap-demochecker
-
- # If the previous step worked, the setup is
- # complete and the Buildbot worker can be started.
-
- $ buildbot-worker start worker/
-
-
-Tipping reserve top-up
-----------------------
-
-Both 'test' and 'demo' setups get their tip reserve topped up
-by a Buildbot worker. The following steps get the reserve topper
-prepared.
-
-.. code-block:: console
-
- # Log-in as <env>-topper, with <env> being either 'test' or 'demo'
-
- $ git clone git://git.taler.net/deployment
- $ ./deployment/prepare-reservetopper <env>
-
- # If the previous steps worked, then it should suffice to start
- # the worker, with:
-
- $ buildbot-worker start worker/
-
-
Producing auditor reports
-------------------------
@@ -470,7 +498,7 @@ prepared.
# Log-in as <env>-auditor, with <env> being either 'test' or 'demo'
$ git clone git://git.taler.net/deployment
- $ ./deployment/prepare-auditorreporter <env>
+ $ ./deployment/buildbot/bootstrap-scripts/prepare-auditorreporter <env>
# If the previous steps worked, then it should suffice to start
# the worker, with:
@@ -481,7 +509,7 @@ prepared.
Database schema versioning
--------------------------
-The Postgres databases of the exchange and the auditor are versioned.
+The PostgreSQL databases of the exchange and the auditor are versioned.
See the 0000.sql file in the respective directory for documentation.
Every set of changes to the database schema must be stored in a new
@@ -636,20 +664,22 @@ in the respective source directory (GNUnet, exchange, merchant) to create the
``.deb`` files. Note that they will be created in the parent directory. This
can be done on gv.taler.net, or on another (secure) machine.
+On ``gv``, we use the ``aptbuilder`` user to manage the reprepro repository.
+
Next, the ``*.deb`` files should be copied to gv.taler.net, say to
-``/root/incoming``. Then, run
+``/home/aptbuilder/incoming``. Then, run
.. code-block:: bash
- # cd /var/www/repos/apt/debian/
- # reprepro includedeb sid /root/incoming/*.deb
+ # cd /home/aptbuilder/apt
+ # reprepro includedeb bullseye ~/incoming/*.deb
-to import all Debian files from ``/root/incoming/`` into the ``sid``
+to import all Debian files from ``~/incoming/`` into the ``bullseye``
distribution. If Debian packages were build against other distributions,
reprepro may need to be first configured for those and the import command
updated accordingly.
-Finally, make sure to clean up ``/root/incoming/`` (by deleting the
+Finally, make sure to clean up ``~/incoming/`` (by deleting the
now imported ``*.deb`` files).
@@ -1104,7 +1134,7 @@ This chapter is a VERY ABSTRACT description of how testing is
implemented in Taler, and in NO WAY wants to substitute the reading of
the actual source code by the user.
-In Taler, a test case is a array of ``struct TALER_TESTING_Command``,
+In Taler, a test case is an array of ``struct TALER_TESTING_Command``,
informally referred to as ``CMD``, that is iteratively executed by the
testing interpreter. This latter is transparently initiated by the
testing library.
@@ -1127,7 +1157,7 @@ CMDs: for example, CMD1 may create some key material and CMD2 needs this
key material to encrypt data.
The offering of internal values from CMD1 to CMD2 is made by *traits*. A
-trait is a ``struct TALER_TESTING_Trait``, and each CMD contains a array
+trait is a ``struct TALER_TESTING_Trait``, and each CMD contains an array
of traits, that it offers via the public trait interface to other
commands. The definition and filling of such array happens transparently
to the test developer.
@@ -1184,6 +1214,12 @@ Refreshing
**Use instead**: "Obtaining change"
+Charge
+ Charge has two opposite meanings (charge to a credit card vs. charge a battery).
+ This can confuse users.
+
+ **Use instead**: "Obtain", "Credit", "Debit", "Withdraw"
+
Coin
Coins are an internal construct, the user should never be aware that their balance
is represented by coins if different denominations.
@@ -1665,7 +1701,7 @@ for the Taler exchange or Taler merchants.
Print version information.
**-w** *WIREFORMAT* \| **--wire** *WIREFORMAT*
- Specifies which wire format to use (i.e. “test” or “sepa”)
+ Specifies which wire format to use (i.e. “x-talerbank” or “iban”)
**--bank-uri**
Alternative to specify wire configuration to use for the exchange and
diff --git a/taler-exchange-manual.rst b/taler-exchange-manual.rst
index ef0d501a..fc06a8e3 100644
--- a/taler-exchange-manual.rst
+++ b/taler-exchange-manual.rst
@@ -63,7 +63,7 @@ etc.). This manual will not cover these aspects of operating a
payment service provider.
We will assume that you can operate a (high-availability,
-high-assurance) Postgres database. Furthermore, we expect some moderate
+high-assurance) PostgreSQL database. Furthermore, we expect some moderate
familiarity with the compilation and installation of free software
packages. You need to understand the cryptographic concepts of private
and public keys and must be able to protect private keys stored in files
@@ -85,7 +85,7 @@ funds in an escrow account.
Note that, given the technical burden (XML-based communications,
additional cryptography, and a vast variety of standards) due to
-interact with banks, the exchange uses a intermediary system to talk
+interact with banks, the exchange uses an intermediary system to talk
to its bank. Such intermediary system abstracts the native banking
protocol by exposing the *Taler Wire Gateway API*; this way, the exchange
can conduct its banking operations in a simplified and JSON-based style.
@@ -181,9 +181,9 @@ components:
The exchange requires a DBMS to stores the transaction history for
the Taler exchange and aggregator, and a (typically separate) DBMS
for the Taler auditor. For now, the GNU Taler reference implementation
- only supports Postgres, but the code could be easily extended to
+ only supports PostgreSQL, but the code could be easily extended to
support another DBMS.
- .. index:: Postgres
+ .. index:: PostgreSQL
- Auditor
The auditor verifies that the transactions performed by the exchange
@@ -291,6 +291,8 @@ risk that Taler does not try to mitigate against.
Please install the following packages before proceeding with the
exchange compilation.
+- Python3 module ``jinja2``
+
.. include:: frags/list-of-dependencies.rst
- GNU Taler exchange (from `download directory <http://ftpmirror.gnu.org/taler/>`__,
@@ -311,6 +313,8 @@ the GNU Taler exchange from source.
.. include:: frags/installing-taler-exchange.rst
+.. include:: frags/install-before-check.rst
+
Installing the GNU Taler binary packages on Debian
--------------------------------------------------
@@ -332,6 +336,10 @@ offline signing and the terms of service.
Sample configuration files for the HTTP reverse proxy can be found in
``/etc/taler-exchange/``.
+Installing the GNU Taler binary packages on Trisquel
+----------------------------------------------------
+
+.. include:: frags/installing-trisquel.rst
Installing the GNU Taler binary packages on Ubuntu
--------------------------------------------------
@@ -512,6 +520,9 @@ must then have the following options:
- ``FEE_REFUND``: What does it cost to refund this coin?
Specified using the same format as value.
+- ``CIPHER``: Which cipher to use for this coin? Must be either ``RSA`` or
+ ``CS``.
+
- ``RSA_KEYSIZE``: How many bits should the RSA modulus (product of the two
primes) have for this type of coin.
@@ -703,13 +714,13 @@ Wire fee structure
.. index:: wire fee
.. index:: fee
-For each wire method (“sepa” or “x-taler-wire”) the
+For each wire method (“iban” or “x-taler-bank”) the
exchange must know about applicable wire fees. This is also done
using the ``taler-exchange-offline`` tool:
.. code-block:: console
- $ taler-exchange-offline wire-fee iban 2040 EUR:0.05 EUR:0.10
+ $ taler-exchange-offline wire-fee 2040 iban EUR:0.05 EUR:0.10 EUR:0.15
The above sets the wire fees for wire transfers involving ``iban`` accounts
(in Euros) in the year 2040 to 5 cents (wire fee) and 10 cents (closing fee).
@@ -779,7 +790,7 @@ they should use the same configuration file.
For the most secure deployment, we recommend using separate users for each of
these processes to minimize information disclosures should any of them be
-compromised. The helpers do not need access to the Postgres database (and
+compromised. The helpers do not need access to the PostgreSQL database (and
thus also should not have it).
The processes that require access to the bank account need to have a
@@ -1040,8 +1051,9 @@ You can run a first simple benchmark using:
$ HTTPD_PID=$!
$ taler-exchange-offline -c benchmark.conf \
download sign \
- enable-account FIXME-DETAILS-MISING-HERE \
- wire-fee FIXME-DETAILS-MISING-HERE \
+ enable-account payto://iban/CH9300762011623852957 \
+ wire-fee iban EUR:0 EUR:0 EUR:0 \
+ global-fee EUR:0 EUR:0 EUR:0 EUR:0 4w 4w 6y 4 \
upload
$ kill -TERM $HTTPD_PID
$ taler-exchange-benchmark -c benchmark.conf -p 4 -r 1 -n 10
@@ -1058,4 +1070,4 @@ repetitions (i.e. if the operation failed the first time), total execution
time (operating system and user space) and other details.
Naturally, additional instrumentation (including using features of the
-Postgres database itself) may help discover performance issues.
+PostgreSQL database itself) may help discover performance issues.
diff --git a/taler-exchange-setup-guide.rst b/taler-exchange-setup-guide.rst
index d56a51bd..4a11e1ac 100644
--- a/taler-exchange-setup-guide.rst
+++ b/taler-exchange-setup-guide.rst
@@ -107,12 +107,12 @@ Finally, the required packages can be installed:
[root@exchange-online]# apt-get install -y nginx postgresql
[root@exchange-online]# apt-get install -y taler-exchange taler-exchange-offline
- [root@exchange-online]# apt-get install -y taler-wallet-cli
+ [root@exchange-online]# apt-get install -y taler-merchant taler-wallet-cli
By default, all installed services will be disabled. You need to enable
and start them later.
-While ``taler-merchant`` and ``taler-wallet`` are not required to operate an
+While ``taler-merchant`` and ``taler-wallet-cli`` are not required to operate an
exchange, they are useful for testing. When asked about using dbconfig to configure
the merchant's database, select ``yes``.
@@ -155,7 +155,7 @@ can use the ``taler-config`` helper:
Services, users, groups and file system hierarchy
=================================================
-The *taler-exchange-httpd* package will create several system users
+The *taler-exchange* package will create several system users
to compartmentalize different parts of the system:
* ``taler-exchange-httpd``: runs the HTTP daemon with the core business logic.
@@ -164,13 +164,13 @@ to compartmentalize different parts of the system:
* ``taler-exchange-closer``: closes idle reserves by triggering wire transfers that refund the originator.
* ``taler-exchange-aggregator``: aggregates deposits into larger wire transfer requests.
* ``taler-exchange-wire``: performs wire transfers with the bank (via LibEuFin/Nexus).
-* ``postgres``: runs the Postgres database (from *postgres* package).
+* ``postgres``: runs the PostgreSQL database (from *postgresql* package).
* ``www-data``: runs the frontend HTTPS service with the TLS keys (from *nginx* package).
.. note::
- The *taler-merchant-httpd* package additionally creates a taler-merchant-httpd user
- to runs the HTTP daemon with the merchant business logic.
+ The *taler-merchant* package additionally creates a ``taler-merchant-httpd`` user
+ to run the HTTP daemon with the merchant business logic.
The exchange setup uses the following system groups:
@@ -210,7 +210,7 @@ The deployment creates the following key locations in the system:
Setup Linting
=============
-The ``taler-wallet-cli`` package comes with a experimental tool that runs various
+The ``taler-wallet-cli`` package comes with an experimental tool that runs various
checks on the current GNU Taler exchange deployment:
.. code-block:: shell-session
@@ -240,7 +240,7 @@ must be specified in ``/etc/taler/taler.conf``.
.. warning::
When editing ``/etc/taler/taler.conf``, take care to not accidentally remove
- the @inline-matching@ directive to include the configuration files in ``conf.d``.
+ the ``@inline-matching@`` directive to include the configuration files in ``conf.d``.
Next, the electronic cash denominations that the exchange offers must be
specified. The ``taler-wallet-cli`` has a helper command that generates a
@@ -248,7 +248,10 @@ reasonable denomination structure.
.. code-block:: shell-session
- taler-wallet-cli deployment gen-coin-config --min-amount EUR:0.01 --max-amount EUR:100 > /etc/taler/conf.d/exchange-coins.conf
+ [root@exchange-online]# taler-wallet-cli deployment gen-coin-config \
+ --min-amount EUR:0.01 \
+ --max-amount EUR:100 \
+ > /etc/taler/conf.d/exchange-coins.conf
You can manually review and edit the generated configuration file. The main
change that is possibly required is updating the various fees.
@@ -284,10 +287,6 @@ machine or a different one.
The main component of LibEuFin is called the Nexus. It implements a Web
service that provides a JSON abstraction layer to access bank accounts.
-The Nexus currently uses an sqlite3 database as storage by default.
-We currently recommend to stick with this default. In future
-versions, there will be a migration path to a PostgreSQL database.
-
The HTTP port and database connection string can be edited in the configuration:
.. code-block:: ini
@@ -366,7 +365,7 @@ a backup copy* of such keys.
$CONNECTION_NAME
At this point, Nexus needs to both communicate its keys to the bank, and
-download the bank's keys. This syncronization happens through the INI, HIA, and
+download the bank's keys. This synchronization happens through the INI, HIA, and
finally, HPB message types.
After the electronic synchronization, the subscriber must confirm their keys
@@ -386,7 +385,7 @@ such letter:
..
FIXME: Maybe is not 100% clear that 'connecting' means exchanging keys
- wiht the bank?
+ with the bank?
Once the connection is synchronized, Nexus needs to import locally the data
corresponding to the bank accounts offered by the bank connection just made.
@@ -492,7 +491,7 @@ in the next step, to **send the payment instructions to the bank**:
.. code-block:: console
- [root@exchange-online]# libeufin-cli accounts submit-payment \
+ [root@exchange-online]# libeufin-cli accounts submit-payments \
--payment-uuid $UUID \
$LOCAL_ACCOUNT_NAME
@@ -610,7 +609,7 @@ and create payment initiations with a Taler wire gateway facade:
facade.talerwiregateway.transfer
..
- FIXME: The two commands above output a empty JSON object
+ FIXME: The two commands above output an empty JSON object
when successful. Possibly, we should suppress that (just like
the other commands do).
@@ -648,7 +647,7 @@ exchange processes should not have access to this information.
enable_debit = yes
# Account identifier in the form of an RFC-8905 payto:// URI.
- # For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
+ # For SEPA, looks like payto://iban/$IBAN?receiver-name=$NAME
# Make sure to URL-encode spaces in $NAME!
payto_uri =
@@ -871,12 +870,15 @@ steps involving the offline signing machine must be completed:
[root@exchange-offline]# taler-exchange-offline \
sign < sig-request.json > sig-response.json
[root@exchange-offline]# taler-exchange-offline \
- enable-account payto://sepa/$IBAN?receiver-name=$NAME > acct-response.json
+ enable-account payto://iban/$IBAN?receiver-name=$NAME > acct-response.json
+ [root@exchange-offline]# taler-exchange-offline \
+ wire-fee now iban EUR:0 EUR:0 EUR:0 > fee-response.json
[root@exchange-offline]# taler-exchange-offline \
- wire-fee 2021 sepa EUR:0 EUR:0 > fee-response.json
+ global-fee now EUR:0 EUR:0 EUR:0 EUR:0 4w 4w 6y 4 > global-response.json
[root@exchange-online]# taler-exchange-offline upload < sig-response.json
[root@exchange-online]# taler-exchange-offline upload < acct-response.json
[root@exchange-online]# taler-exchange-offline upload < fee-response.json
+ [root@exchange-online]# taler-exchange-offline upload < global-response.json
@@ -940,7 +942,7 @@ provides this functionality for testing.)
.. code-block:: shell-session
$ taler-wallet-cli deposit create EUR:5 \
- payto://sepa/$IBAN?receiver-name=Name
+ payto://iban/$IBAN?receiver-name=Name
$ taler-wallet-cli run-pending
diff --git a/taler-mcig.rst b/taler-mcig.rst
index d1247aee..97de36e6 100644
--- a/taler-mcig.rst
+++ b/taler-mcig.rst
@@ -81,7 +81,7 @@ The contract includes:
- (optional) information which details are *forgettable*;
- (optional) a *claim token* that the customer can use later;
- (optional) information on the *refund deadline*;
-- (optional) information on the the *auto-refund period* (how long does the wallet check for refunds without user prompting for it).
+- (optional) information on the *auto-refund period* (how long does the wallet check for refunds without user prompting for it).
If the customer does nothing (timeout / the contract expires),
the merchant backend automatically *unlocks* the product(s),
@@ -315,7 +315,7 @@ The fictitious store, Pretty Pianos, has only two products:
- pianos (physical good);
- *Beethoven Sonatas* (sheet music PDF files, digital good).
-M: :http:post:`/instances/default/private/products`
+M: POST ``/instances/default/private/products``
.. code-block:: javascript
@@ -338,7 +338,7 @@ field's list value.
This means the ``image`` value is *marked as forgettable*.
This will come into play later (see below).
-M: :http:post:`/instances/default/private/products`
+M: POST ``/instances/default/private/products``
.. code-block:: javascript
@@ -384,7 +384,7 @@ they need never pay again for it.
When the customer clicks on the product's "buy" button,
you first POST to ``/private/orders`` to create an order:
-M: :http:post:`/instances/default/private/orders`
+M: POST ``/instances/default/private/orders``
.. code-block:: javascript
@@ -425,7 +425,7 @@ Notes:
Now that there is an order in the system, the wallet *claims* the order.
-W: :http:post:`/orders/G93420934823/claim`
+W: POST ``/orders/G93420934823/claim``
.. code-block:: javascript
@@ -508,7 +508,7 @@ for the offer to time out).
The customer accepts the contract:
-W: :http:post:`/orders/G93420934823/pay`
+W: POST ``/orders/G93420934823/pay``
.. code-block:: javascript
diff --git a/taler-merchant-api-tutorial.rst b/taler-merchant-api-tutorial.rst
index 0be0f4e3..d45ed4fa 100644
--- a/taler-merchant-api-tutorial.rst
+++ b/taler-merchant-api-tutorial.rst
@@ -131,7 +131,7 @@ key in the ``Authorization`` header. The value of this header must be
>>> import requests
>>> requests.get("https://backend.demo.taler.net",
- ... headers={"Authorization": "ApiKey sandbox"})
+ ... headers={"Authorization": "secret-token:secret"})
<Response [200]>
If an HTTP status code other than 200 is returned, something went wrong.
@@ -224,9 +224,9 @@ A minimal Python snippet for creating an order would look like this:
... summary="Donation",
... fulfillment_url="https://example.com/thanks.html"),
... create_token=false)
- >>> response = requests.post("https://backend.demo.taler.net/private/order",
+ >>> response = requests.post("https://backend.demo.taler.net/private/orders",
... json=body,
- ... headers={"Authorization": "ApiKey sandbox"})
+ ... headers={"Authorization": "secret-token:secret"})
<Response [200]>
@@ -282,7 +282,7 @@ backend to do it is the safest method.
>>> import requests
>>> r = requests.get("https://backend.demo.taler.net/private/orders/" + order_id,
- ... headers={"Authorization": "ApiKey sandbox"})
+ ... headers={"Authorization": "secret-token:secret"})
>>> print(r.json())
If the ``order_status`` field in the response is ``paid``, you will not
@@ -350,7 +350,7 @@ This code snipped illustrates giving a refund:
... reason="Customer did not like the product")
>>> requests.post("https://backend.demo.taler.net/private/orders/"
... + order_id + "/refund", json=refund_req,
- ... headers={"Authorization": "ApiKey sandbox"})
+ ... headers={"Authorization": "secret-token:secret"})
<Response [200]>
.. Note::
@@ -439,7 +439,7 @@ funds available for tipping, query the ``/tip-query`` endpoint:
>>> import requests
>>> requests.get("https://backend.demo.taler.net/tip-query?instance=default",
- ... headers={"Authorization": "ApiKey sandbox"})
+ ... headers={"Authorization": "secret-token:secret"})
<Response [200]>
.. _authorize-tip:
@@ -474,7 +474,7 @@ This code snipped illustrates giving a tip:
... justification="User filled out survey",
... next_url="https://merchant.com/thanks.html")
>>> requests.post("https://backend.demo.taler.net/tip-authorize", json=tip_req,
- ... headers={"Authorization": "ApiKey sandbox"})
+ ... headers={"Authorization": "secret-token:secret"})
<Response [200]>
.. _Advanced-topics:
diff --git a/taler-merchant-manual.rst b/taler-merchant-manual.rst
index d5b9fcc7..f568ef15 100644
--- a/taler-merchant-manual.rst
+++ b/taler-merchant-manual.rst
@@ -66,7 +66,7 @@ special currency “KUDOS” and includes its own special bank.
.. index:: back-office
.. index:: backend
.. index:: DBMS
-.. index:: Postgres
+.. index:: PostgreSQL
The Taler software stack for a merchant consists of four main
components:
@@ -90,8 +90,8 @@ components:
describes how to install and configure this backend.
- A *DBMS* which stores the transaction history for the Taler backend.
For now, the GNU Taler reference implementation only supports
- Postgres, but the code could be easily extended to support another
- DBMS. Please review the Postgres documentation for details on
+ PostgreSQL, but the code could be easily extended to support another
+ DBMS. Please review the PostgreSQL documentation for details on
how to configure the database.
The following image illustrates the various interactions of these key
@@ -348,6 +348,8 @@ Installing the GNU Taler merchant backend
.. include:: frags/installing-taler-merchant.rst
+.. include:: frags/install-before-check.rst
+
Installing the GNU Taler binary packages on Debian
--------------------------------------------------
@@ -357,6 +359,12 @@ Installing the GNU Taler binary packages on Debian
.. include:: frags/apt-install-taler-merchant.rst
+Installing the GNU Taler binary packages on Trisquel
+----------------------------------------------------
+
+.. include:: frags/installing-trisquel.rst
+
+
Installing the GNU Taler binary packages on Ubuntu
--------------------------------------------------
@@ -373,13 +381,21 @@ Installing Taler on Debian GNU/Linux from source
.. index:: Wheezy
.. index:: Jessie
.. index:: Stretch
+.. index:: Buster
+.. index:: Bullseye
.. index:: Debian
Debian Wheezy is too old and lacks most of the packages required.
-Debian Jessie is better, but still lacks PostgreSQL 9.6.
+Debian Jessie, Stretch, and Buster are better, but still lack PostgreSQL 12.
-On Debian Stretch, only GNU libmicrohttpd needs to be compiled from
-source. To install dependencies on Debian stretch, run the following
+.. note::
+
+ When compiling PostgreSQL 12, make sure to
+ do ``make world`` to build the ``contrib/`` modules, and
+ ``cd contrib && make install`` to install them, as well.
+
+On Debian Stretch and Buster, only GNU libmicrohttpd needs to be compiled from
+source. To install dependencies on Debian Stretch, run the following
commands:
.. code-block:: console
@@ -396,8 +412,8 @@ commands:
libjansson-dev \
libpq-dev \
postgresql-9.6
- # wget https://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-latest.tar.gz
- # wget https://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-latest.tar.gz.sig
+ # wget https://ftpmirror.gnu.org/libmicrohttpd/libmicrohttpd-latest.tar.gz
+ # wget https://ftpmirror.gnu.org/libmicrohttpd/libmicrohttpd-latest.tar.gz.sig
# gpg -v libmicrohttpd-latest.tar.gz # Should show signed by 939E6BE1E29FC3CC
# tar xf libmicrohttpd-latest.tar.gz
# cd libmicrohttpd-0*
@@ -459,7 +475,7 @@ Backend options
---------------
.. index:: DBMS
-.. index:: Postgres
+.. index:: PostgreSQL
.. index:: UNIX domain socket
.. index:: TCP
.. index:: port
@@ -580,7 +596,7 @@ For the ``postgres`` backend, you need to provide:
This option specifies a postgres access path using the format
``postgres:///$DBNAME``, where ``$DBNAME`` is the name of the
-Postgres database you want to use. Suppose ``$USER`` is the name of
+PostgreSQL database you want to use. Suppose ``$USER`` is the name of
the user who will run the backend process. Then, you need to first
run:
@@ -588,7 +604,7 @@ run:
$ sudo -u postgres createuser -d $USER
-as the Postgres database administrator (usually ``postgres``) to
+as the PostgreSQL database administrator (usually ``postgres``) to
grant ``$USER`` the ability to create new databases. Next, you should
as ``$USER`` run:
@@ -617,7 +633,7 @@ You can improve your security posture if you now REVOKE the rights to CREATE,
DROP or ALTER tables from ``$USER``. However, if you do so, please be aware
that you may have to temporarily GRANT those rights again when you update the
merchant backend. For details on how to REVOKE or GRANT these rights, consult
-the Postgres documentation.
+the PostgreSQL documentation.
Commands, like ``taler-merchant-dbinit``, that support the ``-l LOGFILE``
command-line option, send logging output to standard error by default.
@@ -766,7 +782,7 @@ The following is an example for a complete backend configuration:
CURRENCY = KUDOS
Given the above configuration, the backend will use a database named
-``donations`` within Postgres.
+``donations`` within PostgreSQL.
The backend will deposit the coins it receives to the exchange at
https://exchange.demo.taler.net/, which has the master key
@@ -814,7 +830,7 @@ If everything worked as expected, the command
.. code-block:: console
- $ curl http://localhost:8888/config
+ $ wget -O - http://localhost:8888/config
should return some basic configuration status data about the service.
@@ -837,7 +853,7 @@ Instance setup
First of all, we recommend the use of the single-page administration
application that is served by default at the base URL of the merchant backend.
You can use it to perform all steps described in this section (and more!),
-using a simple Web interface instead of the ``curl`` commands given below.
+using a simple Web interface instead of the ``wget`` commands given below.
The first step for using the backend involves the creation of a ``default``
instance. The ``default`` instance can also create / delete / configure other
@@ -1055,13 +1071,13 @@ follows:
if ($http_authorization !~ "(?i)ApiKey SECURITYTOKEN") {
return 401;
}
- proxy_pass ...; // as above
+ proxy_pass ...; # as above
}
location /management/ {
if ($http_authorization !~ "(?i)ApiKey SECURITYTOKEN") {
return 401;
}
- proxy_pass ...; // as above
+ proxy_pass ...; # as above
}
Here, ``SECURITYTOKEN`` should be replaced with the actual shared secret. Note
@@ -1265,8 +1281,8 @@ merchant. Attempting to upgrade from or to a version in Git is not supported
and may result in subtle data loss.
To safely upgrade the merchant, you should first stop the existing
-``taler-merchant-httpd`` process, backup your merchant database (see Postgres
-manual), and then install the latest version of the code.
+``taler-merchant-httpd`` process, backup your merchant database (see
+PostgreSQL manual), and then install the latest version of the code.
If you REVOKED database permissions, ensure that the rights to CREATE,
DROP, and ALTER tables are GRANTed to ``$USER`` again. Then, run:
@@ -1594,7 +1610,7 @@ A relatively minimal configuration could look like this:
Note that the public key must match the exchange's
-private key and that the Postgres database must
+private key and that the PostgreSQL database must
exist before launching the benchmark. You also
will need to ensure that the Exchange's
details are set up.
@@ -1715,5 +1731,5 @@ request to the merchant, for example:
.. code-block:: console
- $ curl http://$(docker-machine ip)/
+ $ wget -O - http://$(docker-machine ip)/
# A greeting message should be returned by the merchant.
diff --git a/taler-nfc-guide.rst b/taler-nfc-guide.rst
index 1b3cfb18..fbc7a9e7 100644
--- a/taler-nfc-guide.rst
+++ b/taler-nfc-guide.rst
@@ -22,7 +22,8 @@ Background: Payment Processing with GNU Taler
The following steps show a simple payment process with GNU Taler. Examples are
written in `Bash <https://www.gnu.org/software/bash/>`_ syntax,
using `curl <https://curl.haxx.se/docs/manpage.html>`_ to make HTTP(S) requests.
-
+They make use of the :http:post:`[/instances/$INSTANCE]/private/orders`
+and :http:get:`[/instances/$INSTANCE]/private/orders` endpoints.
1. The merchant creates an *order*, which contains the details of the payment
and the product/service that the customer will receive.
@@ -45,7 +46,7 @@ using `curl <https://curl.haxx.se/docs/manpage.html>`_ to make HTTP(S) requests.
can be opened, and give a warning if it is detected that the devices does not have Internet
connectivity.
- The following :http:post:`/private/orders` request to the merchant backend creates a
+ The following POST ``/private/orders`` request to the merchant backend creates a
simple order:
.. code-block:: console
@@ -69,7 +70,7 @@ using `curl <https://curl.haxx.se/docs/manpage.html>`_ to make HTTP(S) requests.
}
2. The merchant checks the payment status of the order using
- :http:get:`/private/orders/$ORDER_ID`:
+ GET ``/private/orders/$ORDER_ID``:
.. code-block:: console
@@ -135,7 +136,8 @@ using `curl <https://curl.haxx.se/docs/manpage.html>`_ to make HTTP(S) requests.
before responding with the fulfillment page.
For in-store payments, the merchant must periodically check the payment status.
- Instead of polling in a busy loop, the ``timeout_ms`` parameter of :http:get:`/private/orders/$ORDER_ID`
+ Instead of polling in a busy loop, the ``timeout_ms`` parameter
+ of GET ``/private/orders/$ORDER_ID``
should be used.
diff --git a/taler-wallet-cli-manual.rst b/taler-wallet-cli-manual.rst
index 0758f9ef..1a2efcb9 100644
--- a/taler-wallet-cli-manual.rst
+++ b/taler-wallet-cli-manual.rst
@@ -127,7 +127,7 @@ is functional:
# 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://sepa/$IBAN
+ $ 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)
diff --git a/taler-wallet.rst b/taler-wallet.rst
index 4de69520..2fa41d9e 100644
--- a/taler-wallet.rst
+++ b/taler-wallet.rst
@@ -86,10 +86,6 @@ Please see :ref:`Build-apps-from-source` in the :doc:`taler-developer-manual`.
APIs and Data Formats
=====================
-.. warning::
-
- These APIs are still a work in progress and *not* final.
-
Envelope Format
---------------
@@ -107,7 +103,7 @@ following envelope:
export interface WalletSuccess {
type: "response";
- operation: string,
+ operation: string;
// ID to correlate success response to request
id: string;
// Result type depends on operation
@@ -118,7 +114,7 @@ following envelope:
export interface WalletError {
type: "error";
- operation: string,
+ operation: string;
// ID to correlate error response to request
id: string;
error: WalletErrorInfo;
@@ -153,487 +149,6 @@ following envelope:
details: unknown;
}
-Balances
---------
-
-Balances are the amounts of digital cash held by the wallet.
-
-:name: ``"getBalances"``
-:description: Get a list of balances per currency.
-:response:
- .. ts:def:: BalancesResponse
-
- interface BalancesResponse {
- // a list of balances sorted by currency.
- // (currencies with shorter names first, then lexically ascending).
- //
- // Note: Even when a currency has no balance, but pending or past transactions,
- // it should be included in this list with a balance of zero.
- balances: Balance[];
- }
-
- .. ts:def:: Balance
-
- // Balance for one currency.
- // The currency can be derived from any of the
- // "Amount" fields, as the currency is present even
- // when the amount is zero.
- interface Balance {
- // The total Amount that is currently available to be spent
- // including amounts tied up in ongoing refresh operations. These are hidden from the user.
- // If the user tries to spend coins locked up this way,
- // the wallet will give an error message different from "insufficient balance".
- available: Amount;
-
- // the total incoming amount that will be added to the available balance
- // when all pending transactions succeed (including internal refreshes)
- pendingIncoming: Amount;
-
- // the total outgoing amount that will be subtracted from the available balance
- // when all pending transactions succeed (including internal refreshes)
- pendingOutgoing: Amount;
-
- // true if the balance requires user-interaction, e.g. accepting a tip
- // (DEV: can be left out of a first implementation)
- requiresUserInput: boolean;
- }
-
-Transactions
-------------
-
-Transactions are all operations or events that affect the balance.
-
-:Name: ``"getTransactions"``
-:Description: Get a list of past and pending transactions.
-:Request:
- .. ts:def:: TransactionsRequest
-
- interface TransactionsRequest {
- // return only transactions in the given currency, if present
- currency?: string;
-
- // if present, results will be limited to transactions related to the given search string
- search?: string;
- }
-:Response:
- .. ts:def:: TransactionsResponse
-
- interface TransactionsResponse {
- // a list of past and pending transactions sorted by pending, timestamp and transactionId.
- // In case two events are both pending and have the same timestamp,
- // they are sorted by the transactionId
- // (i.e. pending before non-pending transactions, newer before older
- // and if all equal transactionId lexically ascending).
- transactions: Transaction[];
- }
-
- .. ts:def:: Transaction
-
- interface Transaction {
- // opaque unique ID for the transaction, used as a starting point for paginating queries
- // and for invoking actions on the transaction (e.g. deleting/hiding it from the history)
- transactionId: string;
-
- // the type of the transaction; different types might provide additional information
- type: TransactionType;
-
- // main timestamp of the transaction
- timestamp: Timestamp;
-
- // true if the transaction is still pending, false otherwise
- // If a transaction is not longer pending, its timestamp will be updated,
- // but its transactionId will remain unchanged
- pending: boolean;
-
- // if present, the transaction encountered a fatal error that needs to be shown to the user
- error?: TransactionError;
-
- // Raw amount of the transaction (exclusive of fees or other extra costs)
- amountRaw: Amount;
-
- // Amount added or removed from the wallet's balance (including all fees and other costs)
- amountEffective: Amount;
- }
-
- .. ts:def:: TransactionType
-
- type TransactionType = (
- TransactionWithdrawal |
- TransactionPayment |
- TransactionRefund |
- TransactionTip |
- TransactionRefresh
- )
-
- .. ts:def:: TransactionError
-
- interface TransactionError {
- // TALER_EC_* unique error code.
- // The action(s) offered and message displayed on the transaction item depend on this code.
- ec: number;
-
- // English-only error hint, if available.
- hint?: string;
-
- // Error details specific to "ec", if applicable/available
- details?: any;
- }
-
- .. ts:def:: WithdrawalDetails
-
- export type WithdrawalDetails =
- | WithdrawalDetailsForManualTransfer
- | WithdrawalDetailsForTalerBankIntegrationApi;
-
- .. ts:def:: WithdrawalDetailsForManualTransfer
-
- interface WithdrawalDetailsForManualTransfer {
- type: "manual-transfer";
-
- // Payto URIs that the exchange supports.
- // Already contains the amount and message.
- exchangePaytoUris: string[];
-
- // Public key of the newly created reserve.
- // Not useful for the UI, but required for integration testing.
- reservePub: string;
- }
-
- .. ts:def:: WithdrawalDetailsForTalerBankIntegrationApi
-
- interface WithdrawalDetailsForTalerBankIntegrationApi {
- type: "taler-bank-integration-api";
-
- // Set to true if the bank has confirmed the withdrawal, false if not.
- // An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI.
- // See also bankConfirmationUrl below.
- confirmed: boolean;
-
- // If the withdrawal is unconfirmed, this can include a URL for user
- // initiated confirmation.
- bankConfirmationUrl?: string;
- }
-
- .. ts:def:: TransactionWithdrawal
-
- // This should only be used for actual withdrawals
- // and not for tips that have their own transactions type.
- interface TransactionWithdrawal extends Transaction {
- type: string = "withdrawal",
-
- // Exchange that was withdrawn from.
- exchangeBaseUrl: string;
-
- // Amount that has been subtracted from the reserve's balance for this withdrawal.
- amountRaw: Amount;
-
- // Amount that actually was (or will be) added to the wallet's balance.
- // Should always be shown as a positive amount.
- amountEffective: Amount;
-
- // Further details
- withdrawalDetails: WithdrawalDetails;
- }
-
- .. ts:def:: TransactionPayment
-
- interface TransactionPayment extends Transaction {
- type: string = "payment",
-
- // Additional information about the payment.
- info: OrderShortInfo;
-
- // Wallet-internal end-to-end identifier for the payment
- // (assigned before the order is even downloaded, thus the name).
- proposalId: string;
-
- // The current status of this payment.
- status: PaymentStatus;
-
- // Amount that must be paid for the contract
- amountRaw: Amount;
-
- // Amount that was paid, including deposit, wire and refresh fees.
- // Should always be shown as a negative amount.
- amountEffective: Amount;
- }
-
- .. ts:def:: OrderShortInfo
-
- interface OrderShortInfo {
- // Order ID, uniquely identifies the order within a merchant instance
- orderId: string;
-
- // More information about the merchant
- merchant: Merchant;
-
- // Summary of the order, given by the merchant
- summary: string;
-
- // Map from IETF BCP 47 language tags to localized summaries
- summary_i18n?: { [lang_tag: string]: string };
-
- // List of products that are part of the order
- products: Product[];
-
- // URL of the fulfillment, given by the merchant
- fulfillmentUrl?: string;
-
- // Message shown to the user after the payment is complete.
- fulfillmentMessage?: string;
-
- // Map from IETF BCP 47 language tags to localized fulfillment messages
- fulfillmentMessage_i18n: { [lang_tag: string]: string };
- }
-
- .. ts:def:: PaymentStatus
-
- enum PaymentStatus {
- // Explicitly aborted after timeout / failure
- Aborted = "aborted",
-
- // Payment failed, wallet will auto-retry.
- // User should be given the option to retry now / abort.
- Failed = "failed",
-
- // Paid successfully
- Paid = "paid",
-
- // Only offered, user must accept / decline
- Offered = "offered",
-
- // User accepted, payment is processing.
- Accepted = "accepted",
- }
-
- .. ts:def:: TransactionRefund
-
- interface TransactionRefund extends Transaction {
- type: string = "refund",
-
- // ID for the transaction that is refunded
- refundedTransactionId: string;
-
- // Additional information about the refunded payment
- info: OrderShortInfo;
-
- // Part of the refund that couldn't be applied because the refund permissions were expired
- amountInvalid: Amount;
-
- // Amount that has been refunded by the merchant.
- // Corresponds to amountRefundGranted from the applyRefund response.
- amountRaw: Amount;
-
- // Amount will be added to the wallet's balance after fees and refreshing.
- // Should always be shown as a positive amount.
- amountEffective: Amount;
- }
-
- .. ts:def:: TransactionTip
-
- interface TransactionTip extends Transaction {
- type: string = "tip",
-
- // The current status of this tip.
- status: TipStatus;
-
- // Exchange that the tip will be (or was) withdrawn from
- exchangeBaseUrl: string;
-
- // More information about the merchant that sent the tip
- merchant: Merchant;
-
- // Raw amount of the tip, without extra fees that apply
- amountRaw: Amount;
-
- // Amount will be (or was) added to the wallet's balance after fees and refreshing.
- // Should always be shown as a positive amount.
- amountEffective: Amount;
- }
-
- .. ts:def:: TipStatus
-
- enum TipStatus {
- // Only offered, user must accept / decline
- Offered = "offered",
-
- // User accepted, tip is processing.
- Accepted = "accepted",
-
- // User declined.
- Declined = "declined",
-
- // Received successfully
- Received = "received",
- }
-
- .. ts:def:: TransactionRefresh
-
- // A transaction shown for refreshes that are not associated to other transactions
- // such as a refresh necessary before coin expiration.
- // It should only be returned by the API if the effective amount is different from zero.
- interface TransactionRefresh extends Transaction {
- type: string = "refresh",
-
- // Exchange that the coins are refreshed with
- exchangeBaseUrl: string;
-
- // Raw amount that is refreshed
- amountRaw: Amount;
-
- // Amount that will be paid as fees for the refresh.
- // Should always be shown as a negative amount.
- amountEffective: Amount;
- }
-
-
-:Name: ``"deleteTransaction"``
-:Description: Delete a transaction by ID.
-:Request:
- .. ts:def:: DeleteTransactionRequest
-
- interface DeleteTransactionRequest {
- // Transaction ID (opaque!) as returned in
- // the transaction list response.
- transactionId: string;
- }
-:Response: Returns an empty object
-
-Refunds
--------
-
-:Name: ``"applyRefund"``
-:Description: Process a refund from a ``taler://refund`` URI.
-:Request:
- .. ts:def:: WalletApplyRefundRequest
-
- interface WalletApplyRefundRequest {
- talerRefundUri: string;
- }
-:Response:
- .. ts:def:: WalletApplyRefundResponse
-
- interface WalletApplyRefundResponse {
- // Identifier for the purchase that was refunded
- // DEPRECATED: Will disappear soon.
- contractTermsHash: string;
-
- amountEffectivePaid: Amount;
-
- amountRefundGranted: Amount;
-
- amountRefundGone: Amount;
-
- pendingAtExchange: boolean;
-
- // Short info about the order being refunded.
- info: OrderShortInfo;
- }
-
-Exchange Management
--------------------
-
-List Exchanges
-~~~~~~~~~~~~~~
-
-:Name: ``"listExchanges"``
-:Description:
- List all exchanges.
-:Response:
- .. ts:def:: ExchangesListResponse
-
- interface ExchangesListResponse {
- exchanges: ExchangeListItem[];
- }
-
- .. ts:def:: ExchangeListItem
-
- interface ExchangeListItem {
- exchangeBaseUrl: string;
- currency: string;
- paytoUris: string[];
- }
-
-Add Exchange
-~~~~~~~~~~~~
-
-:Name: ``"addExchange"``
-:Description:
- Add an exchange.
-:Request:
- .. ts:def:: ExchangeAddRequest
-
- interface ExchangeAddRequest {
- exchangeBaseUrl: string;
- }
-:Response:
- On success, the response is an `ExchangeListItem`.
-
-
-Force Exchange Update
-~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"forceUpdateExchange"``
-:Description:
- Force updating an exchange.
- Re-queries current cryptographic key material, wire information
- and terms of service from the exchange. Also applies denomination revocations
- if applicable.
-:Request:
- .. ts:def:: ExchangeForceUpdateRequest
-
- interface ExchangeForceUpdateRequest {
- exchangeBaseUrl: string;
- }
-:Response:
- On success, the response is an `ExchangeListItem`.
-
-
-Get Terms of Service
-~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"getExchangeTos"``
-:Description:
- Get the exchange's current ToS and which version of the ToS (if any)
- the user has accepted.
-:Request:
- .. ts:def:: ExchangeGetTosRequest
-
- interface ExchangeGetTosRequest {
- exchangeBaseUrl: string;
- }
-:Response:
- .. ts:def:: ExchangeGetTosResult
-
- interface GetExchangeTosResult {
- // Markdown version of the current ToS.
- tos: string;
-
- // Version tag of the current ToS.
- currentEtag: string;
-
- // Version tag of the last ToS that the user has accepted,
- // if any.
- acceptedEtag: string | undefined;
- }
-
-Set Accepted Terms of Service Version
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"setExchangeTosAccepted"``
-:Description:
- Store that the user has accepted a version of the exchange's ToS.
-:Request:
- .. ts:def:: ExchangeSetTosAccepted
-
- interface ExchangeGetTosRequest {
- exchangeBaseUrl: string;
- acceptedEtag: string;
- }
-:Response:
- On success, the response is an empty object.
-
-
Withdrawal
----------
@@ -657,464 +172,6 @@ A typical API sequence for *manual* withdrawals can for example look like this:
#. ``"acceptManualWithdrawal"`` after the user confirmed withdrawal with associated fees
-Get Details For Bank-integrated Withdrawal
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"getWithdrawalDetailsForUri"``
-:Description:
- Get information about exchanges for a bank-integrated withdrawal from a ``taler://withdraw`` URI.
-:Request:
- .. ts:def:: GetWithdrawalUriDetailsRequest
-
- interface GetWithdrawalUriDetailsRequest {
- talerWithdrawUri: string;
- }
-:Response:
- .. ts:def:: WithdrawalDetailsForUri
-
- interface WithdrawalDetailsForUri {
- // The amount that the user wants to withdraw
- amount: Amount;
-
- // Exchange suggested by the wallet
- defaultExchangeBaseUrl?: string;
-
- // A list of exchanges that can be used for this withdrawal
- possibleExchanges: ExchangeListItem[];
- }
-
-Get Withdrawal Details
-~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"getWithdrawalDetailsForAmount"``
-:Description:
- Get information about fees and exchange for a withdrawal of a given amount.
- Can be used for both bank-integrated and manual withdrawals.
-:Request:
- .. ts:def:: WithdrawalDetailsRequest
-
- interface WithdrawalDetailsRequest {
- exchangeBaseUrl: string;
- amount: Amount;
- }
-:Response:
- .. ts:def:: WithdrawalDetails
-
- interface WithdrawalDetails {
- // Did the user accept the current version of the exchange's terms of service?
- tosAccepted: boolean;
-
- // Amount that will be transferred to the exchange.
- amountRaw: Amount;
-
- // Amount that will be added to the user's wallet balance.
- amountEffective: Amount;
- }
-
-Accept Bank-integrated Withdrawal
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"acceptWithdrawal"``
-:Description:
- Accept a bank-integrated withdrawal, where the bank transfers funds automatically.
-:Request:
- .. ts:def:: GetManualWithdrawalDetailsRequest
-
- interface AcceptWithdrawalRequest {
- talerWithdrawUri: string;
- exchangeBaseUrl: string;
- }
-:Response:
- .. ts:def:: AcceptWithdrawalResponse
-
- interface AcceptWithdrawalResponse {
- // a URL for user initiated confirmation.
- bankConfirmationUrl?: string;
- }
-
-Accept Manual Withdrawal
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"acceptManualWithdrawal"``
-:Description:
- Accept a manual withdrawal, where the user has to transfer funds manually.
-:Request:
- .. ts:def:: AcceptManualWithdrawalRequest
-
- interface AcceptManualWithdrawalRequest {
- exchangeBaseUrl: string;
- amount: Amount;
- }
-:Response:
- .. ts:def:: AcceptManualWithdrawalResponse
-
- interface AcceptManualWithdrawalResponse {
- // Payto URIs to fund the withdrawal,
- // with amount and message provided.
- exchangePaytoUris: string[];
- }
-
-Deposits
---------
-
-Deposits are direct payments into a payment target (given via
-a payto URI). They don't involve a merchant.
-
-:Name: ``"createDepositGroup"``
-:Description:
- Deposit funds directly into a payment target.
-:Request:
- .. ts:def:: CreateDepositGroupRequest
-
- interface CreateDepositGroupRequest {
- depositPaytoUri: string;
- amount: Amount;
- }
-:Response:
- .. ts:def:: CreateDepositGroupResponse
-
- interface CreateDepositGroupResponse {
- depositGroupId: string;
- }
-
-
-Payments
---------
-
-Prepare Pay
-~~~~~~~~~~~
-
-:Name: ``"preparePay"``
-:Description:
- Fetch information about a payment request from a merchant.
-:Request:
- .. ts:def:: PreparePayRequest
-
- interface PreparePayRequest {
- talerPayUri: string;
- }
-:Response:
- .. ts:def:: PreparePayResponse
-
- type PreparePayResponse =
- | PreparePayPaymentPossibleResponse
- | PreparePayAlreadyConfirmedResponse
- | PreparePayInsufficientBalanceResponse;
-
- .. ts:def:: PreparePayPaymentPossibleResponse
-
- interface PreparePayPaymentPossibleResponse {
- status: "payment-possible";
-
- proposalId: string;
-
- // Verbatim contract terms as generated by the merchant.
- contractTerms: ContractTerms;
-
- // Amount that the merchant is asking for.
- amountRaw: Amount;
-
- // Amount that will effectively be subtracted from the wallet's
- // balance when accepting this proposal.
- amountEffective: Amount;
- }
-
- .. ts:def:: PreparePayInsufficientBalanceResponse
-
- interface PreparePayInsufficientBalanceResponse {
- status: "insufficient-balance";
-
- proposalId: string;
-
- // Amount that the merchant is asking for.
- amountRaw: Amount;
-
- // Verbatim contract terms as generated by the merchant.
- contractTerms: ContractTerms;
- }
-
- .. ts:def:: PreparePayAlreadyConfirmedResponse
-
- interface PreparePayAlreadyConfirmedResponse {
- status: "already-confirmed";
-
- proposalId: string;
-
- // Did the payment succeed?
- paid: boolean;
-
- // Amount that the merchant is asking for.
- amountRaw: Amount;
-
- // Amount that will be subtracted from the wallet balance
- amountEffective: Amount;
-
- // Verbatim contract terms as generated by the merchant.
- contractTerms: ContractTerms;
- }
-
-
-Confirm Payment
-~~~~~~~~~~~~~~~
-
-:Name: ``"confirmPay"``
-:Description:
- Confirm making a payment.
-
-:Request:
- .. ts:def:: GetManualWithdrawalDetailsRequest
-
- interface ConfirmPayRequest {
- proposalId: string;
- }
-:Response:
- .. ts:def:: ConfirmPayResultDone
-
- interface ConfirmPayResultDone {
- type: "done";
-
- contractTerms: ContractTerms;
- }
-
- .. ts:def:: ConfirmPayResultPending
-
- // Payment was marked as confirmed,
- // but the attempt(s) to pay were not successful yet.
- interface ConfirmPayPending {
- type: "pending";
-
- lastError: TransactionError;
- }
-
- .. ts:def:: ConfirmPayResult
-
- type ConfirmPayResult =
- | ConfirmPayResultDone;
- | ConfirmPayResultPending;
-
-Abort Failed Payment
-~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"abortFailedPayWithRefund"``
-:Description:
- Abort a failed payment and try to get a refund for the
- partially paid amount.
-:Request:
- .. ts:def:: AbortPayWithRefundRequest
-
- export interface AbortPayWithRefundRequest {
- proposalId: string;
- }
-:Response:
- On success, the response is an empty object.
-
-Tipping API Calls
------------------
-
-Preparing a tip
-~~~~~~~~~~~~~~~
-
-:Name: ``"prepareTip"``
-:Description:
- Prepare to accept a tip based in a ``taler://tip`` URI.
-:Request:
- .. ts:def:: PrepareTipRequest
-
- interface PrepareTipRequest {
- talerTipUri: string;
- }
-:Response:
- .. ts:def:: PrepareTipResult
-
- interface PrepareTipResult {
- // Unique ID for the tip assigned by the wallet.
- // Typically different from the merchant-generated tip ID.
- walletTipId: string;
-
- // Has the tip already been accepted?
- accepted: boolean;
- tipAmountRaw: Amount;
- tipAmountEffective: Amount;
- exchangeBaseUrl: string;
- expirationTimestamp: Timestamp;
- }
-
-
-Accepting a tip
-~~~~~~~~~~~~~~~
-
-:Name: ``"acceptTip"``
-:Description:
- Prepare to accept a tip based in a ``taler://tip`` URI.
-:Request:
- .. ts:def:: AcceptTipRequest
-
- interface AcceptTipRequest {
- walletTipId: string;
- }
-:Response:
- On success, the response is an empty object.
-
-Testing API calls
------------------
-
-The following API calls are useful for testing.
-
-Withdraw balance from the TESTKUDOS environment
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"withdrawTestkudos"``
-:Description:
- Withdraw a balance from the ``TESTKUDOS`` environment.
-:Request:
- The request parameters are ignored.
-:Response:
- On success, the response is an empty object.
-
-Withdraw balance from a test environment
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"withdrawTestBalance"``
-:Description:
- Withdraw a balance from a test environment.
-:Request:
- .. ts:def:: WithdrawTestBalanceRequest
-
- interface WithdrawTestBalanceRequest {
- amount: string;
- bankBaseUrl: string;
- exchangeBaseUrl: string;
- }
-:Response:
- On success, the response is an empty object.
-
-Run integration test
-~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"runIntegrationTest"``
-:Description:
- Run a basic integration test that does a withdrawal, payment,
- refund and again a payment. Useful to generate test data
- in the integration tests of other components.
-:Request:
- .. ts:def:: IntegrationTestArgs
-
- interface IntegrationTestArgs {
- exchangeBaseUrl: string;
- bankBaseUrl: string;
- merchantBaseUrl: string;
- merchantApiKey: string;
- amountToWithdraw: string;
- amountToSpend: string;
- }
-:Response:
- On success, the response is an empty object.
-
-Make a test payment
-~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"testPay"``
-:Description:
- Make a test payment with existing funds.
-:Request:
- .. ts:def:: TestPayArgs
-
- interface TestPayArgs {
- merchantBaseUrl: string;
- merchantApiKey: string;
- amount: string;
- summary: string;
- }
-
-
-Dump all coins to JSON
-~~~~~~~~~~~~~~~~~~~~~~
-
-:Name: ``"dumpCoins"``
-:Description:
- Make a test payment with existing funds.
-:Request:
- The request object is ignored.
-:Response:
- .. code:: ts
-
- interface CoinDumpJson {
- coins: Array<{
- /**
- * The coin's denomination's public key.
- */
- denom_pub: string;
- /**
- * Hash of denom_pub.
- */
- denom_pub_hash: string;
- /**
- * Value of the denomination (without any fees).
- */
- denom_value: string;
- /**
- * Public key of the coin.
- */
- coin_pub: string;
- /**
- * Base URL of the exchange for the coin.
- */
- exchange_base_url: string;
- /**
- * Remaining value on the coin, to the knowledge of
- * the wallet.
- */
- remaining_value: string;
- /**
- * Public key of the parent coin.
- * Only present if this coin was obtained via refreshing.
- */
- refresh_parent_coin_pub: string | undefined;
- /**
- * Public key of the reserve for this coin.
- * Only present if this coin was obtained via refreshing.
- */
- withdrawal_reserve_pub: string | undefined;
- /**
- * Is the coin suspended?
- * Suspended coins are not considered for payments.
- */
- coin_suspended: boolean;
- }>;
- }
-
-
-Suspend/unsuspend a coin
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-A suspended coin will not be used by the wallet for payments.
-This functionality is only used for testing.
-
-:Name: ``"setCoinSuspended"``
-:Description:
- Make a test payment with existing funds.
-:Request:
- .. ts:def:: SetCoinSuspendedRequest
-
- interface SetCoinSuspendedRequest {
- coinPub: string;
- suspended: boolean;
- }
-:Request:
- On success, the response is an empty object.
-
-Global Errors
--------------
-
-* Backup/Sync/Anastasis failed
-* refresh after pay failed for multiple attempts
- (depending on online status)
-* scheduled refresh (to avoid expiration) failed
-* general recoups (?)
-* failed recoup
-* (maybe) fatal errors during withdrawal
-* pending refund failed permanently (?)
-
Integration Tests
=================
diff --git a/wallet-confirm-withdraw.svg b/wallet-confirm-withdraw.svg
new file mode 100644
index 00000000..899728ae
--- /dev/null
+++ b/wallet-confirm-withdraw.svg
@@ -0,0 +1,16 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 645.5 1015.5" width="645.5" height="1015.5">
+ <!-- svg-source:excalidraw -->
+ <!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2baVfiSFx1MDAxN8ffz6fIcd62TO2L79hUXFxQREV55jmeXHUwMDAwgUBCXHUwMDEyQ9ic0999KqBcdTAwMTAgYJrWXHUwMDE2p+V4VKqyVKp+91/31q3884em7Vx1MDAwNWPP2DvQ9oxRXbfbXHJfXHUwMDFm7n1cdTAwMGLLXHUwMDA3ht9ru46qQpPvPbfv1ydHmkHg9Vx1MDAwZf76a35Gqu52p2dcdTAwMTm20TWcoKeO+5/6rmn/TH5H7uNcdTAwMWL1QHdatjE5YVJcdTAwMTW5XHUwMDE1IculRdeZ3Fx1MDAxNlx1MDAwMkCQ5Fx1MDAxOMrZXHUwMDEx7V5O3S8wXHUwMDFhqrqp2z1jXlx1MDAxM1x1MDAxNu05h82K/mhJK1t8urox3Y7h30du22zbdjlcdTAwMTjb04fS62bfj9T2XHUwMDAy37WMSrtcdTAwMTGY4d2Xymfn9Vxc1Vx1MDAwNfOzfLffMlx1MDAxZKPXWzjH9fR6O1x1MDAxOE+fYlY67YVcdTAwMDNtXjKaXHUwMDFjIVFcblwiOitcdTAwMWNP+oWmOF1qQ9a1XT9sw59g8pm3oqbXrZZqitOYXHUwMDFkXHUwMDEz+LrT83RfXHLO/Ljh89MxhFJERj6zI0yj3TJcdTAwMDN1iMRCtVx1MDAwMMw+kVx1MDAwZTEmXHUwMDAzXHUwMDAwXHUwMDAxRoBcblxu5s1cZpvgXHUwMDE1XHUwMDFhXHUwMDEzXHUwMDE4/r/cgabue89cdTAwMWS111x1MDAwYr9Emlx1MDAxZrY8PyXp5fRJ3fdv2zCF1zNcdTAwMDWJelx1MDAxMkokT8xU//Ixa8G+mb0s92tiP91qmHn/XHUwMDEzMIVTgi8yXHUwMDA1hXxHoHCKk41AUbpCXHUwMDEw5MrEXHUwMDA1pfDXXHUwMDEzXHUwMDE0XHUwMDE4oyBcdTAwMGVcdTAwMWXI6Fp4kIBcdTAwMTBwSHBieDrp5tktyT9cdTAwMTVcdTAwMWac/qEv789rQ1bdeXggXHUwMDAzK/AgQd5TkDCKIYasalx1MDAwZZaAM0ZcdTAwMDF+XHUwMDE3YuZcdTAwMDPjOkG5/Vx1MDAxNPZccmZcdTAwMGKlh3q3bY9fJskpUiFK6lx1MDAwNlx1MDAxOd2xtMlTNlxyX2tcdTAwMTiB3rZ70S7pXHUwMDE5dtuZXFxz8eS03W6F7O3ZRnNcdTAwMTHKoK1m3Fl14Hp728tcIlx1MDAxNmxcdTAwMWTZXHUwMDE4XHUwMDEzIJRmJFdFNFx1MDAxZYlcdTAwMDZo3Vxic2h2XHUwMDAyUW0/le67O1x1MDAwZjZcdTAwMDWrM60yaZFC68muS6QjfXuyIWcpyUKZU8qBXHUwMDA0QyxcdTAwMDb0xalcdTAwMTbIXHUwMDE17lWrhZRcdTAwMDLGz7XPZeFjZlx1MDAxZYeD0dg0WVx0XFyOQXNUrKcv957rP0RQMV+LXHUwMDFkwUjNx5zQxNjlrnGt10Z6JX/bqTw1feT27d2fjClU2lx0I1xiLFx1MDAwMYhIXG5cdTAwMDNMn1x1MDAwZlx1MDAwMez9WCRsoSGArLKIYGrWlPB3RFx1MDAxNF5glERNhVJGJPtcdTAwMDNgjNNqKFI0vqNfke66roi0tWE7MMPwRrdjZVx1MDAxYvK1XfMmMj5l/DZbqY/S/ea5azHLs1x1MDAwNo1zt16bXy7e3l7m7Vx1MDAwNbIwh9E5e0ZB7LDjXHJcdTAwMTS/1Wz/0fa5ra/wXHUwMDAyPlwigqtRZzw6uC9RXHUwMDA3XHUwMDA0y6Uzx1G5LJJySZNEsqusO33bXiH20jZcdTAwMTSYithoXHUwMDBmRkxcdTAwMDGBJOz/XGKy8Vx1MDAxNrFcdTAwMDHkk9vi3SG7vW3rXHUwMDFkcthcdTAwMTlfX42veDKQXHUwMDExlqkl/5OQeJZ5jN/4xXJcdTAwMDKWXHUwMDAxxJxSXHUwMDE2iVx0I0FcdTAwMTDasCojXHUwMDEwUvJcdTAwMGaS+IpcdFkuZNLFXHUwMDAzzYKogztcdTAwMTY0XHUwMDExtjq7yvRZ/r5cIk/R5aDcwLend9XHXrNMtmaay1imyVx1MDAxN9PbMU2USkjJXHUwMDExjGM6XHUwMDEyMy4zLSlRXG6dLP5JyHSmkD3QbFx1MDAwNK1cdTAwMGW2LVx1MDAwNPHOXHUwMDEyfcFBoXfSsi46VXJcdTAwMDOD2khvutVtiaZcYkRjqVx1MDAxOdFcdTAwMTJ+XHUwMDExvVxy0YRcdTAwMGLGaTTsSybSUlx1MDAwMoJcdTAwMTB/S4ejqHeNXHUwMDAzLT+qm2q0XGat4NRTu4r0XHI+hFx1MDAwNZEp034uyF9XR5afXHUwMDA2eGukXHUwMDE5jEWagi+kt1x1MDAxMmnBVfiIXHUwMDA0imV6lfT56itcdTAwMTdUXGLI3pDpdFdcdTAwMWRcdTAwMTdcdTAwMWNoN+WcesTdwznZulx1MDAxZVx1MDAxNuv6jEjClFxmJE+gjdvSO9r3qmnPr41L18EhbFYvdlx1MDAwN71169WcKVx1MDAxM50vny2ur1x1MDAxMLLeXHUwMDA2MSPNOv+JZWuxkPVgMi6yZikqsfK2XHUwMDExJVx1MDAxMkBOli1cdTAwMDJcdTAwMDHOIYUwPlx1MDAwYvJcXKZK8zJzedFpl5npXHUwMDFlNa7cXHUwMDBi6Fx1MDAxN4boQ9f2XHUwMDEwW+9TcSwxZkzAxPBJViqdZrqw4qOzYlx1MDAxNz9cdTAwMTZcdTAwMDeeXdx9+Fx1MDAwNE5cdFwiXHUwMDAwllxiqnAqXCJPU/hoXG5AwoGSLoQphutcdTAwMTf3fpbFcFx1MDAxNXFGmVT3W0VcdTAwMTHSXHUwMDE0lFxmcEEmRypcdTAwMTdh1YWWqq3stZW9d0UxToYhSk16XHUwMDE3cUKVn1x1MDAxZbGzXHUwMDA1VZ6P4Wxlz/XG8ZJcdTAwMGLW9sUvzMnQtYvjysFcdTAwMDBIXHUwMDE52Fx1MDAwZmRcdTAwMWKv97PVo5wrx7etk0y/drJcdTAwMWZcXIPD3TegzeotVERcdTAwMDEweUFcdTAwMWK9m/28iZZjrGJcdTAwMWSqPJVXXGaocGtm3caZkb7tdkeg1KpegLtcdTAwMGbWcrFcdTAwMTI1v6CIIFKqIH4kUXNcdTAwMDXK40H15tJ0jkv57sNRPzvSdz9Rs6zlZFx0RZpCULyMPsL43Vh8XHUwMDFiLWdcXKhaRj9cdTAwMTTG30rN0TpcdTAwMTOSQvl3jIjkXHUwMDE5dlbbz1x1MDAxZlt2SVx1MDAxNo+vXHUwMDFl8oEvPLt+tPtcdTAwMTa0UcwpgilCP42YQ6hcXFig5uDXPHPr3LpcdTAwMDSPXHUwMDE39X1cdTAwMWSUee5hjMtew/hYNecrOar5XG4+VC5cdTAwMWVhOLljsY+rxlXFXHUwMDFiUL+Sz2TuLnnNxbe7z+JGNadq+Fx0i6j5bos5goBcYkkweU3M35XF30nMydrYliFAIWIwuTt0fnVcdTAwMGby11x1MDAxN8fnd4Nz+fTY4Sg38nbfgDaLOUMp+XnEXHUwMDFjXHUwMDAxXHTBYpIn3oBcdTAwMWPT9PtwRJ9aslghx/r9iXXrfPAyy9pcdTAwMTU+5Y4ub1x1MDAxOXuNxVx1MDAwMjTcgs6650f0yTJhqcdzJ3T3Wdws5oyn5OdcdTAwMTFzVS3DXHIh8jXH4l1Z/G+I+Vx1MDAxNGpvKNOnN96x5dxcdTAwMWTJ/dLJqWVVTlZTPjGKP837kIUkT/iI0ZW8mVx1MDAxMFx1MDAwMZSiq1x1MDAwM46g0sno5ne+geavNNCLXHUwMDFjI2Wn0eA0XCJ4dG1qk1xudebCtpVcdTAwMWbNXHUwMDAybaDo/qyTQydtcZx7aNIsyFQvS1x1MDAwM7BKUdzWO6VcdTAwMGJLXHUwMDAwcS7jXHUwMDEyh1TEXHUwMDEwXHUwMDE08eq+iFmXOORcXI094ihux1x1MDAxMlwi6+NcdTAwMWQuXHUwMDA041x1MDAxY9Mk8U7SxKFcdTAwMWRcdTAwMTi+o1x1MDAwN+2BYY+/aWO3r9V1R1OXcrVe+F9gtnta6Uqru1xyQ3N9zfVcZkfTro9cdTAwMGJl7axQPNX+dsJzTH1gaLpW01x1MDAxZKvttLS052ltp1x1MDAxN+i2bTTUJfRA6/U9z/WDnnZ1mNWEXHUwMDA00Yn6VycpXHTYYDqH9kMh91RcdTAwMTj2xdNcdTAwMTHP3e9XXHUwMDFhNMhvm3NnSmdjNkZh9pVz325cdTAwMWKJJFx1MDAwMtM4u5FrXHUwMDE3faFyuVx1MDAxMCRRc/tps+n1a1x1MDAxZDVcdTAwMDNcdTAwMWZopUpejXu6nFM/XHUwMDA0U/VV/fNZ8++rU9jsvVx1MDAxYVx1MDAwMpmKOn4gXHUwMDAxX2FOXHRfPDxcZq2j8ztGQFvW+589UFTO32dK4UDGJVDWXHUwMDAy498xmzvnpnuq81IlYId687pf22/lR1wi+8E5nPV70EOviXKeaPvMc1x1MDAwZce6OVx1MDAxYVp1/9pcdTAwMTjw6jBbv/H54Gn3WdxcdTAwMTgoMlx1MDAxNeAs5HB2O1CkXGZBqa7x2qLfu6L4X4pcdTAwMTPh9X32qGqWzvrGXcutXzL1sDfJPHxcIlx1MDAxNklSnMW49yTiZm7n3lx1MDAxYspZ/VxyfVx1MDAxNCg45pBSXHUwMDE093JccpQrzsv83VUslWNcdTAwMTh5V/in3ZRz3TKU5+1cdTAwMWJa4Gr9nvpjXHUwMDFhypP3QzdAe/ZhvmmuKvWH7Wn1307XdYyxNlSDoDluoOm+r4JcdTAwMDPlzk9DgWHo01x1MDAwN1x1MDAxZrljdqP33qlcdTAwMWTfyv6ooVx1MDAxZlx1MDAwZfRcdTAwMGJqV+C41o9cdHzXLZ9AxFNLXHUwMDFlPGNxXHUwMDFlPIUxwa+InvxlXHUwMDFma+yDc07C7UBx6yXrd1x1MDAxMYV7XHUwMDBiXHUwMDA1QeQnzGNcdTAwMDM210FcdTAwMTftXHUwMDFm2/nSWdlr2KDLhU7TyYK+yG6T6auKILWKXHUwMDBij4v3yFx1MDAwZrDym8Z7XHUwMDEwIMhcdTAwMDSFq9HdpHKDj6hmNZFsw3BCLd1iazVeuMhPh3ZyXHUwMDAzwEG2g24ydXZxWfQy+/jee1x1MDAwMFYuXHUwMDExwJitrFqERTFcdTAwMWVcdTAwMDGKcVxivlx1MDAxNi1cdTAwMTK9bStcdTAwMTFYeM18XHUwMDBl8YblvslcdTAwMWJgXHUwMDE4kDeEOHBcdTAwMGZ2YHHij2dXfk/3vHKgXHUwMDA3Yd2U571B21x1MDAxOGZWsfizOfmEXHUwMDBl8fc/vv9cdTAwMGIhXG42wCJ9<!-- payload-end -->
+ <defs>
+ <style>
+ @font-face {
+ font-family: "Virgil";
+ src: url("https://excalidraw.com/Virgil.woff2");
+ }
+ @font-face {
+ font-family: "Cascadia";
+ src: url("https://excalidraw.com/Cascadia.woff2");
+ }
+ </style>
+ </defs>
+ <rect x="0" y="0" width="645.5" height="1015.5" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 66.75) rotate(0 311.25 469.375)"><path d="M0.1 -0.43 C191.81 -1, 384.73 -0.48, 622.99 0.04 M0.24 0.31 C237.23 1.21, 474.12 1.74, 622.31 0.32 M622.81 0.69 C619.82 265.46, 618.57 531.76, 622.65 939.19 M622.53 0.18 C625.38 220.28, 625.13 441.42, 622.35 938.57 M622.74 938.73 C459.11 938.32, 294.82 938.61, -0.09 938.67 M622.29 938.85 C380.68 940.76, 139.59 940.06, 0.15 938.78 M0.56 938.83 C-0.16 733.07, 0.31 526.84, -0.04 -0.27 M-0.24 938.63 C-1.96 647.7, -2.08 356.96, -0.3 0.05" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(11.75 10) rotate(0 311.875 27.5)"><path d="M-0.43 -0.54 C226.54 -0.29, 453.87 0.19, 623.79 0.65 M0.31 0.19 C148.17 -1.46, 297.24 -1.68, 624.07 -0.07 M625.48 1.56 C623.23 10.11, 625.05 23.69, 624.84 53.7 M624.2 0.53 C623.31 20.31, 624.81 40.45, 623.3 54.63 M623.73 55.12 C400.92 55.1, 178.89 55.67, -0.08 55.7 M623.85 54.74 C383.85 53.9, 142.86 53.83, 0.03 54.68 M0.19 54.52 C0.68 39.24, -0.45 22, -0.67 0.2 M-0.3 54.31 C0.38 35.05, 1.01 16.31, 0.12 -0.68" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(78.75 105.75) rotate(0 164.5 20.5)"><text x="0" y="32" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Bank transfer details</text></g><g stroke-linecap="round" transform="translate(420 929.25) rotate(0 88.48214285714312 24.375)"><path d="M1.19 1.55 C68.32 -0.77, 134.91 1.85, 176.01 1.6 M0.39 0.86 C50.84 0.56, 99.35 1.47, 177.15 0.55 M177.1 0.91 C178.37 10.19, 177.1 24.64, 176.22 47.86 M177.27 -0.03 C177.39 12.18, 176.42 25.35, 176.85 48.65 M175.91 49.23 C106.99 51.87, 40.71 48.37, 0.75 48.88 M177.66 48.85 C137.93 47.4, 99.73 46.84, -0.05 48.41 M-1.2 48.14 C-1.6 33.39, -2.18 19.55, -1.51 0.24 M-0.21 47.95 C-0.76 37.33, -0.29 26.84, -0.34 0.86" stroke="#c92a2a" stroke-width="1" fill="none"></path></g><g transform="translate(432.58928571428623 945.3035714285706) rotate(0 73.357142857143 10.678571428571445)"><text x="0" y="17.357142857142897" font-family="Helvetica, Segoe UI Emoji" font-size="18.57142857142862px" fill="#c92a2a" text-anchor="start" style="white-space: pre;" direction="ltr">cancel withdrawal</text></g><g transform="translate(77.875 192.75) rotate(0 52 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Please wire</text></g><g transform="translate(157.625 262.75) rotate(0 89.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">IBAN: k12j3jk1h23kj</text></g><g transform="translate(157.625 300.75) rotate(0 74.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">BIC: l21kj3lk213j</text></g><g transform="translate(157.625 341.25) rotate(0 95.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Name: Exchange Inc.</text></g><g transform="translate(157.625 382.25) rotate(0 75 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Amount: USD 10</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 261) rotate(0 19.374999999999886 13.296568627450881)"><path d="M0.2 1.6 C6.8 -0.4, 15.21 2.08, 40.58 0.44 M0.54 -0.57 C14.67 0.42, 29.71 0.1, 39.41 0.7 M39.39 0.92 C36.83 9.01, 39.01 14.69, 39.87 25.7 M39.27 0.69 C39.52 11.43, 39.74 20.45, 38.03 26.49 M38.22 25.75 C22.92 25.11, 8.14 27.35, -1.75 26.82 M39.03 26.1 C27.25 26.21, 14.83 26.88, 0.93 26.14 M-0.74 24.9 C0.74 19.34, 1.19 11.08, 0.05 -0.27 M0.7 25.62 C1.09 16.81, -0.57 8.72, 0.31 -0.89" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568687 266.01470588235316) rotate(0 12.156862745097897 7.598039215686214)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 301.2034313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-1.8 -0.14 C12.37 1.47, 28.62 1.27, 37.36 -1.73 M0.39 1 C13.1 -0.86, 25.79 0.16, 37.85 -0.43 M38.24 1.81 C37.08 7.65, 38.9 17.94, 40.18 24.78 M39.45 0.2 C39.01 7.37, 39.26 12.39, 38.56 26.23 M39.88 27.03 C24.89 26.77, 10.63 25.1, 1.58 27.03 M39.75 27.54 C28.74 27.01, 18.79 27.01, 0.77 27.26 M1.89 28.26 C0.17 21.47, -0.68 14.05, 0.4 -0.35 M0.39 25.68 C0.67 15.44, 0.45 5.81, 0.11 0.6" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 306.21813725490233) rotate(0 12.156862745097897 7.598039215686214)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 342.4534313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-0.38 -0.73 C16.13 0.66, 29.47 1.3, 39.19 -0.71 M0.79 0.22 C12.09 0.74, 24.2 1.24, 39.69 0.34 M40.29 1.33 C37.37 11.2, 38.46 21.39, 40.42 26.95 M38.95 -0.18 C39.64 9.14, 37.8 18.36, 37.83 27.31 M38.96 27.79 C28.84 25.41, 21.91 27.74, -1.04 28.06 M38.26 26.44 C28 27.11, 17.37 27.04, -0.97 26.55 M-0.09 25.8 C-0.26 15.61, -1.09 7.69, -0.49 0.26 M-0.4 27.52 C0.35 19.06, 0.53 12.64, -0.83 0.34" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 347.46813725490233) rotate(0 12.156862745097897 7.598039215686185)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 383.9534313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-0.49 -0.78 C15.26 -0.5, 31.64 0.17, 36.97 0.79 M0.5 0.28 C8.18 0.84, 15.8 -0.41, 39.54 0.52 M38.8 1.45 C37.97 6.29, 39.23 13.66, 39.55 26.93 M38.41 -0.99 C38.69 5.01, 38.56 10.33, 38.16 27.15 M40.58 24.93 C27.27 28.29, 18.6 25.21, 1.55 26.35 M38.83 27.42 C29.93 26.92, 21.21 25.9, -0.76 26.99 M1.87 26.4 C-1.77 17.07, 1.42 10.3, 1.88 0.91 M0.53 26.14 C-0.51 17.99, -0.02 10.03, 0.66 0.83" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 388.96813725490233) rotate(0 12.156862745097897 7.598039215686185)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(159.125 690) rotate(0 151.25 108.125)"><path d="M-0.59 -0.65 C83.72 -0.73, 165.78 1.76, 301.11 -0.16 M-0.3 0.73 C68.13 1.91, 137.74 1.33, 302.4 0.68 M303.7 1.55 C302.89 46.8, 303.22 91.56, 301.87 215.05 M302.76 -0.38 C303.92 68.39, 303.93 138.49, 303.34 215.92 M301.06 217.22 C191.77 218.63, 83.96 217.97, 1.38 217.12 M303.01 216.46 C187.06 215.84, 72.43 214.73, -0.58 215.7 M-0.49 214.79 C2.29 161.3, 1.47 109.67, 1.42 -0.99 M-0.83 215.67 C0.89 145.95, -0.16 76.71, -0.78 0.74" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(42.125 600.25) rotate(0 292.5 22.5)"><text x="0" y="17.5" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Alternatively, you can also scan this QR code or open THIS LINK </text><text x="0" y="40" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">you have a banking App installed that supports RFC 8905</text></g><g transform="translate(157.625 423.75) rotate(0 180 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">subject: QWE123ASDASD435QWEASD</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 428.2034313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-0.38 1.12 C8.3 2.21, 20.85 0.78, 38.1 -1.47 M-0.14 0.95 C8.36 -0.35, 17.46 -0.48, 38.72 -0.75 M40.24 -0.22 C38.82 5.85, 37.43 11.71, 36.87 24.97 M37.87 -0.72 C38.61 10.14, 39.32 21.4, 39.72 26.66 M40.72 26.3 C28.27 25.29, 15.24 27.74, 1.91 25.32 M38.39 27.31 C25.46 26.86, 10.75 27.7, -0.74 26.71 M-0.24 28.49 C1.24 17.12, 1.53 5.62, 2 -0.76 M-0.96 25.67 C-0.53 18.42, -0.61 10.78, 0.5 0.34" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 433.21813725490233) rotate(0 12.156862745097897 7.598039215686185)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g transform="translate(65.875 504.25) rotate(0 226.5 22.5)"><text x="0" y="17.5" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#e67700" text-anchor="start" style="white-space: pre;" direction="ltr">Make sure to use the correct subject, otherwise the</text><text x="0" y="40" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#e67700" text-anchor="start" style="white-space: pre;" direction="ltr">money will not arrive in this wallet.</text></g><g stroke-linecap="round" transform="translate(45.375 487.75) rotate(0 257.5 43.75)"><path d="M0.34 0.72 C200.37 1.93, 399 2.26, 514.51 -0.13 M0.03 -0.22 C123.26 0.72, 246 1.36, 514.88 0.35 M514.83 -1.24 C515.78 20.87, 515.26 40.81, 513.13 85.59 M514.86 -0.18 C515.13 22.54, 514.29 45.85, 514.42 87.17 M515.15 87.04 C350.04 88.8, 185.58 89.06, 0.52 86.83 M514.84 87.7 C403.52 86.46, 292.49 86.9, -0.2 87.35 M0.93 86.82 C-0.35 56.29, -1.91 25.03, -1.42 -1.99 M-0.54 87.01 C0.32 55.97, -0.86 25.45, 0.04 -0.52" stroke="#e67700" stroke-width="1" fill="none"></path></g><g transform="translate(198.875 191.5) rotate(0 35 12)"><text x="0" y="19" font-family="Cascadia, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">USD 10</text></g><g transform="translate(287.625 190.25) rotate(0 11.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">to:</text></g></svg> \ No newline at end of file
diff --git a/wallet-mobile-overview.svg b/wallet-mobile-overview.svg
new file mode 100644
index 00000000..5f41659b
--- /dev/null
+++ b/wallet-mobile-overview.svg
@@ -0,0 +1,103 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1190.625 647.5" width="1190.625" height="647.5">
+ <!-- svg-source:excalidraw -->
+
+ <defs>
+ <style>
+ @font-face {
+ font-family: "Virgil";
+ src: url("https://excalidraw.com/Virgil.woff2");
+ }
+ @font-face {
+ font-family: "Cascadia";
+ src: url("https://excalidraw.com/Cascadia.woff2");
+ }
+ </style>
+ </defs>
+ <rect x="0" y="0" width="1190.625" height="647.5" fill="#ffffff"></rect>
+ <g stroke-linecap="round" transform="translate(10 10) rotate(0 433.125 313.75)">
+ <path d="M0.15 -0.75 C280.81 -1.32, 560.96 -0.77, 865.84 0.07 M-0.13 0.07 C216.66 2.23, 432.34 1.9, 866.24 0.31 M866.35 -0.29 C865.75 141.88, 865.41 283.41, 865.87 628.1 M866.59 -0.15 C866.57 194.82, 866.86 388.81, 866.44 627.67 M866.04 626.84 C593.8 625.52, 322.18 624.91, -0.79 627.56 M866.3 627.9 C664.27 625.26, 462.26 625.16, -0.11 627.57 M-0.45 627.39 C-2.5 418.01, -2.68 208, 0.23 -0.21 M-0.06 627.34 C0.58 426.24, 0.45 225.45, 0.23 -0.27" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g stroke-linecap="round" transform="translate(272.375 151.75) rotate(0 154.875 148)">
+ <path d="M0.26 1.43 C101.9 -0.6, 205.03 -1.56, 309.89 0.7 M-0.48 -0.28 C68.23 1.47, 135.67 1.08, 310.07 0.6 M310.69 0.76 C308.01 75.39, 307.82 151.52, 308.65 296.47 M310.19 -0.01 C307.79 95.24, 307.74 190.28, 309.85 295.66 M309.75 295.55 C245.1 293.74, 181.38 292.61, 1.2 296.97 M309.1 296.38 C190.11 296.75, 69.6 297.2, 0.22 295.93 M-0.53 295.42 C2 207.16, 1.39 117.05, -1.37 -0.64 M-0.05 295.88 C-0.31 226.22, 0.9 156.92, 0.33 0.27" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g stroke-linecap="round" transform="translate(36.625 147) rotate(0 105 151)">
+ <path d="M-0.72 -0.1 C68.83 -0.69, 140.24 0.01, 210.59 -1.5 M-0.17 -0.16 C43.48 1.05, 86.21 1.19, 210.57 0.58 M210.63 0.08 C209.93 105.13, 210.15 208.37, 209.12 302.44 M209.3 0.08 C212.33 95.38, 211.41 190.12, 209.67 301.85 M208.37 300.54 C160.16 303.18, 114.89 303.07, 0.48 303.5 M209.46 302.41 C150.83 302.73, 93.63 302, 0.27 302.86 M-0.9 301.87 C0.71 231.93, 1.75 158.31, -0.45 0.41 M-0.42 301.98 C-1.94 191.93, -1.85 82.62, -0.09 -0.62" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g stroke-linecap="round" transform="translate(49.375 34.75) rotate(0 270 51)">
+ <path d="M-0.03 -0.2 C204.47 1.13, 409.77 1.14, 539.32 -0.47 M0.05 0.26 C187.08 0.34, 374.58 0.77, 539.94 -0.06 M538.35 -0.86 C538.73 36.84, 541 72.79, 540.45 103.97 M539.44 -0.48 C540.3 30.38, 540.23 59.33, 539.1 102.57 M539.71 102.5 C420.92 100.6, 302.28 100.54, 0.5 102.71 M539.91 101.81 C358.02 99.85, 176.84 99.89, 0.12 101.78 M-1.2 102.2 C-0.36 61.46, -1.01 20.24, 1.47 -0.64 M-0.24 102.88 C-0.3 64.62, -0.12 25.79, -1 0.06" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g stroke-linecap="round" transform="translate(598.625 169.5) rotate(0 125.5 132)">
+ <path d="M-0.91 -0.78 C71.81 0.78, 144.15 -0.04, 249.54 0.93 M-0.29 0.51 C55.19 1.81, 110.83 1.76, 251.51 0.72 M250.63 -0.76 C250.96 87.25, 252.54 176.17, 251.48 263.11 M250.52 0.08 C251.68 103.86, 251.42 207.46, 251.58 263.74 M250.61 265.44 C157.64 267.2, 64.04 266.1, -1.62 264.1 M251.38 264.76 C161.63 263.52, 70.94 263.51, 0.35 264.16 M-1.04 265.34 C2.31 190.08, 1.15 115.07, 0.84 0.84 M0.27 264.27 C-1.34 179.25, -1.01 94.2, 0.45 0.04" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g stroke-linecap="round" transform="translate(267.375 456.5) rotate(0 144.25 63.75)">
+ <path d="M0.48 1.16 C114.8 -1.77, 226.6 -1.82, 289.37 0.19 M0.37 0.57 C70.6 -1.46, 142.5 -1.87, 288.39 -0.41 M289.98 1.65 C287.67 52.38, 288.73 101.99, 288.96 128.65 M288.32 0.63 C289.17 26.61, 289.5 51.51, 288.5 128.17 M289.65 128.81 C189.01 126.88, 89.27 126.99, -0.33 127.8 M288.44 126.9 C220.2 127.93, 153.44 128.19, 0.38 127.81 M1.29 125.65 C0.56 86.14, -2.12 46.84, -1.25 -0.95 M-0.29 126.52 C-0.49 77.56, -1.63 29.12, -0.87 0.1" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g transform="translate(58.625 208.5) rotate(0 69 80.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Select currency</text>
+ <text x="0" y="41" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">EUR</text>
+ <text x="0" y="64" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">CHF</text>
+ <text x="0" y="87" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">USD</text>
+ <text x="0" y="110" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">BTC</text>
+ <text x="0" y="133" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"></text>
+ <text x="0" y="156" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">[Withdraw]</text>
+ </g>
+ <g transform="translate(293.875 181.75) rotate(0 86.5 80.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Pending operations</text>
+ <text x="0" y="41" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Balance in selected</text>
+ <text x="0" y="64" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">currency</text>
+ <text x="0" y="87" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"></text>
+ <text x="0" y="110" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"></text>
+ <text x="0" y="133" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"></text>
+ <text x="0" y="156" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"></text>
+ </g>
+ <g transform="translate(326.875 492) rotate(0 56.5 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Send money</text>
+ </g>
+ <g transform="translate(628.625 187.5) rotate(0 84 23)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Transaction history</text>
+ <text x="0" y="41" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr"></text>
+ </g>
+ <g stroke-linecap="round" transform="translate(936.625 169.75) rotate(0 122 126.5)">
+ <path d="M0.91 -0.91 C52.69 -0.51, 104.5 -1.1, 244.72 -1.48 M-0.67 0.09 C92.15 2.03, 184.38 1.6, 244.79 -0.04 M244.06 0.75 C245.53 88.2, 243.77 173.71, 245.37 254.62 M243.79 -0.33 C245.21 62.95, 244.21 125.42, 243.2 252.8 M244.97 253.34 C159.06 253.7, 71.75 253.14, -0.26 254.22 M244.41 252.94 C178.4 254.73, 114.81 254.17, -0.61 253.76 M1.54 254.04 C2.62 171.09, 2.56 88.97, -0.74 1.1 M-0.07 253.07 C1.05 153.82, 1.14 54.8, 0.03 0.62" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g transform="translate(967.625 195.75) rotate(0 84 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Transaction details</text>
+ </g>
+ <g transform="translate(298.875 383.75) rotate(0 0.00004999999998744897 0.00004999999998744897)" stroke="none">
+ <path fill="#000000" d="M 1.57,-1.57 Q 1.57,-1.57 1.77,-1.27 1.98,-0.98 2.09,-0.64 2.20,-0.29 2.19,0.05 2.18,0.41 2.05,0.75 1.93,1.09 1.70,1.37 1.48,1.65 1.18,1.84 0.87,2.04 0.52,2.12 0.18,2.21 -0.17,2.18 -0.53,2.15 -0.86,2.01 -1.19,1.87 -1.46,1.63 -1.73,1.39 -1.90,1.07 -2.08,0.76 -2.15,0.41 -2.21,0.05 -2.17,-0.29 -2.12,-0.65 -1.96,-0.97 -1.80,-1.29 -1.54,-1.54 -1.29,-1.80 -0.97,-1.96 -0.65,-2.12 -0.29,-2.17 0.06,-2.21 0.41,-2.15 0.76,-2.08 1.07,-1.90 1.39,-1.73 1.63,-1.46 1.87,-1.19 2.01,-0.86 2.15,-0.53 2.18,-0.17 2.21,0.18 2.12,0.52 2.03,0.87 1.84,1.18 1.65,1.48 1.37,1.70 1.09,1.93 0.75,2.05 0.41,2.18 0.05,2.19 -0.30,2.20 -0.64,2.09 -0.98,1.98 -1.27,1.77 -1.57,1.56 -1.57,1.57 -1.57,1.57 -1.73,1.35 -1.90,1.14 -2.01,0.90 -2.12,0.66 -2.16,0.39 -2.21,0.13 -2.20,-0.13 -2.18,-0.40 -2.10,-0.65 -2.02,-0.91 -1.88,-1.14 -1.74,-1.36 -1.55,-1.55 -1.36,-1.74 -1.14,-1.88 -0.91,-2.02 -0.65,-2.10 -0.40,-2.18 -0.13,-2.20 0.13,-2.21 0.39,-2.16 0.66,-2.12 0.90,-2.01 1.14,-1.90 1.35,-1.73 1.57,-1.57 1.57,-1.57 L 1.57,-1.57 Z"></path>
+ </g>
+ <g transform="translate(310.875 354.75) rotate(0 40 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">[Deposit]</text>
+ </g>
+ <g transform="translate(757.625 250.5) rotate(0 35 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">[Delete]</text>
+ </g>
+ <g transform="translate(1042.625 261.75) rotate(0 35 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">[Forget]</text>
+ </g>
+ <g transform="translate(340 95.25) rotate(0 71 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">amount, subject</text>
+ </g>
+ <g transform="translate(81.25 51.5) rotate(0 58.5 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Send Invoice</text>
+ </g>
+ <g transform="translate(482.875 167.75) rotate(0 15 12.5)">
+ <text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">[?]</text>
+ </g>
+ <g stroke-linecap="round">
+ <g transform="translate(300 62.5) rotate(0 0.4346650920482489 32.930345324641095)">
+ <path d="M1.17 -0.33 C1.22 10.67, -0.64 55.15, -0.73 66.43 M0.33 -1.55 C0.85 9.53, 1.63 55.71, 1.6 67.41" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ </g>
+ <g transform="translate(137.5 91.5) rotate(0 26 11.5)">
+ <text x="0" y="18" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">target</text>
+ </g>
+ <g stroke-linecap="round" transform="translate(516.25 398.75) rotate(0 18.75 19.375)">
+ <path d="M12.92 1.01 C16.48 -0.48, 22.52 -0.81, 26.16 0.83 C29.8 2.47, 32.97 6.89, 34.75 10.85 C36.52 14.81, 37.63 20.49, 36.81 24.59 C35.99 28.68, 32.97 33.11, 29.85 35.42 C26.73 37.74, 22.08 38.7, 18.1 38.48 C14.12 38.25, 8.92 36.89, 5.96 34.05 C3 31.21, 0.83 25.65, 0.34 21.46 C-0.14 17.26, 0.68 12.58, 3.06 8.89 C5.44 5.2, 12.3 0.79, 14.62 -0.67 C16.95 -2.13, 16.64 -0.38, 16.99 0.13 M16.61 -0.86 C20.08 -1.86, 24.07 -0.08, 27.27 2.37 C30.47 4.81, 34.22 10.05, 35.83 13.82 C37.43 17.6, 38.35 21.05, 36.9 25.03 C35.45 29.01, 30.68 35.52, 27.16 37.72 C23.63 39.92, 19.78 39.32, 15.77 38.25 C11.75 37.17, 5.65 34.69, 3.05 31.26 C0.46 27.83, -0.02 21.77, 0.2 17.64 C0.42 13.52, 1.52 9.63, 4.36 6.52 C7.2 3.41, 15.12 0.13, 17.26 -1 C19.41 -2.13, 17.2 -0.78, 17.2 -0.25" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+ <g stroke-linecap="round" transform="translate(192 397.625) rotate(0 18.75 19.375)">
+ <path d="M17.85 0.94 C21.44 0.46, 25.57 1.47, 28.86 3.74 C32.14 6.01, 36.59 10.58, 37.58 14.55 C38.57 18.53, 36.72 23.73, 34.8 27.61 C32.87 31.5, 29.45 35.99, 26.03 37.85 C22.62 39.72, 18.13 40.18, 14.29 38.81 C10.45 37.43, 5.33 33.05, 2.99 29.61 C0.64 26.17, -0.19 22.35, 0.2 18.17 C0.6 13.99, 2.27 7.39, 5.37 4.5 C8.48 1.61, 16.38 1.42, 18.84 0.81 C21.29 0.2, 20.1 0.81, 20.13 0.83 M12.52 -0.21 C16.27 -1.92, 22.65 -1.55, 26.39 0.39 C30.13 2.34, 33.29 7.72, 34.97 11.45 C36.64 15.19, 37.14 18.79, 36.44 22.81 C35.74 26.82, 33.76 32.77, 30.76 35.52 C27.76 38.27, 22.47 39.49, 18.43 39.31 C14.39 39.12, 9.35 37.3, 6.53 34.41 C3.7 31.53, 2.23 26.49, 1.47 22.01 C0.71 17.53, -0.16 11.26, 1.96 7.53 C4.09 3.8, 12.15 0.47, 14.23 -0.38 C16.3 -1.23, 14.25 1.84, 14.41 2.44" stroke="#000000" stroke-width="1" fill="none"></path>
+ </g>
+</svg>
+
+ \ No newline at end of file
diff --git a/wallet-start-manual-withdraw.svg b/wallet-start-manual-withdraw.svg
new file mode 100644
index 00000000..708b4cdb
--- /dev/null
+++ b/wallet-start-manual-withdraw.svg
@@ -0,0 +1,16 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 649.25 613" width="649.25" height="613">
+ <!-- svg-source:excalidraw -->
+ <!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2aWXPaOFx1MDAxY8Df+ylcdTAwMTj2taW6JfeNJqXlyEXClnRnp+PYXHUwMDAym/igtrnS6Xdf2STYYENdSnazMzCZJNaBZOmn/6nvryqVarRcdTAwMTjL6rtKVc5cct2xzUCfVV/H5VNcdTAwMTmEtu+pKpQ8h/4kMJKWVlx1MDAxNI3Dd2/fpj1qhu8ue0lHutKLQtXuL/VcXKl8T35nxlx0pFx1MDAxMene0JFJh6QqXHUwMDFkXG5cIr5Zeu57ybBcdTAwMWFcIphcdTAwMGLG8KqBXHUwMDFknqrhXCJpqtqB7oQyrYmLqro+ciZcdTAwMWW6gNc9+G0442cyYN101IHtONfRwlm+k25YkyAzpzBcbvx7+dk2Iyue10b5ql/oq1x1MDAxNUh7XHUwMDA1/mRoeTJcZtf6+GPdsKNFXFxcdTAwMDbAqnS5XGLvKmnJXFw9YU5Xz3FcdTAwMGZEUY1ujH7iO35cdTAwMTCP/lx1MDAwN0g+6fh3unE/VJPwzFWbKNC9cKxcdTAwMDdqV9J2s8f3Yohnvt2S9tCKVDHF2eJQJktcZjHhkKk9QKuaeKhx00x2++/NJbL0YPy4XHUwMDE01TB+yEwznuGHJSpP3ZO6XHUwMDFmr/eBhpNt0CDGXHUwMDExwFx1MDAxNGmloWmF0Fx1MDAxYlx1MDAxYi1cXO9p/S/NXHUwMDFlnUze3338XHUwMDFmQFNbx1x1MDAwNmr8XHUwMDE5oVx1MDAxMTVeRE1cdTAwMWVcdTAwMTmqcUSpwPjfQ2a5je2BOb+zvzgmXHUwMDFkTCdu53YyMCaZbdzBVbyehIK11cSU1Vx1MDAxMM0tXHUwMDA0XHUwMDEyqPD0ZEvzXHUwMDFidqjt+K9R3Hcnn9jgXGJcIo0xnp7N9EhraLPw6URzJYFcYtFwivf2XHUwMDEznWfGmzjOXHUwMDBlarri/JN9XHUwMDFkdq6QNVx1MDAxZVx1MDAxOTfWXGL1vmp5alwiOY82gcG1XHLBjVx1MDAxMclcdTAwMDHDWZ5cdTAwMTaEj6z8lFx1MDAxNUaxgIQzWMCK2GoyXGJcdTAwMDFcdTAwMDBcdTAwMTNcdTAwMWPCvVlJtz7ecjU/Y1x1MDAxMqiVNVx1MDAxNpml9b3o2n5ItFxyWCtt6K7tLJ5cZqjV19RcdTAwMWR7XHUwMDE4z7vqyEG0ps9cIltcdTAwMTlUq+rIz4o/PZSO7cnk4OwgeN41Qlx1MDAxOTVbJ99cdTAwMWGyaTeaI3tuXHL2lntEXHUwMDAzOYYxQDWiZT55pMlR/JVcdTAwMTB/QGNcdTAwMTCjjNLMWNxgq0VDiOpcdTAwMDRZXHUwMDE5g+aXxV87asvTL2bvZEIuKVuMOmdh0Chcclx1MDAwZuNrSnKpN9cshSeEaLHRucvKOVKTbLDggkIqYFx1MDAxMTRcdTAwMTBsVZpqXHUwMDA2XHUwMDEwXHUwMDAxRNmzaE23fv+V9q3ueHo1PNV7oze9Py+uy2lNuElcZqF5rSmOWnM/XHUwMDExg4hQnlx1MDAwMWGFtJDttFCgiTJudkmdKeeGpTZJvlSdiU5cdTAwMWEtfNd5oIEwyc2cXnwx5Hkpflx1MDAxOdpwuyiAtby8g+hI8F5cdTAwMDRrXHUwMDFjY1x1MDAwNDgvXHUwMDA0mImtdlx1MDAxZqGMaMpLOFx1MDAxY8OmdP1apDsyqHkyeqkkXHUwMDFicsE7/WFjMGjctuyW8/H8ejQqRTJcdTAwMTdrXHUwMDFjY46KXHUwMDE0N8FHjveUxJrS3MXGXHUwMDFlyznAqdpmMciadkBh/KHXfan0dj41xt1cdTAwMTnisD1tNHufXHUwMDAy7E6ih1L0kpxcdTAwMWPORFx1MDAxNVZhq6NcdTAwMTDeXHUwMDBiXlx1MDAwNDDgXHUwMDA0IFxc5HxDgLfSi6FiXHUwMDE3UnpAMay7qt2LXHUwMDE1v64xXHUwMDFiKMup0WOQf5hd3t+a901Y3vnOUcxggftccotcXKc1O/pI8lx1MDAxNpI5Rlx1MDAwNFx0rOWQTcxcdLBccmSm3FjONED35nhcdTAwMDczV5/aXHUwMDFkrWt/XHLOUPiZd/Wvp1edSWlm+FqiJl4uXHUwMDBleIFcdTAwMDWKMCig5tfi1JiRgcH/39A8lsV7PJqC3tBzZ/3eotG2NK970V9UXHUwMDFm6/dS8Yhwylx1MDAwMC5cbmfvyFBcdGVcdTAwMTlcdTAwMTDB+fN45qAhXHUwMDFh1sx84/7puv3LnjA/10vFsznflEVcdTAwMWPRXCKLXHUwMDEw4oL43y9p1SNWP1x1MDAxMVlcZlx1MDAxMEpcdTAwMGJF1naqIFx1MDAxMqpcdTAwMGbnv1x1MDAxMfDJ6d4w0oOoMrMjK075685L1cL3yOxdynpfmPVcdTAwMDdXXHUwMDFmoiGhLVxcPoopclFMklx0QK2MSVxcmFx1MDAwMiX8mPorIys5XHUwMDA3yqwsVsRgq1wi1rhiWlx1MDAxOZXPoojRtLlcYupcdTAwMDPR6UxcdTAwMWbshkXDTmNo5rHRg8CfbVxiS8hrXGZoyodmXGKuhc6WOVx1MDAxNFFjyvVTUlx1MDAxZSjjTbB8gFx1MDAxM9Wwsi5cdTAwMTDEXFxoQFx1MDAxOcyY5MlCqlx1MDAxMeFcdTAwMDRRqDFBIKb8yFnKWfIuXHUwMDA1nos6zeqnMMWMtK2cKV2nYYZcdTAwMGbhdo99O3tcdTAwMWIq/qT/VdItS1x1MDAxZVb///26sPV2TpLaXHUwMDFjIen35Vx1MDAxNJCjh9GJ77p2pF7qMp7k5sxcdTAwMTNx/972TNtcdTAwMWJu1knP3FKT9KrHZ8SSulnQL1v3eJhcblx1MDAwZuWW85bZv7xOTLVcdTAwMWZcdTAwMDXKYdVomcTv8vT32ouzm8nJYorE6LSNwVxitvz+yzlcYsXXfpRcdTAwMTCtXHUwMDAxSrhcIlx1MDAxNaq/mUtcdTAwMTPLXHUwMDE4XHUwMDFlrFHOkFDOO6RcXMvcRzj0jaDfXHUwMDE0YE9qXHUwMDAxUFx1MDAxNpstmZTyvlx1MDAwMqDottBvkl3yxPxcIvsvVVDsUJTdeed6tFBA4v74/OpcXGveydGslFMhNp1VXG5woVOBRFx1MDAwMT/HUN3PXHIrJfnii1x1MDAwNblcdTAwMWIxscWaiz2vxCVhVNkmnFx1MDAxZTDMXFw3zcpLz/stTlx1MDAwNqY/XHUwMDFlP7Q/3p663HjjXHUwMDBm0G1U2lEgJJe8Zlx1MDAxMFx1MDAxNuJcZoqCL8eQXVx1MDAxOaJcdFx1MDAxMFRgVpg42ZHBXHUwMDE2UHlcdTAwMTecPk9UpXFyo79pXd54l/2JRlxmy/t48eG+XFy+WNu8JchcdTAwMTCtiWOm7WC8UI0pq0jk71x1MDAwNMZrulx1MDAxNVx1MDAxN6VcIpWfoFx1MDAxNObhXHUwMDA04IHybIZcdTAwMWFFXHUwMDA2OySfa5tmVjRcdTAwMTVcYr9Xj3ZCVVx1MDAxZo+vIz2K65ZIV6e2nL3Pk/HHIPnElvmPVz/+XHUwMDAxs5+03iJ9<!-- payload-end -->
+ <defs>
+ <style>
+ @font-face {
+ font-family: "Virgil";
+ src: url("https://excalidraw.com/Virgil.woff2");
+ }
+ @font-face {
+ font-family: "Cascadia";
+ src: url("https://excalidraw.com/Cascadia.woff2");
+ }
+ </style>
+ </defs>
+ <rect x="0" y="0" width="649.25" height="613" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 65.5) rotate(0 313.75 268.75)"><path d="M-0.64 0.27 C183.16 -1.41, 366.24 -0.99, 627.16 0.51 M-0.15 -0.1 C236.49 0.74, 472.1 0.89, 627.58 0.04 M627.21 -0.72 C626.48 193.78, 626.92 387.94, 628.16 537.25 M627.6 0.23 C626.3 157.65, 626.19 315.27, 627.48 537.61 M628.24 537.4 C433.34 538.98, 237.43 537.81, -0.4 537.85 M627.22 537.62 C394.58 539.45, 161.17 539.58, 0.16 537.15 M-0.55 537.11 C1.14 370.78, 1.74 202.32, -0.78 -0.26 M0.14 537.27 C0.47 350.59, 0.24 163.71, -0.21 0.08" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(10.5 10) rotate(0 314.375 27.5)"><path d="M-0.69 -0.79 C127.48 -0.76, 255.38 -0.98, 629.49 0.7 M-0.2 -0.18 C126.63 0.32, 253.87 0.26, 628.57 0.36 M630.22 1.63 C628.12 11.75, 630.32 26.32, 628.28 53.54 M628.96 -0.46 C630.15 16.06, 629.64 31.3, 628.68 55.27 M627.95 55.16 C438.86 56.91, 249.65 56.3, -0.66 55.38 M628.6 55.01 C476.39 54.97, 323.43 54.72, 0.08 55.29 M-1.67 56.06 C-0.22 42.88, 0.37 29.96, -0.66 0.81 M-0.87 54.22 C-0.63 33.16, -0.58 12.55, -0.53 -0.4" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(85 169.25) rotate(0 141.25 26.25)"><path d="M1.21 0.41 C68.36 -0.08, 136.37 0.62, 283.47 1.06 M0.21 0.65 C93.87 0.11, 186.6 0.74, 282.54 0.55 M282.46 -1.04 C282.42 15.3, 281.47 35.08, 281.87 53.39 M281.66 -0.58 C283.36 18.87, 281.88 39.9, 282.65 52.09 M282.87 52.04 C184.95 49.55, 87.55 50.33, 0.38 52.12 M282.75 52.39 C209.93 50.11, 138.48 50.34, -0.32 52.46 M1.68 51.38 C1.52 39.89, 0.18 28.67, 1.43 -1.8 M-0.79 51.78 C-0.51 40.69, -0.69 30.09, 0.57 0.22" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(88.75 137) rotate(0 38 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">currency</text></g><g stroke-linecap="round" transform="translate(85 303) rotate(0 151.24999999999994 22.5)"><path d="M0.91 -1.14 C95.79 -1.44, 190.83 -2.21, 302.59 -0.46 M-0.67 -0.65 C98.46 0.86, 196.25 0.51, 303.17 -0.24 M303.21 -0.52 C304.15 10.5, 304.18 22.15, 302.86 44.2 M302.76 -0.89 C301.3 15.99, 301.72 33.25, 301.94 45.83 M303.14 44.41 C194.91 45.12, 86.09 43.91, -0.44 44.54 M303.16 44.91 C192.69 46.89, 83.74 46.68, -0.07 45.58 M1.14 45.24 C0.92 29.4, 1.3 13.59, -0.8 -0.83 M0.16 45.29 C0.93 32.24, 0.27 21.07, -0.38 -0.09" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(311.25 171.75) rotate(0 28.75 25)"><path d="M0.44 -0.77 C14.94 -1.14, 28.74 1, 57.05 -0.33 M0.08 0.59 C16.41 -0.48, 30.76 0.45, 58.18 0.53 M58.91 0.53 C59.04 14.79, 59.13 29.43, 59.12 50.44 M57.26 -0.91 C57.45 13.3, 58.12 25.66, 57.66 50.88 M58.73 49.01 C41.72 51.08, 27.73 49.64, 1.53 49.91 M57.62 49.75 C34.13 49.25, 11.8 50.29, 0.88 49.68 M0.38 51.81 C2.16 34.87, 0.54 24.89, 0.68 0.55 M0.44 49.31 C-1.01 30.58, 0.05 10.84, 0.61 0.3" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(86.25 267) rotate(0 43 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">exchange</text></g><g transform="translate(97.5 314.5) rotate(0 63 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">demo.taler.net</text></g><g transform="translate(113 185.75) rotate(0 21.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">EUR</text></g><g transform="translate(77.5 392) rotate(0 33 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">amount</text></g><g stroke-linecap="round" transform="translate(77.5 423) rotate(0 157.5 25.625)"><path d="M0.98 -1.01 C93.63 -1.56, 187.05 -3.04, 315.73 1.11 M-0.04 -0.04 C73.77 -1.1, 147.37 -0.72, 314.99 0.25 M315.02 0.27 C313.45 15.41, 314.14 29.54, 316.03 53 M315.56 -0.56 C314.38 19.16, 315.73 36.89, 315.62 50.8 M315.01 51.64 C198.27 52.87, 77.9 51.61, -0.42 51.5 M315.63 51.68 C235.57 50.27, 157.37 48.94, -0.59 51.29 M-1.47 51.7 C-2.4 38.22, -1.94 25.61, -0.44 -1.56 M0.68 50.43 C-1.32 35.36, 0.49 21.68, -0.96 -0.5" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(372.5 520.5) rotate(0 115 26.25)"><path d="M0.51 -0.64 C92.55 2.54, 183 2.13, 229.93 -0.07 M0.15 0.19 C65.69 1.02, 132.13 2.16, 229.76 -0.72 M229.11 1.89 C230.91 18.79, 228.5 38.07, 229.53 53.38 M230.66 0.83 C230.81 16.54, 229.79 34.44, 230.53 52.73 M229 53.36 C174.76 53.18, 121.58 52.1, 1.39 53.28 M230.71 52.74 C155.74 52.99, 81.01 53.08, -0.33 51.67 M0.52 53.55 C-1.36 39.47, -0.09 24.78, 0.87 1.84 M-0.64 51.9 C-0.32 36.64, 0.36 20.89, 0.12 -0.53" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(407.5 538.75) rotate(0 69.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">start withdrawal</text></g><g stroke-linecap="round" transform="translate(321.25 299) rotate(0 31.875 23.75)"><path d="M0.91 0.47 C20.99 -1.15, 47.29 1.06, 63.61 1.5 M-0.45 -0.41 C19.5 -0.46, 40.52 -0.3, 64.3 0.46 M65.47 1.81 C63.31 12.54, 65.46 28.51, 63.36 46.29 M63.61 0.61 C62.98 11.81, 62.86 25.37, 63.81 48.33 M62.66 48.86 C43.77 48.27, 24.95 48.59, -0.58 46.58 M64.61 47.11 C49.77 47.35, 34.75 47.13, 0.07 47.88 M-0.29 48.17 C-2.19 29.17, -0.3 11.96, -0.98 1.71 M0.7 47.1 C0.04 33.01, -1.35 17.47, -0.73 -0.83" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(352.60947862109833 311.65257480315864) rotate(0 1.4633860506549468 11.261301259659206)"><path d="M-0.49 -0.03 C-0.24 3.78, 0.68 18.59, 1.29 22.17 M1.45 -1.1 C2.08 2.97, 3.2 19.72, 3.42 23.62" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(352.60947862109833 311.65257480315864) rotate(0 1.4633860506549468 11.261301259659206)"><path d="M-1.12 12.29 C-0.05 16.92, 0.53 18.56, 2.44 24.48 M-0.95 13.8 C0.07 16.86, 1.42 19.69, 3.08 23.78" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(352.60947862109833 311.65257480315864) rotate(0 1.4633860506549468 11.261301259659206)"><path d="M6.55 11.8 C5.47 16.54, 3.89 18.32, 2.44 24.48 M6.73 13.31 C5.4 16.51, 4.4 19.5, 3.08 23.78" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(337.05473931054917 184.57628740157924) rotate(0 1.5295271914883983 11.188407159857434)"><path d="M0.43 -0.55 C0.52 3.3, 1.07 19.06, 1.58 22.93 M-0.8 1.78 C-0.42 5.28, 3.19 17.38, 3.86 21.11" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(337.05473931054917 184.57628740157924) rotate(0 1.5295271914883983 11.188407159857434)"><path d="M-2.85 10.97 C0.59 16.31, 2.72 18.44, 3.1 22.21 M-2.68 11.97 C0.41 15.44, 2.12 18.93, 3.78 21.23" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(337.05473931054917 184.57628740157924) rotate(0 1.5295271914883983 11.188407159857434)"><path d="M4.63 9.18 C5.18 15.24, 4.43 18.06, 3.1 22.21 M4.8 10.19 C5.06 14.27, 3.92 18.44, 3.78 21.23" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(422.5 316.75) rotate(0 64 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Add exchange</text></g><g stroke-linecap="round" transform="translate(76.25 424.75) rotate(0 50 25.625)"><path d="M0.77 1.74 C24.32 0.96, 44.2 -1.69, 98.65 1.57 M-0.69 0.75 C21.48 -0.76, 43.4 0.32, 100.75 -0.92 M100.71 -0.97 C99.78 15.92, 101.32 30.35, 99.88 50.27 M100.65 0.76 C99.43 14.4, 101.11 27.48, 100.96 50.81 M98.17 49.87 C65.22 52.06, 33 50.69, 1.35 50.22 M99.74 51.11 C68.13 52.11, 37.2 52.49, -0.3 50.26 M-1.09 53.01 C1.03 36.68, -0.88 21.05, -0.53 0.24 M0.79 50.4 C0.83 34.32, -0.05 16.09, 0.79 0.09" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(104.75 438.875) rotate(0 21.5 11.5)"><text x="21.5" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">EUR</text></g></svg> \ No newline at end of file
diff --git a/wallet/wallet-core.md b/wallet/wallet-core.md
new file mode 100644
index 00000000..e5bed3cf
--- /dev/null
+++ b/wallet/wallet-core.md
@@ -0,0 +1,3135 @@
+# Wallet-Core API Documentation
+This file is auto-generated from [wallet-core](https://git.taler.net/wallet-core.git/tree/packages/taler-wallet-core/src/wallet-api-types.ts).
+## Overview
+### Unknown Group
+* [InitWalletOp](#initwalletop)
+* [GetVersionOp](#getversionop)
+### Basic Wallet Information
+* [GetBalancesOp](#getbalancesop)
+### Managing Transactions
+* [GetTransactionsOp](#gettransactionsop)
+* [GetTransactionByIdOp](#gettransactionbyidop)
+* [RetryPendingNowOp](#retrypendingnowop)
+* [DeleteTransactionOp](#deletetransactionop)
+* [RetryTransactionOp](#retrytransactionop)
+### Withdrawals
+* [GetWithdrawalDetailsForAmountOp](#getwithdrawaldetailsforamountop)
+* [GetWithdrawalDetailsForUriOp](#getwithdrawaldetailsforuriop)
+* [AcceptBankIntegratedWithdrawalOp](#acceptbankintegratedwithdrawalop)
+* [AcceptManualWithdrawalOp](#acceptmanualwithdrawalop)
+### Merchant Payments
+* [PreparePayForUriOp](#preparepayforuriop)
+* [GetContractTermsDetailsOp](#getcontracttermsdetailsop)
+* [ConfirmPayOp](#confirmpayop)
+* [AbortPayWithRefundOp](#abortpaywithrefundop)
+* [ApplyRefundOp](#applyrefundop)
+* [ApplyRefundFromPurchaseIdOp](#applyrefundfrompurchaseidop)
+* [PrepareRefundOp](#preparerefundop)
+### Tipping
+* [PrepareTipOp](#preparetipop)
+* [AcceptTipOp](#accepttipop)
+### Exchange Management
+* [ListExchangesOp](#listexchangesop)
+* [AddExchangeOp](#addexchangeop)
+* [ListKnownBankAccountsOp](#listknownbankaccountsop)
+* [AddKnownBankAccountsOp](#addknownbankaccountsop)
+* [ForgetKnownBankAccountsOp](#forgetknownbankaccountsop)
+* [SetExchangeTosAcceptedOp](#setexchangetosacceptedop)
+* [GetExchangeTosOp](#getexchangetosop)
+* [GetExchangeDetailedInfoOp](#getexchangedetailedinfoop)
+* [ListCurrenciesOp](#listcurrenciesop)
+### Deposits
+* [CreateDepositGroupOp](#createdepositgroupop)
+* [TrackDepositGroupOp](#trackdepositgroupop)
+* [GetFeeForDepositOp](#getfeefordepositop)
+* [PrepareDepositOp](#preparedepositop)
+### Backups
+* [ExportBackupRecoveryOp](#exportbackuprecoveryop)
+* [ImportBackupRecoveryOp](#importbackuprecoveryop)
+* [RunBackupCycleOp](#runbackupcycleop)
+* [ExportBackupOp](#exportbackupop)
+* [AddBackupProviderOp](#addbackupproviderop)
+* [RemoveBackupProviderOp](#removebackupproviderop)
+* [GetBackupInfoOp](#getbackupinfoop)
+* [SetWalletDeviceIdOp](#setwalletdeviceidop)
+* [ExportBackupPlainOp](#exportbackupplainop)
+### Peer Payments
+* [InitiatePeerPushPaymentOp](#initiatepeerpushpaymentop)
+* [CheckPeerPushPaymentOp](#checkpeerpushpaymentop)
+* [AcceptPeerPushPaymentOp](#acceptpeerpushpaymentop)
+* [InitiatePeerPullPaymentOp](#initiatepeerpullpaymentop)
+* [CheckPeerPullPaymentOp](#checkpeerpullpaymentop)
+* [AcceptPeerPullPaymentOp](#acceptpeerpullpaymentop)
+### Database Management
+* [ExportDbOp](#exportdbop)
+* [ImportDbOp](#importdbop)
+* [ClearDbOp](#cleardbop)
+* [RecycleOp](#recycleop)
+### Testing and Debugging
+* [ApplyDevExperimentOp](#applydevexperimentop)
+* [SetDevModeOp](#setdevmodeop)
+* [RunIntegrationTestOp](#runintegrationtestop)
+* [TestCryptoOp](#testcryptoop)
+* [WithdrawTestBalanceOp](#withdrawtestbalanceop)
+* [WithdrawTestkudosOp](#withdrawtestkudosop)
+* [TestPayOp](#testpayop)
+* [WithdrawFakebankOp](#withdrawfakebankop)
+* [GetPendingTasksOp](#getpendingtasksop)
+* [DumpCoinsOp](#dumpcoinsop)
+* [SetCoinSuspendedOp](#setcoinsuspendedop)
+* [ForceRefreshOp](#forcerefreshop)
+## Operation Reference
+### InitWalletOp
+```typescript
+/**
+ * Initialize wallet-core.
+ *
+ * Must be the request before any other operations.
+ */
+export type InitWalletOp = {
+ op: WalletApiOperation.InitWallet;
+ request: EmptyObject;
+ response: InitResponse;
+};
+// InitWallet = "initWallet"
+
+```
+```typescript
+export interface InitResponse {
+ versionInfo: WalletCoreVersion;
+}
+
+```
+
+### GetVersionOp
+```typescript
+export type GetVersionOp = {
+ op: WalletApiOperation.GetVersion;
+ request: EmptyObject;
+ response: WalletCoreVersion;
+};
+// GetVersion = "getVersion"
+
+```
+
+### GetBalancesOp
+```typescript
+/**
+ * Get current wallet balance.
+ */
+export type GetBalancesOp = {
+ op: WalletApiOperation.GetBalances;
+ request: EmptyObject;
+ response: BalancesResponse;
+};
+// GetBalances = "getBalances"
+
+```
+```typescript
+export interface BalancesResponse {
+ balances: Balance[];
+}
+
+```
+```typescript
+export interface Balance {
+ available: AmountString;
+ pendingIncoming: AmountString;
+ pendingOutgoing: AmountString;
+ hasPendingTransactions: boolean;
+ requiresUserInput: boolean;
+}
+
+```
+
+### GetTransactionsOp
+```typescript
+/**
+ * Get transactions.
+ */
+export type GetTransactionsOp = {
+ op: WalletApiOperation.GetTransactions;
+ request: TransactionsRequest;
+ response: TransactionsResponse;
+};
+// GetTransactions = "getTransactions"
+
+```
+```typescript
+export interface TransactionsRequest {
+ /**
+ * return only transactions in the given currency
+ */
+ currency?: string;
+ /**
+ * if present, results will be limited to transactions related to the given search string
+ */
+ search?: string;
+}
+
+```
+```typescript
+export interface TransactionsResponse {
+ transactions: Transaction[];
+}
+
+```
+
+### GetTransactionByIdOp
+```typescript
+export type GetTransactionByIdOp = {
+ op: WalletApiOperation.GetTransactionById;
+ request: TransactionByIdRequest;
+ response: Transaction;
+};
+// GetTransactionById = "getTransactionById"
+
+```
+```typescript
+export interface TransactionByIdRequest {
+ transactionId: string;
+}
+
+```
+
+### RetryPendingNowOp
+```typescript
+export type RetryPendingNowOp = {
+ op: WalletApiOperation.RetryPendingNow;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+// RetryPendingNow = "retryPendingNow"
+
+```
+
+### DeleteTransactionOp
+```typescript
+/**
+ * Delete a transaction locally in the wallet.
+ */
+export type DeleteTransactionOp = {
+ op: WalletApiOperation.DeleteTransaction;
+ request: DeleteTransactionRequest;
+ response: EmptyObject;
+};
+// DeleteTransaction = "deleteTransaction"
+
+```
+```typescript
+export interface DeleteTransactionRequest {
+ transactionId: string;
+}
+
+```
+
+### RetryTransactionOp
+```typescript
+/**
+ * Immediately retry a transaction.
+ */
+export type RetryTransactionOp = {
+ op: WalletApiOperation.RetryTransaction;
+ request: RetryTransactionRequest;
+ response: EmptyObject;
+};
+// RetryTransaction = "retryTransaction"
+
+```
+```typescript
+export interface RetryTransactionRequest {
+ transactionId: string;
+}
+
+```
+
+### GetWithdrawalDetailsForAmountOp
+```typescript
+/**
+ * Get details for withdrawing a particular amount (manual withdrawal).
+ */
+export type GetWithdrawalDetailsForAmountOp = {
+ op: WalletApiOperation.GetWithdrawalDetailsForAmount;
+ request: GetWithdrawalDetailsForAmountRequest;
+ response: ManualWithdrawalDetails;
+};
+// GetWithdrawalDetailsForAmount = "getWithdrawalDetailsForAmount"
+
+```
+```typescript
+export interface GetWithdrawalDetailsForAmountRequest {
+ exchangeBaseUrl: string;
+ amount: string;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface ManualWithdrawalDetails {
+ /**
+ * Did the user accept the current version of the exchange's
+ * terms of service?
+ */
+ tosAccepted: boolean;
+ /**
+ * Amount that the user will transfer to the exchange.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that will be added to the user's wallet balance.
+ */
+ amountEffective: AmountString;
+ /**
+ * Ways to pay the exchange.
+ */
+ paytoUris: string[];
+ /**
+ * If the exchange supports age-restricted coins it will return
+ * the array of ages.
+ */
+ ageRestrictionOptions?: number[];
+}
+
+```
+
+### GetWithdrawalDetailsForUriOp
+```typescript
+/**
+ * Get details for withdrawing via a particular taler:// URI.
+ */
+export type GetWithdrawalDetailsForUriOp = {
+ op: WalletApiOperation.GetWithdrawalDetailsForUri;
+ request: GetWithdrawalDetailsForUriRequest;
+ response: WithdrawUriInfoResponse;
+};
+// GetWithdrawalDetailsForUri = "getWithdrawalDetailsForUri"
+
+```
+```typescript
+export interface GetWithdrawalDetailsForUriRequest {
+ talerWithdrawUri: string;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface WithdrawUriInfoResponse {
+ amount: AmountString;
+ defaultExchangeBaseUrl?: string;
+ possibleExchanges: ExchangeListItem[];
+}
+
+```
+
+### AcceptBankIntegratedWithdrawalOp
+```typescript
+/**
+ * Accept a bank-integrated withdrawal.
+ */
+export type AcceptBankIntegratedWithdrawalOp = {
+ op: WalletApiOperation.AcceptBankIntegratedWithdrawal;
+ request: AcceptBankIntegratedWithdrawalRequest;
+ response: AcceptWithdrawalResponse;
+};
+// AcceptBankIntegratedWithdrawal = "acceptBankIntegratedWithdrawal"
+
+```
+```typescript
+export interface AcceptBankIntegratedWithdrawalRequest {
+ talerWithdrawUri: string;
+ exchangeBaseUrl: string;
+ forcedDenomSel?: ForcedDenomSel;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface AcceptWithdrawalResponse {
+ reservePub: string;
+ confirmTransferUrl?: string;
+ transactionId: string;
+}
+
+```
+
+### AcceptManualWithdrawalOp
+```typescript
+/**
+ * Create a manual withdrawal.
+ */
+export type AcceptManualWithdrawalOp = {
+ op: WalletApiOperation.AcceptManualWithdrawal;
+ request: AcceptManualWithdrawalRequest;
+ response: AcceptManualWithdrawalResult;
+};
+// AcceptManualWithdrawal = "acceptManualWithdrawal"
+
+```
+```typescript
+export interface AcceptManualWithdrawalRequest {
+ exchangeBaseUrl: string;
+ amount: string;
+ restrictAge?: number;
+}
+
+```
+```typescript
+export interface AcceptManualWithdrawalResult {
+ /**
+ * Payto URIs that can be used to fund the withdrawal.
+ */
+ exchangePaytoUris: string[];
+ /**
+ * Public key of the newly created reserve.
+ */
+ reservePub: string;
+ transactionId: string;
+}
+
+```
+
+### PreparePayForUriOp
+```typescript
+/**
+ * Prepare to make a payment
+ */
+export type PreparePayForUriOp = {
+ op: WalletApiOperation.PreparePayForUri;
+ request: PreparePayRequest;
+ response: PreparePayResult;
+};
+// PreparePayForUri = "preparePayForUri"
+
+```
+```typescript
+export interface PreparePayRequest {
+ talerPayUri: string;
+}
+
+```
+```typescript
+/**
+ * Result of a prepare pay operation.
+ */
+export declare type PreparePayResult =
+ | PreparePayResultInsufficientBalance
+ | PreparePayResultAlreadyConfirmed
+ | PreparePayResultPaymentPossible;
+
+```
+```typescript
+export interface PreparePayResultInsufficientBalance {
+ status: PreparePayResultType.InsufficientBalance;
+ proposalId: string;
+ contractTerms: MerchantContractTerms;
+ amountRaw: string;
+ noncePriv: string;
+}
+
+```
+```typescript
+export interface PreparePayResultAlreadyConfirmed {
+ status: PreparePayResultType.AlreadyConfirmed;
+ contractTerms: MerchantContractTerms;
+ paid: boolean;
+ amountRaw: string;
+ amountEffective: string;
+ contractTermsHash: string;
+ proposalId: string;
+}
+
+```
+```typescript
+/**
+ * Payment is possible.
+ */
+export interface PreparePayResultPaymentPossible {
+ status: PreparePayResultType.PaymentPossible;
+ proposalId: string;
+ contractTerms: MerchantContractTerms;
+ contractTermsHash: string;
+ amountRaw: string;
+ amountEffective: string;
+ noncePriv: string;
+}
+
+```
+
+### GetContractTermsDetailsOp
+```typescript
+export type GetContractTermsDetailsOp = {
+ op: WalletApiOperation.GetContractTermsDetails;
+ request: GetContractTermsDetailsRequest;
+ response: WalletContractData;
+};
+// GetContractTermsDetails = "getContractTermsDetails"
+
+```
+```typescript
+export interface GetContractTermsDetailsRequest {
+ proposalId: string;
+}
+
+```
+```typescript
+/**
+ * Data extracted from the contract terms that is relevant for payment
+ * processing in the wallet.
+ */
+export interface WalletContractData {
+ products?: Product[];
+ summaryI18n:
+ | {
+ [lang_tag: string]: string;
+ }
+ | undefined;
+ /**
+ * Fulfillment URL, or the empty string if the order has no fulfillment URL.
+ *
+ * Stored as a non-nullable string as we use this field for IndexedDB indexing.
+ */
+ fulfillmentUrl: string;
+ contractTermsHash: string;
+ fulfillmentMessage?: string;
+ fulfillmentMessageI18n?: InternationalizedString;
+ merchantSig: string;
+ merchantPub: string;
+ merchant: MerchantInfo;
+ amount: AmountString;
+ orderId: string;
+ merchantBaseUrl: string;
+ summary: string;
+ autoRefund: TalerProtocolDuration | undefined;
+ maxWireFee: AmountString;
+ wireFeeAmortization: number;
+ payDeadline: TalerProtocolTimestamp;
+ refundDeadline: TalerProtocolTimestamp;
+ allowedAuditors: AllowedAuditorInfo[];
+ allowedExchanges: AllowedExchangeInfo[];
+ timestamp: TalerProtocolTimestamp;
+ wireMethod: string;
+ wireInfoHash: string;
+ maxDepositFee: AmountString;
+ minimumAge?: number;
+ deliveryDate: TalerProtocolTimestamp | undefined;
+ deliveryLocation: Location | undefined;
+}
+
+```
+```typescript
+export interface AllowedAuditorInfo {
+ auditorBaseUrl: string;
+ auditorPub: string;
+}
+
+```
+```typescript
+export interface AllowedExchangeInfo {
+ exchangeBaseUrl: string;
+ exchangePub: string;
+}
+
+```
+
+### ConfirmPayOp
+```typescript
+/**
+ * Confirm a payment that was previously prepared with
+ * {@link PreparePayForUriOp}
+ */
+export type ConfirmPayOp = {
+ op: WalletApiOperation.ConfirmPay;
+ request: ConfirmPayRequest;
+ response: ConfirmPayResult;
+};
+// ConfirmPay = "confirmPay"
+
+```
+```typescript
+export interface ConfirmPayRequest {
+ proposalId: string;
+ sessionId?: string;
+ forcedCoinSel?: ForcedCoinSel;
+}
+
+```
+```typescript
+export declare type ConfirmPayResult =
+ | ConfirmPayResultDone
+ | ConfirmPayResultPending;
+
+```
+```typescript
+/**
+ * Result for confirmPay
+ */
+export interface ConfirmPayResultDone {
+ type: ConfirmPayResultType.Done;
+ contractTerms: MerchantContractTerms;
+ transactionId: string;
+}
+
+```
+```typescript
+export interface ConfirmPayResultPending {
+ type: ConfirmPayResultType.Pending;
+ transactionId: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+
+### AbortPayWithRefundOp
+```typescript
+/**
+ * Abort a pending payment with a refund.
+ */
+export type AbortPayWithRefundOp = {
+ op: WalletApiOperation.AbortFailedPayWithRefund;
+ request: AbortPayWithRefundRequest;
+ response: EmptyObject;
+};
+// AbortFailedPayWithRefund = "abortFailedPayWithRefund"
+
+```
+```typescript
+export interface AbortPayWithRefundRequest {
+ proposalId: string;
+}
+
+```
+
+### ApplyRefundOp
+```typescript
+/**
+ * Check for a refund based on a taler://refund URI.
+ */
+export type ApplyRefundOp = {
+ op: WalletApiOperation.ApplyRefund;
+ request: ApplyRefundRequest;
+ response: ApplyRefundResponse;
+};
+// ApplyRefund = "applyRefund"
+
+```
+```typescript
+export interface ApplyRefundRequest {
+ talerRefundUri: string;
+}
+
+```
+
+### ApplyRefundFromPurchaseIdOp
+```typescript
+export type ApplyRefundFromPurchaseIdOp = {
+ op: WalletApiOperation.ApplyRefundFromPurchaseId;
+ request: ApplyRefundFromPurchaseIdRequest;
+ response: ApplyRefundResponse;
+};
+// ApplyRefundFromPurchaseId = "applyRefundFromPurchaseId"
+
+```
+```typescript
+export interface ApplyRefundFromPurchaseIdRequest {
+ purchaseId: string;
+}
+
+```
+
+### PrepareRefundOp
+```typescript
+export type PrepareRefundOp = {
+ op: WalletApiOperation.PrepareRefund;
+ request: PrepareRefundRequest;
+ response: PrepareRefundResult;
+};
+// PrepareRefund = "prepareRefund"
+
+```
+```typescript
+export interface PrepareRefundRequest {
+ talerRefundUri: string;
+}
+
+```
+```typescript
+export interface PrepareRefundResult {
+ proposalId: string;
+ effectivePaid: AmountString;
+ gone: AmountString;
+ granted: AmountString;
+ pending: boolean;
+ awaiting: AmountString;
+ info: OrderShortInfo;
+}
+
+```
+
+### PrepareTipOp
+```typescript
+/**
+ * Query and store information about a tip.
+ */
+export type PrepareTipOp = {
+ op: WalletApiOperation.PrepareTip;
+ request: PrepareTipRequest;
+ response: PrepareTipResult;
+};
+// PrepareTip = "prepareTip"
+
+```
+```typescript
+export interface PrepareTipRequest {
+ talerTipUri: string;
+}
+
+```
+```typescript
+export interface PrepareTipResult {
+ /**
+ * Unique ID for the tip assigned by the wallet.
+ * Typically different from the merchant-generated tip ID.
+ */
+ walletTipId: string;
+ /**
+ * Has the tip already been accepted?
+ */
+ accepted: boolean;
+ /**
+ * Amount that the merchant gave.
+ */
+ tipAmountRaw: AmountString;
+ /**
+ * Amount that arrived at the wallet.
+ * Might be lower than the raw amount due to fees.
+ */
+ tipAmountEffective: AmountString;
+ /**
+ * Base URL of the merchant backend giving then tip.
+ */
+ merchantBaseUrl: string;
+ /**
+ * Base URL of the exchange that is used to withdraw the tip.
+ * Determined by the merchant, the wallet/user has no choice here.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Time when the tip will expire. After it expired, it can't be picked
+ * up anymore.
+ */
+ expirationTimestamp: TalerProtocolTimestamp;
+}
+
+```
+
+### AcceptTipOp
+```typescript
+/**
+ * Accept a tip.
+ */
+export type AcceptTipOp = {
+ op: WalletApiOperation.AcceptTip;
+ request: AcceptTipRequest;
+ response: EmptyObject;
+};
+// AcceptTip = "acceptTip"
+
+```
+```typescript
+export interface AcceptTipRequest {
+ walletTipId: string;
+}
+
+```
+
+### ListExchangesOp
+```typescript
+/**
+ * List exchanges known to the wallet.
+ */
+export type ListExchangesOp = {
+ op: WalletApiOperation.ListExchanges;
+ request: EmptyObject;
+ response: ExchangesListResponse;
+};
+// ListExchanges = "listExchanges"
+
+```
+```typescript
+export interface ExchangesListResponse {
+ exchanges: ExchangeListItem[];
+}
+
+```
+
+### AddExchangeOp
+```typescript
+/**
+ * Add / force-update an exchange.
+ */
+export type AddExchangeOp = {
+ op: WalletApiOperation.AddExchange;
+ request: AddExchangeRequest;
+ response: EmptyObject;
+};
+// AddExchange = "addExchange"
+
+```
+
+### ListKnownBankAccountsOp
+```typescript
+export type ListKnownBankAccountsOp = {
+ op: WalletApiOperation.ListKnownBankAccounts;
+ request: ListKnownBankAccountsRequest;
+ response: KnownBankAccounts;
+};
+// ListKnownBankAccounts = "listKnownBankAccounts"
+
+```
+```typescript
+export interface ListKnownBankAccountsRequest {
+ currency?: string;
+}
+
+```
+```typescript
+export interface KnownBankAccounts {
+ accounts: KnownBankAccountsInfo[];
+}
+
+```
+```typescript
+export interface KnownBankAccountsInfo {
+ uri: PaytoUri;
+ kyc_completed: boolean;
+ currency: string;
+ alias: string;
+}
+
+```
+```typescript
+export declare type PaytoUri =
+ | PaytoUriUnknown
+ | PaytoUriIBAN
+ | PaytoUriTalerBank
+ | PaytoUriBitcoin;
+
+```
+```typescript
+export interface PaytoUriUnknown extends PaytoUriGeneric {
+ isKnown: false;
+}
+
+```
+```typescript
+export interface PaytoUriGeneric {
+ targetType: string;
+ targetPath: string;
+ params: {
+ [name: string]: string;
+ };
+}
+
+```
+```typescript
+export interface PaytoUriIBAN extends PaytoUriGeneric {
+ isKnown: true;
+ targetType: "iban";
+ iban: string;
+}
+
+```
+```typescript
+export interface PaytoUriTalerBank extends PaytoUriGeneric {
+ isKnown: true;
+ targetType: "x-taler-bank";
+ host: string;
+ account: string;
+}
+
+```
+```typescript
+export interface PaytoUriBitcoin extends PaytoUriGeneric {
+ isKnown: true;
+ targetType: "bitcoin";
+ segwitAddrs: Array<string>;
+}
+
+```
+
+### AddKnownBankAccountsOp
+```typescript
+export type AddKnownBankAccountsOp = {
+ op: WalletApiOperation.AddKnownBankAccounts;
+ request: AddKnownBankAccountsRequest;
+ response: EmptyObject;
+};
+// AddKnownBankAccounts = "addKnownBankAccounts"
+
+```
+```typescript
+export interface AddKnownBankAccountsRequest {
+ payto: string;
+ alias: string;
+ currency: string;
+}
+
+```
+
+### ForgetKnownBankAccountsOp
+```typescript
+export type ForgetKnownBankAccountsOp = {
+ op: WalletApiOperation.ForgetKnownBankAccounts;
+ request: ForgetKnownBankAccountsRequest;
+ response: EmptyObject;
+};
+// ForgetKnownBankAccounts = "forgetKnownBankAccounts"
+
+```
+```typescript
+export interface ForgetKnownBankAccountsRequest {
+ payto: string;
+}
+
+```
+
+### SetExchangeTosAcceptedOp
+```typescript
+/**
+ * Accept a particular version of the exchange terms of service.
+ */
+export type SetExchangeTosAcceptedOp = {
+ op: WalletApiOperation.SetExchangeTosAccepted;
+ request: AcceptExchangeTosRequest;
+ response: EmptyObject;
+};
+// SetExchangeTosAccepted = "setExchangeTosAccepted"
+
+```
+```typescript
+export interface AcceptExchangeTosRequest {
+ exchangeBaseUrl: string;
+ etag: string | undefined;
+}
+
+```
+
+### GetExchangeTosOp
+```typescript
+/**
+ * Get the current terms of a service of an exchange.
+ */
+export type GetExchangeTosOp = {
+ op: WalletApiOperation.GetExchangeTos;
+ request: GetExchangeTosRequest;
+ response: GetExchangeTosResult;
+};
+// GetExchangeTos = "getExchangeTos"
+
+```
+```typescript
+export interface GetExchangeTosRequest {
+ exchangeBaseUrl: string;
+ acceptedFormat?: string[];
+}
+
+```
+```typescript
+export interface GetExchangeTosResult {
+ /**
+ * Markdown version of the current ToS.
+ */
+ content: string;
+ /**
+ * Version tag of the current ToS.
+ */
+ currentEtag: string;
+ /**
+ * Version tag of the last ToS that the user has accepted,
+ * if any.
+ */
+ acceptedEtag: string | undefined;
+ /**
+ * Accepted content type
+ */
+ contentType: string;
+ tosStatus: ExchangeTosStatus;
+}
+
+```
+
+### GetExchangeDetailedInfoOp
+```typescript
+/**
+ * Get the current terms of a service of an exchange.
+ */
+export type GetExchangeDetailedInfoOp = {
+ op: WalletApiOperation.GetExchangeDetailedInfo;
+ request: AddExchangeRequest;
+ response: ExchangeDetailedResponse;
+};
+// GetExchangeDetailedInfo = "getExchangeDetailedInfo"
+
+```
+```typescript
+export interface ExchangeDetailedResponse {
+ exchange: ExchangeFullDetails;
+}
+
+```
+```typescript
+export interface ExchangeFullDetails {
+ exchangeBaseUrl: string;
+ currency: string;
+ paytoUris: string[];
+ tos: ExchangeTosStatusDetails;
+ auditors: ExchangeAuditor[];
+ wireInfo: WireInfo;
+ denomFees: DenomOperationMap<FeeDescription[]>;
+ transferFees: Record<string, FeeDescription[]>;
+ globalFees: FeeDescription[];
+}
+
+```
+```typescript
+export interface ExchangeTosStatusDetails {
+ acceptedVersion?: string;
+ currentVersion?: string;
+ contentType?: string;
+ content?: string;
+}
+
+```
+```typescript
+export interface WireInfo {
+ feesForType: WireFeeMap;
+ accounts: ExchangeAccount[];
+}
+
+```
+```typescript
+/**
+ * Information about one of the exchange's bank accounts.
+ */
+export interface ExchangeAccount {
+ payto_uri: string;
+ master_sig: string;
+}
+
+```
+```typescript
+export interface FeeDescription {
+ group: string;
+ from: AbsoluteTime;
+ until: AbsoluteTime;
+ fee?: AmountString;
+}
+
+```
+
+### ListCurrenciesOp
+```typescript
+/**
+ * List currencies known to the wallet.
+ */
+export type ListCurrenciesOp = {
+ op: WalletApiOperation.ListCurrencies;
+ request: EmptyObject;
+ response: WalletCurrencyInfo;
+};
+// ListCurrencies = "listCurrencies"
+
+```
+```typescript
+export interface WalletCurrencyInfo {
+ trustedAuditors: {
+ currency: string;
+ auditorPub: string;
+ auditorBaseUrl: string;
+ }[];
+ trustedExchanges: {
+ currency: string;
+ exchangeMasterPub: string;
+ exchangeBaseUrl: string;
+ }[];
+}
+
+```
+
+### CreateDepositGroupOp
+```typescript
+/**
+ * Create a new deposit group.
+ *
+ * Deposit groups are used to deposit multiple coins to a bank
+ * account, usually the wallet user's own bank account.
+ */
+export type CreateDepositGroupOp = {
+ op: WalletApiOperation.CreateDepositGroup;
+ request: CreateDepositGroupRequest;
+ response: CreateDepositGroupResponse;
+};
+// CreateDepositGroup = "createDepositGroup"
+
+```
+```typescript
+export interface CreateDepositGroupRequest {
+ depositPaytoUri: string;
+ amount: AmountString;
+}
+
+```
+```typescript
+export interface CreateDepositGroupResponse {
+ depositGroupId: string;
+ transactionId: string;
+}
+
+```
+
+### TrackDepositGroupOp
+```typescript
+/**
+ * Track the status of a deposit group by querying the exchange.
+ */
+export type TrackDepositGroupOp = {
+ op: WalletApiOperation.TrackDepositGroup;
+ request: TrackDepositGroupRequest;
+ response: TrackDepositGroupResponse;
+};
+// TrackDepositGroup = "trackDepositGroup"
+
+```
+```typescript
+export interface TrackDepositGroupRequest {
+ depositGroupId: string;
+}
+
+```
+```typescript
+export interface TrackDepositGroupResponse {
+ responses: {
+ status: number;
+ body: any;
+ }[];
+}
+
+```
+
+### GetFeeForDepositOp
+```typescript
+export type GetFeeForDepositOp = {
+ op: WalletApiOperation.GetFeeForDeposit;
+ request: GetFeeForDepositRequest;
+ response: DepositGroupFees;
+};
+// GetFeeForDeposit = "getFeeForDeposit"
+
+```
+```typescript
+export interface GetFeeForDepositRequest {
+ depositPaytoUri: string;
+ amount: AmountString;
+}
+
+```
+```typescript
+export interface DepositGroupFees {
+ coin: AmountString;
+ wire: AmountString;
+ refresh: AmountString;
+}
+
+```
+
+### PrepareDepositOp
+```typescript
+export type PrepareDepositOp = {
+ op: WalletApiOperation.PrepareDeposit;
+ request: PrepareDepositRequest;
+ response: PrepareDepositResponse;
+};
+// PrepareDeposit = "prepareDeposit"
+
+```
+```typescript
+export interface PrepareDepositRequest {
+ depositPaytoUri: string;
+ amount: AmountString;
+}
+
+```
+```typescript
+export interface PrepareDepositResponse {
+ totalDepositCost: AmountString;
+ effectiveDepositAmount: AmountString;
+}
+
+```
+
+### ExportBackupRecoveryOp
+```typescript
+/**
+ * Export the recovery information for the wallet.
+ */
+export type ExportBackupRecoveryOp = {
+ op: WalletApiOperation.ExportBackupRecovery;
+ request: EmptyObject;
+ response: BackupRecovery;
+};
+// ExportBackupRecovery = "exportBackupRecovery"
+
+```
+
+### ImportBackupRecoveryOp
+```typescript
+/**
+ * Import recovery information into the wallet.
+ */
+export type ImportBackupRecoveryOp = {
+ op: WalletApiOperation.ImportBackupRecovery;
+ request: RecoveryLoadRequest;
+ response: EmptyObject;
+};
+// ImportBackupRecovery = "importBackupRecovery"
+
+```
+```typescript
+/**
+ * Load recovery information into the wallet.
+ */
+export interface RecoveryLoadRequest {
+ recovery: BackupRecovery;
+ strategy?: RecoveryMergeStrategy;
+}
+
+```
+```typescript
+/**
+ * Strategy for loading recovery information.
+ */
+export declare enum RecoveryMergeStrategy {
+ /**
+ * Keep the local wallet root key, import and take over providers.
+ */
+ Ours = "ours",
+ /**
+ * Migrate to the wallet root key from the recovery information.
+ */
+ Theirs = "theirs",
+}
+
+```
+
+### RunBackupCycleOp
+```typescript
+/**
+ * Manually make and upload a backup.
+ */
+export type RunBackupCycleOp = {
+ op: WalletApiOperation.RunBackupCycle;
+ request: RunBackupCycleRequest;
+ response: EmptyObject;
+};
+// RunBackupCycle = "runBackupCycle"
+
+```
+```typescript
+export interface RunBackupCycleRequest {
+ /**
+ * List of providers to backup or empty for all known providers.
+ */
+ providers?: Array<string>;
+}
+
+```
+
+### ExportBackupOp
+```typescript
+export type ExportBackupOp = {
+ op: WalletApiOperation.ExportBackup;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+// ExportBackup = "exportBackup"
+
+```
+
+### AddBackupProviderOp
+```typescript
+/**
+ * Add a new backup provider.
+ */
+export type AddBackupProviderOp = {
+ op: WalletApiOperation.AddBackupProvider;
+ request: AddBackupProviderRequest;
+ response: EmptyObject;
+};
+// AddBackupProvider = "addBackupProvider"
+
+```
+```typescript
+export interface AddBackupProviderRequest {
+ backupProviderBaseUrl: string;
+ name: string;
+ /**
+ * Activate the provider. Should only be done after
+ * the user has reviewed the provider.
+ */
+ activate?: boolean;
+}
+
+```
+
+### RemoveBackupProviderOp
+```typescript
+export type RemoveBackupProviderOp = {
+ op: WalletApiOperation.RemoveBackupProvider;
+ request: RemoveBackupProviderRequest;
+ response: EmptyObject;
+};
+// RemoveBackupProvider = "removeBackupProvider"
+
+```
+```typescript
+export interface RemoveBackupProviderRequest {
+ provider: string;
+}
+
+```
+
+### GetBackupInfoOp
+```typescript
+/**
+ * Get some useful stats about the backup state.
+ */
+export type GetBackupInfoOp = {
+ op: WalletApiOperation.GetBackupInfo;
+ request: EmptyObject;
+ response: BackupInfo;
+};
+// GetBackupInfo = "getBackupInfo"
+
+```
+```typescript
+export interface BackupInfo {
+ walletRootPub: string;
+ deviceId: string;
+ providers: ProviderInfo[];
+}
+
+```
+```typescript
+/**
+ * Information about one provider.
+ *
+ * We don't store the account key here,
+ * as that's derived from the wallet root key.
+ */
+export interface ProviderInfo {
+ active: boolean;
+ syncProviderBaseUrl: string;
+ name: string;
+ terms?: BackupProviderTerms;
+ /**
+ * Last communication issue with the provider.
+ */
+ lastError?: TalerErrorDetail;
+ lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp;
+ lastAttemptedBackupTimestamp?: TalerProtocolTimestamp;
+ paymentProposalIds: string[];
+ backupProblem?: BackupProblem;
+ paymentStatus: ProviderPaymentStatus;
+}
+
+```
+```typescript
+export interface BackupProviderTerms {
+ supportedProtocolVersion: string;
+ annualFee: AmountString;
+ storageLimitInMegabytes: number;
+}
+
+```
+```typescript
+export type BackupProblem =
+ | BackupUnreadableProblem
+ | BackupConflictingDeviceProblem;
+
+```
+```typescript
+export interface BackupUnreadableProblem {
+ type: "backup-unreadable";
+}
+
+```
+```typescript
+export interface BackupConflictingDeviceProblem {
+ type: "backup-conflicting-device";
+ otherDeviceId: string;
+ myDeviceId: string;
+ backupTimestamp: AbsoluteTime;
+}
+
+```
+```typescript
+export type ProviderPaymentStatus =
+ | ProviderPaymentTermsChanged
+ | ProviderPaymentPaid
+ | ProviderPaymentInsufficientBalance
+ | ProviderPaymentUnpaid
+ | ProviderPaymentPending;
+
+```
+```typescript
+export interface ProviderPaymentTermsChanged {
+ type: ProviderPaymentType.TermsChanged;
+ paidUntil: AbsoluteTime;
+ oldTerms: BackupProviderTerms;
+ newTerms: BackupProviderTerms;
+}
+
+```
+```typescript
+export interface ProviderPaymentPaid {
+ type: ProviderPaymentType.Paid;
+ paidUntil: AbsoluteTime;
+}
+
+```
+```typescript
+export interface ProviderPaymentInsufficientBalance {
+ type: ProviderPaymentType.InsufficientBalance;
+}
+
+```
+```typescript
+export interface ProviderPaymentUnpaid {
+ type: ProviderPaymentType.Unpaid;
+}
+
+```
+```typescript
+export interface ProviderPaymentPending {
+ type: ProviderPaymentType.Pending;
+}
+
+```
+
+### SetWalletDeviceIdOp
+```typescript
+/**
+ * Set the internal device ID of the wallet, used to
+ * identify whether a different/new wallet is accessing
+ * the backup of another wallet.
+ */
+export type SetWalletDeviceIdOp = {
+ op: WalletApiOperation.SetWalletDeviceId;
+ request: SetWalletDeviceIdRequest;
+ response: EmptyObject;
+};
+// SetWalletDeviceId = "setWalletDeviceId"
+
+```
+```typescript
+export interface SetWalletDeviceIdRequest {
+ /**
+ * New wallet device ID to set.
+ */
+ walletDeviceId: string;
+}
+
+```
+
+### ExportBackupPlainOp
+```typescript
+/**
+ * Export a backup JSON, mostly useful for testing.
+ */
+export type ExportBackupPlainOp = {
+ op: WalletApiOperation.ExportBackupPlain;
+ request: EmptyObject;
+ response: WalletBackupContentV1;
+};
+// ExportBackupPlain = "exportBackupPlain"
+
+```
+
+### InitiatePeerPushPaymentOp
+```typescript
+/**
+ * Initiate an outgoing peer push payment.
+ */
+export type InitiatePeerPushPaymentOp = {
+ op: WalletApiOperation.InitiatePeerPushPayment;
+ request: InitiatePeerPushPaymentRequest;
+ response: InitiatePeerPushPaymentResponse;
+};
+// InitiatePeerPushPayment = "initiatePeerPushPayment"
+
+```
+```typescript
+export interface InitiatePeerPushPaymentRequest {
+ exchangeBaseUrl?: string;
+ partialContractTerms: PeerContractTerms;
+}
+
+```
+```typescript
+export interface InitiatePeerPushPaymentResponse {
+ exchangeBaseUrl: string;
+ pursePub: string;
+ mergePriv: string;
+ contractPriv: string;
+ talerUri: string;
+ transactionId: string;
+}
+
+```
+```typescript
+/**
+ * Contract terms between two wallets (as opposed to a merchant and wallet).
+ */
+export interface PeerContractTerms {
+ amount: AmountString;
+ summary: string;
+ purse_expiration: TalerProtocolTimestamp;
+}
+
+```
+
+
+### CheckPeerPushPaymentOp
+```typescript
+/**
+ * Check an incoming peer push payment.
+ */
+export type CheckPeerPushPaymentOp = {
+ op: WalletApiOperation.CheckPeerPushPayment;
+ request: CheckPeerPushPaymentRequest;
+ response: CheckPeerPushPaymentResponse;
+};
+// CheckPeerPushPayment = "checkPeerPushPayment"
+
+```
+```typescript
+export interface CheckPeerPushPaymentRequest {
+ talerUri: string;
+}
+
+```
+```typescript
+export interface CheckPeerPushPaymentResponse {
+ contractTerms: PeerContractTerms;
+ amount: AmountString;
+ peerPushPaymentIncomingId: string;
+}
+
+```
+
+### AcceptPeerPushPaymentOp
+```typescript
+/**
+ * Accept an incoming peer push payment.
+ */
+export type AcceptPeerPushPaymentOp = {
+ op: WalletApiOperation.AcceptPeerPushPayment;
+ request: AcceptPeerPushPaymentRequest;
+ response: EmptyObject;
+};
+// AcceptPeerPushPayment = "acceptPeerPushPayment"
+
+```
+```typescript
+export interface AcceptPeerPushPaymentRequest {
+ /**
+ * Transparent identifier of the incoming peer push payment.
+ */
+ peerPushPaymentIncomingId: string;
+}
+
+```
+
+### InitiatePeerPullPaymentOp
+```typescript
+/**
+ * Initiate an outgoing peer pull payment.
+ */
+export type InitiatePeerPullPaymentOp = {
+ op: WalletApiOperation.InitiatePeerPullPayment;
+ request: InitiatePeerPullPaymentRequest;
+ response: InitiatePeerPullPaymentResponse;
+};
+// InitiatePeerPullPayment = "initiatePeerPullPayment"
+
+```
+```typescript
+export interface InitiatePeerPullPaymentRequest {
+ /**
+ * FIXME: Make this optional?
+ */
+ exchangeBaseUrl: string;
+ partialContractTerms: PeerContractTerms;
+}
+
+```
+```typescript
+export interface InitiatePeerPullPaymentResponse {
+ /**
+ * Taler URI for the other party to make the payment
+ * that was requested.
+ */
+ talerUri: string;
+ transactionId: string;
+}
+
+```
+
+### CheckPeerPullPaymentOp
+```typescript
+/**
+ * Prepare for an incoming peer pull payment.
+ */
+export type CheckPeerPullPaymentOp = {
+ op: WalletApiOperation.CheckPeerPullPayment;
+ request: CheckPeerPullPaymentRequest;
+ response: CheckPeerPullPaymentResponse;
+};
+// CheckPeerPullPayment = "checkPeerPullPayment"
+
+```
+```typescript
+export interface CheckPeerPullPaymentRequest {
+ talerUri: string;
+}
+
+```
+```typescript
+export interface CheckPeerPullPaymentResponse {
+ contractTerms: PeerContractTerms;
+ amount: AmountString;
+ peerPullPaymentIncomingId: string;
+}
+
+```
+
+### AcceptPeerPullPaymentOp
+```typescript
+/**
+ * Accept an incoming peer pull payment.
+ */
+export type AcceptPeerPullPaymentOp = {
+ op: WalletApiOperation.AcceptPeerPullPayment;
+ request: AcceptPeerPullPaymentRequest;
+ response: EmptyObject;
+};
+// AcceptPeerPullPayment = "acceptPeerPullPayment"
+
+```
+```typescript
+export interface AcceptPeerPullPaymentRequest {
+ /**
+ * Transparent identifier of the incoming peer pull payment.
+ */
+ peerPullPaymentIncomingId: string;
+}
+
+```
+
+### ExportDbOp
+```typescript
+/**
+ * Export the wallet database's contents to JSON.
+ */
+export type ExportDbOp = {
+ op: WalletApiOperation.ExportDb;
+ request: EmptyObject;
+ response: any;
+};
+// ExportDb = "exportDb"
+
+```
+
+### ImportDbOp
+```typescript
+export type ImportDbOp = {
+ op: WalletApiOperation.ImportDb;
+ request: any;
+ response: any;
+};
+// ImportDb = "importDb"
+
+```
+
+### ClearDbOp
+```typescript
+/**
+ * Dangerously clear the whole wallet database.
+ */
+export type ClearDbOp = {
+ op: WalletApiOperation.ClearDb;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+// ClearDb = "clearDb"
+
+```
+
+### RecycleOp
+```typescript
+/**
+ * Export a backup, clear the database and re-import it.
+ */
+export type RecycleOp = {
+ op: WalletApiOperation.Recycle;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+// Recycle = "recycle"
+
+```
+
+### ApplyDevExperimentOp
+```typescript
+/**
+ * Apply a developer experiment to the current wallet state.
+ *
+ * This allows UI developers / testers to play around without
+ * an elaborate test environment.
+ */
+export type ApplyDevExperimentOp = {
+ op: WalletApiOperation.ApplyDevExperiment;
+ request: ApplyDevExperimentRequest;
+ response: EmptyObject;
+};
+// ApplyDevExperiment = "applyDevExperiment"
+
+```
+```typescript
+export interface ApplyDevExperimentRequest {
+ devExperimentUri: string;
+}
+
+```
+
+### SetDevModeOp
+```typescript
+export type SetDevModeOp = {
+ op: WalletApiOperation.SetDevMode;
+ request: SetDevModeRequest;
+ response: EmptyObject;
+};
+// SetDevMode = "setDevMode"
+
+```
+```typescript
+export interface SetDevModeRequest {
+ devModeEnabled: boolean;
+}
+
+```
+
+### RunIntegrationTestOp
+```typescript
+/**
+ * Run a simple integration test on a test deployment
+ * of the exchange and merchant.
+ */
+export type RunIntegrationTestOp = {
+ op: WalletApiOperation.RunIntegrationTest;
+ request: IntegrationTestArgs;
+ response: EmptyObject;
+};
+// RunIntegrationTest = "runIntegrationTest"
+
+```
+```typescript
+export interface IntegrationTestArgs {
+ exchangeBaseUrl: string;
+ bankBaseUrl: string;
+ bankAccessApiBaseUrl?: string;
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ amountToWithdraw: string;
+ amountToSpend: string;
+}
+
+```
+
+### TestCryptoOp
+```typescript
+/**
+ * Test crypto worker.
+ */
+export type TestCryptoOp = {
+ op: WalletApiOperation.TestCrypto;
+ request: EmptyObject;
+ response: any;
+};
+// TestCrypto = "testCrypto"
+
+```
+
+### WithdrawTestBalanceOp
+```typescript
+/**
+ * Make withdrawal on a test deployment of the exchange
+ * and merchant.
+ */
+export type WithdrawTestBalanceOp = {
+ op: WalletApiOperation.WithdrawTestBalance;
+ request: WithdrawTestBalanceRequest;
+ response: EmptyObject;
+};
+// WithdrawTestBalance = "withdrawTestBalance"
+
+```
+```typescript
+export interface WithdrawTestBalanceRequest {
+ amount: string;
+ bankBaseUrl: string;
+ /**
+ * Bank access API base URL. Defaults to the bankBaseUrl.
+ */
+ bankAccessApiBaseUrl?: string;
+ exchangeBaseUrl: string;
+ forcedDenomSel?: ForcedDenomSel;
+}
+
+```
+
+### WithdrawTestkudosOp
+```typescript
+/**
+ * Make a withdrawal of testkudos on test.taler.net.
+ */
+export type WithdrawTestkudosOp = {
+ op: WalletApiOperation.WithdrawTestkudos;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+// WithdrawTestkudos = "withdrawTestkudos"
+
+```
+
+### TestPayOp
+```typescript
+/**
+ * Make a test payment using a test deployment of
+ * the exchange and merchant.
+ */
+export type TestPayOp = {
+ op: WalletApiOperation.TestPay;
+ request: TestPayArgs;
+ response: TestPayResult;
+};
+// TestPay = "testPay"
+
+```
+```typescript
+export interface TestPayArgs {
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ amount: string;
+ summary: string;
+ forcedCoinSel?: ForcedCoinSel;
+}
+
+```
+```typescript
+export interface TestPayResult {
+ payCoinSelection: PayCoinSelection;
+}
+
+```
+```typescript
+/**
+ * Result of selecting coins, contains the exchange, and selected
+ * coins with their denomination.
+ */
+export interface PayCoinSelection {
+ /**
+ * Amount requested by the merchant.
+ */
+ paymentAmount: AmountString;
+ /**
+ * Public keys of the coins that were selected.
+ */
+ coinPubs: string[];
+ /**
+ * Amount that each coin contributes.
+ */
+ coinContributions: AmountString[];
+ /**
+ * How much of the wire fees is the customer paying?
+ */
+ customerWireFees: AmountString;
+ /**
+ * How much of the deposit fees is the customer paying?
+ */
+ customerDepositFees: AmountString;
+}
+
+```
+
+### WithdrawFakebankOp
+```typescript
+/**
+ * Make a withdrawal from a fakebank, i.e.
+ * a bank where test users can be registered freely
+ * and testing APIs are available.
+ */
+export type WithdrawFakebankOp = {
+ op: WalletApiOperation.WithdrawFakebank;
+ request: WithdrawFakebankRequest;
+ response: EmptyObject;
+};
+// WithdrawFakebank = "withdrawFakebank"
+
+```
+```typescript
+export interface WithdrawFakebankRequest {
+ amount: AmountString;
+ exchange: string;
+ bank: string;
+}
+
+```
+
+### GetPendingTasksOp
+```typescript
+/**
+ * Get wallet-internal pending tasks.
+ */
+export type GetPendingTasksOp = {
+ op: WalletApiOperation.GetPendingOperations;
+ request: EmptyObject;
+ response: PendingTasksResponse;
+};
+// GetPendingOperations = "getPendingOperations"
+
+```
+```typescript
+/**
+ * Response returned from the pending operations API.
+ */
+export interface PendingOperationsResponse {
+ /**
+ * List of pending operations.
+ */
+ pendingOperations: PendingTaskInfo[];
+}
+
+```
+```typescript
+/**
+ * Information about a pending operation.
+ */
+export type PendingTaskInfo = PendingTaskInfoCommon &
+ (
+ | PendingExchangeUpdateTask
+ | PendingExchangeCheckRefreshTask
+ | PendingPurchaseTask
+ | PendingRefreshTask
+ | PendingTipPickupTask
+ | PendingWithdrawTask
+ | PendingRecoupTask
+ | PendingDepositTask
+ | PendingBackupTask
+ );
+
+```
+```typescript
+/**
+ * Fields that are present in every pending operation.
+ */
+export interface PendingTaskInfoCommon {
+ /**
+ * Type of the pending operation.
+ */
+ type: PendingTaskType;
+ /**
+ * Unique identifier for the pending task.
+ */
+ id: string;
+ /**
+ * Set to true if the operation indicates that something is really in progress,
+ * as opposed to some regular scheduled operation that can be tried later.
+ */
+ givesLifeness: boolean;
+ /**
+ * Operation is active and waiting for a longpoll result.
+ */
+ isLongpolling: boolean;
+ /**
+ * Operation is waiting to be executed.
+ */
+ isDue: boolean;
+ /**
+ * Timestamp when the pending operation should be executed next.
+ */
+ timestampDue: AbsoluteTime;
+ /**
+ * Retry info. Currently used to stop the wallet after any operation
+ * exceeds a number of retries.
+ */
+ retryInfo?: RetryInfo;
+}
+
+```
+```typescript
+export enum PendingTaskType {
+ ExchangeUpdate = "exchange-update",
+ ExchangeCheckRefresh = "exchange-check-refresh",
+ Purchase = "purchase",
+ Refresh = "refresh",
+ Recoup = "recoup",
+ TipPickup = "tip-pickup",
+ Withdraw = "withdraw",
+ Deposit = "deposit",
+ Backup = "backup",
+}
+
+```
+```typescript
+export interface RetryInfo {
+ firstTry: AbsoluteTime;
+ nextRetry: AbsoluteTime;
+ retryCounter: number;
+}
+
+```
+```typescript
+export interface RetryPolicy {
+ readonly backoffDelta: Duration;
+ readonly backoffBase: number;
+ readonly maxTimeout: Duration;
+}
+
+```
+```typescript
+// Declare "static" methods in Error
+interface ErrorConstructor {
+ /** Create .stack property on a target object */
+ captureStackTrace(targetObject: object, constructorOpt?: Function): void;
+ /**
+ * Optional override for formatting stack traces
+ *
+ * @see https://v8.dev/docs/stack-trace-api#customizing-stack-traces
+ */
+ prepareStackTrace?:
+ | ((err: Error, stackTraces: NodeJS.CallSite[]) => any)
+ | undefined;
+ stackTraceLimit: number;
+}
+
+```
+```typescript
+interface CallSite {
+ /**
+ * Value of "this"
+ */
+ getThis(): unknown;
+ /**
+ * Type of "this" as a string.
+ * This is the name of the function stored in the constructor field of
+ * "this", if available. Otherwise the object's [[Class]] internal
+ * property.
+ */
+ getTypeName(): string | null;
+ /**
+ * Current function
+ */
+ getFunction(): Function | undefined;
+ /**
+ * Name of the current function, typically its name property.
+ * If a name property is not available an attempt will be made to try
+ * to infer a name from the function's context.
+ */
+ getFunctionName(): string | null;
+ /**
+ * Name of the property [of "this" or one of its prototypes] that holds
+ * the current function
+ */
+ getMethodName(): string | null;
+ /**
+ * Name of the script [if this function was defined in a script]
+ */
+ getFileName(): string | null;
+ /**
+ * Current line number [if this function was defined in a script]
+ */
+ getLineNumber(): number | null;
+ /**
+ * Current column number [if this function was defined in a script]
+ */
+ getColumnNumber(): number | null;
+ /**
+ * A call site object representing the location where eval was called
+ * [if this function was created using a call to eval]
+ */
+ getEvalOrigin(): string | undefined;
+ /**
+ * Is this a toplevel invocation, that is, is "this" the global object?
+ */
+ isToplevel(): boolean;
+ /**
+ * Does this call take place in code defined by a call to eval?
+ */
+ isEval(): boolean;
+ /**
+ * Is this call in native V8 code?
+ */
+ isNative(): boolean;
+ /**
+ * Is this a constructor call?
+ */
+ isConstructor(): boolean;
+}
+
+```
+```typescript
+/**
+ * The wallet is currently updating information about an exchange.
+ */
+export interface PendingExchangeUpdateTask {
+ type: PendingTaskType.ExchangeUpdate;
+ exchangeBaseUrl: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+/**
+ * The wallet should check whether coins from this exchange
+ * need to be auto-refreshed.
+ */
+export interface PendingExchangeCheckRefreshTask {
+ type: PendingTaskType.ExchangeCheckRefresh;
+ exchangeBaseUrl: string;
+}
+
+```
+```typescript
+/**
+ * A purchase needs to be processed (i.e. for download / payment / refund).
+ */
+export interface PendingPurchaseTask {
+ type: PendingTaskType.Purchase;
+ proposalId: string;
+ retryInfo?: RetryInfo;
+ /**
+ * Status of the payment as string, used only for debugging.
+ */
+ statusStr: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+/**
+ * Status of an ongoing withdrawal operation.
+ */
+export interface PendingRefreshTask {
+ type: PendingTaskType.Refresh;
+ lastError?: TalerErrorDetail;
+ refreshGroupId: string;
+ finishedPerCoin: boolean[];
+ retryInfo?: RetryInfo;
+}
+
+```
+```typescript
+/**
+ * The wallet is picking up a tip that the user has accepted.
+ */
+export interface PendingTipPickupTask {
+ type: PendingTaskType.TipPickup;
+ tipId: string;
+ merchantBaseUrl: string;
+ merchantTipId: string;
+}
+
+```
+```typescript
+/**
+ * Status of an ongoing withdrawal operation.
+ */
+export interface PendingWithdrawTask {
+ type: PendingTaskType.Withdraw;
+ lastError: TalerErrorDetail | undefined;
+ retryInfo?: RetryInfo;
+ withdrawalGroupId: string;
+}
+
+```
+```typescript
+export interface PendingRecoupTask {
+ type: PendingTaskType.Recoup;
+ recoupGroupId: string;
+ retryInfo?: RetryInfo;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+```typescript
+/**
+ * Status of an ongoing deposit operation.
+ */
+export interface PendingDepositTask {
+ type: PendingTaskType.Deposit;
+ lastError: TalerErrorDetail | undefined;
+ retryInfo: RetryInfo | undefined;
+ depositGroupId: string;
+}
+
+```
+```typescript
+export interface PendingBackupTask {
+ type: PendingTaskType.Backup;
+ backupProviderBaseUrl: string;
+ lastError: TalerErrorDetail | undefined;
+}
+
+```
+
+### DumpCoinsOp
+```typescript
+/**
+ * Dump all coins of the wallet in a simple JSON format.
+ */
+export type DumpCoinsOp = {
+ op: WalletApiOperation.DumpCoins;
+ request: EmptyObject;
+ response: CoinDumpJson;
+};
+// DumpCoins = "dumpCoins"
+
+```
+```typescript
+/**
+ * Easy to process format for the public data of coins
+ * managed by the wallet.
+ */
+export interface CoinDumpJson {
+ coins: Array<{
+ /**
+ * The coin's denomination's public key.
+ */
+ denom_pub: DenominationPubKey;
+ /**
+ * Hash of denom_pub.
+ */
+ denom_pub_hash: string;
+ /**
+ * Value of the denomination (without any fees).
+ */
+ denom_value: string;
+ /**
+ * Public key of the coin.
+ */
+ coin_pub: string;
+ /**
+ * Base URL of the exchange for the coin.
+ */
+ exchange_base_url: string;
+ /**
+ * Public key of the parent coin.
+ * Only present if this coin was obtained via refreshing.
+ */
+ refresh_parent_coin_pub: string | undefined;
+ /**
+ * Public key of the reserve for this coin.
+ * Only present if this coin was obtained via refreshing.
+ */
+ withdrawal_reserve_pub: string | undefined;
+ coin_status: CoinStatus;
+ spend_allocation:
+ | {
+ id: string;
+ amount: string;
+ }
+ | undefined;
+ /**
+ * Information about the age restriction
+ */
+ ageCommitmentProof: AgeCommitmentProof | undefined;
+ }>;
+}
+
+```
+```typescript
+export declare type DenominationPubKey =
+ | RsaDenominationPubKey
+ | CsDenominationPubKey;
+
+```
+```typescript
+export interface RsaDenominationPubKey {
+ readonly cipher: DenomKeyType.Rsa;
+ readonly rsa_public_key: string;
+ readonly age_mask: number;
+}
+
+```
+```typescript
+export interface CsDenominationPubKey {
+ readonly cipher: DenomKeyType.ClauseSchnorr;
+ readonly age_mask: number;
+ readonly cs_public_key: string;
+}
+
+```
+```typescript
+/**
+ * Status of a coin.
+ */
+export declare enum CoinStatus {
+ /**
+ * Withdrawn and never shown to anybody.
+ */
+ Fresh = "fresh",
+ /**
+ * Fresh, but currently marked as "suspended", thus won't be used
+ * for spending. Used for testing.
+ */
+ FreshSuspended = "fresh-suspended",
+ /**
+ * A coin that has been spent and refreshed.
+ */
+ Dormant = "dormant",
+}
+
+```
+```typescript
+export interface AgeCommitmentProof {
+ commitment: AgeCommitment;
+ proof: AgeProof;
+}
+
+```
+```typescript
+export interface AgeCommitment {
+ mask: number;
+ /**
+ * Public keys, one for each age group specified in the age mask.
+ */
+ publicKeys: Edx25519PublicKeyEnc[];
+}
+
+```
+```typescript
+export declare type Edx25519PublicKeyEnc = FlavorP<
+ string,
+ "Edx25519PublicKeyEnc",
+ 32
+>;
+
+```
+```typescript
+export declare type FlavorP<T, FlavorT extends string, S extends number> = T & {
+ _flavor?: `taler.${FlavorT}`;
+ _size?: S;
+};
+
+```
+```typescript
+export interface AgeProof {
+ /**
+ * Private keys. Typically smaller than the number of public keys,
+ * because we drop private keys from age groups that are restricted.
+ */
+ privateKeys: Edx25519PrivateKeyEnc[];
+}
+
+```
+```typescript
+export declare type Edx25519PrivateKeyEnc = FlavorP<
+ string,
+ "Edx25519PrivateKeyEnc",
+ 64
+>;
+
+```
+
+### SetCoinSuspendedOp
+```typescript
+/**
+ * Set a coin as (un-)suspended.
+ * Suspended coins won't be used for payments.
+ */
+export type SetCoinSuspendedOp = {
+ op: WalletApiOperation.SetCoinSuspended;
+ request: SetCoinSuspendedRequest;
+ response: EmptyObject;
+};
+// SetCoinSuspended = "setCoinSuspended"
+
+```
+```typescript
+export interface SetCoinSuspendedRequest {
+ coinPub: string;
+ suspended: boolean;
+}
+
+```
+
+### ForceRefreshOp
+```typescript
+/**
+ * Force a refresh on coins where it would not
+ * be necessary.
+ */
+export type ForceRefreshOp = {
+ op: WalletApiOperation.ForceRefresh;
+ request: ForceRefreshRequest;
+ response: EmptyObject;
+};
+// ForceRefresh = "forceRefresh"
+
+```
+```typescript
+export interface ForceRefreshRequest {
+ coinPubList: string[];
+}
+
+```
+
+## Common Declarations
+```typescript
+export interface WalletCoreVersion {
+ hash: string | undefined;
+ version: string;
+ exchange: string;
+ merchant: string;
+ bank: string;
+ devMode: boolean;
+}
+```
+```typescript
+export declare type Transaction =
+ | TransactionWithdrawal
+ | TransactionPayment
+ | TransactionRefund
+ | TransactionTip
+ | TransactionRefresh
+ | TransactionDeposit
+ | TransactionPeerPullCredit
+ | TransactionPeerPullDebit
+ | TransactionPeerPushCredit
+ | TransactionPeerPushDebit;
+```
+```typescript
+export interface TransactionWithdrawal extends TransactionCommon {
+ type: TransactionType.Withdrawal;
+ /**
+ * Exchange of the withdrawal.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+ withdrawalDetails: WithdrawalDetails;
+}
+```
+```typescript
+export interface TransactionCommon {
+ transactionId: TransactionIdStr;
+ type: TransactionType;
+ timestamp: TalerProtocolTimestamp;
+ pending: boolean;
+ /**
+ * True if the transaction encountered a problem that might be
+ * permanent. A frozen transaction won't be automatically retried.
+ */
+ frozen: boolean;
+ /**
+ * Raw amount of the transaction (exclusive of fees or other extra costs).
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount added or removed from the wallet's balance (including all fees and other costs).
+ */
+ amountEffective: AmountString;
+ error?: TalerErrorDetail;
+}
+```
+```typescript
+export declare enum TransactionType {
+ Withdrawal = "withdrawal",
+ Payment = "payment",
+ Refund = "refund",
+ Refresh = "refresh",
+ Tip = "tip",
+ Deposit = "deposit",
+ PeerPushDebit = "peer-push-debit",
+ PeerPushCredit = "peer-push-credit",
+ PeerPullDebit = "peer-pull-debit",
+ PeerPullCredit = "peer-pull-credit",
+}
+```
+```typescript
+export interface TalerProtocolTimestamp {
+ readonly t_s: number | "never";
+}
+```
+```typescript
+export interface TalerErrorDetail {
+ code: TalerErrorCode;
+ hint?: string;
+ [x: string]: unknown;
+}
+```
+```typescript
+export declare type WithdrawalDetails =
+ | WithdrawalDetailsForManualTransfer
+ | WithdrawalDetailsForTalerBankIntegrationApi;
+```
+```typescript
+interface WithdrawalDetailsForManualTransfer {
+ type: WithdrawalType.ManualTransfer;
+ /**
+ * Payto URIs that the exchange supports.
+ *
+ * Already contains the amount and message.
+ */
+ exchangePaytoUris: string[];
+ reservePub: string;
+}
+```
+```typescript
+interface WithdrawalDetailsForTalerBankIntegrationApi {
+ type: WithdrawalType.TalerBankIntegrationApi;
+ /**
+ * Set to true if the bank has confirmed the withdrawal, false if not.
+ * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI.
+ * See also bankConfirmationUrl below.
+ */
+ confirmed: boolean;
+ /**
+ * If the withdrawal is unconfirmed, this can include a URL for user
+ * initiated confirmation.
+ */
+ bankConfirmationUrl?: string;
+ reservePub: string;
+}
+```
+```typescript
+export interface TransactionPayment extends TransactionCommon {
+ type: TransactionType.Payment;
+ /**
+ * Additional information about the payment.
+ */
+ info: OrderShortInfo;
+ /**
+ * Wallet-internal end-to-end identifier for the payment.
+ */
+ proposalId: string;
+ /**
+ * How far did the wallet get with processing the payment?
+ */
+ status: PaymentStatus;
+ /**
+ * Amount that must be paid for the contract
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that was paid, including deposit, wire and refresh fees.
+ */
+ amountEffective: AmountString;
+ /**
+ * Amount that has been refunded by the merchant
+ */
+ totalRefundRaw: AmountString;
+ /**
+ * Amount will be added to the wallet's balance after fees and refreshing
+ */
+ totalRefundEffective: AmountString;
+ /**
+ * Amount pending to be picked up
+ */
+ refundPending: AmountString | undefined;
+ /**
+ * Reference to applied refunds
+ */
+ refunds: RefundInfoShort[];
+}
+```
+```typescript
+export interface OrderShortInfo {
+ /**
+ * Order ID, uniquely identifies the order within a merchant instance
+ */
+ orderId: string;
+ /**
+ * Hash of the contract terms.
+ */
+ contractTermsHash: string;
+ /**
+ * More information about the merchant
+ */
+ merchant: MerchantInfo;
+ /**
+ * Summary of the order, given by the merchant
+ */
+ summary: string;
+ /**
+ * Map from IETF BCP 47 language tags to localized summaries
+ */
+ summary_i18n?: InternationalizedString;
+ /**
+ * List of products that are part of the order
+ */
+ products: Product[] | undefined;
+ /**
+ * Time indicating when the order should be delivered.
+ * May be overwritten by individual products.
+ */
+ delivery_date?: TalerProtocolTimestamp;
+ /**
+ * Delivery location for (all!) products.
+ */
+ delivery_location?: Location;
+ /**
+ * URL of the fulfillment, given by the merchant
+ */
+ fulfillmentUrl?: string;
+ /**
+ * Plain text message that should be shown to the user
+ * when the payment is complete.
+ */
+ fulfillmentMessage?: string;
+ /**
+ * Translations of fulfillmentMessage.
+ */
+ fulfillmentMessage_i18n?: InternationalizedString;
+}
+```
+```typescript
+export interface MerchantInfo {
+ name: string;
+ jurisdiction?: Location;
+ address?: Location;
+ logo?: string;
+ website?: string;
+ email?: string;
+}
+```
+```typescript
+export interface Location {
+ country?: string;
+ country_subdivision?: string;
+ district?: string;
+ town?: string;
+ town_location?: string;
+ post_code?: string;
+ street?: string;
+ building_name?: string;
+ building_number?: string;
+ address_lines?: string[];
+}
+```
+```typescript
+export interface InternationalizedString {
+ [lang_tag: string]: string;
+}
+```
+```typescript
+export interface Product {
+ product_id?: string;
+ description: string;
+ description_i18n?: {
+ [lang_tag: string]: string;
+ };
+ quantity?: number;
+ unit?: string;
+ price?: AmountString;
+ image?: string;
+ taxes?: Tax[];
+ delivery_date?: TalerProtocolTimestamp;
+}
+```
+```typescript
+export interface Tax {
+ name: string;
+ tax: AmountString;
+}
+```
+```typescript
+export declare enum PaymentStatus {
+ /**
+ * Explicitly aborted after timeout / failure
+ */
+ Aborted = "aborted",
+ /**
+ * Payment failed, wallet will auto-retry.
+ * User should be given the option to retry now / abort.
+ */
+ Failed = "failed",
+ /**
+ * Paid successfully
+ */
+ Paid = "paid",
+ /**
+ * User accepted, payment is processing.
+ */
+ Accepted = "accepted",
+}
+```
+```typescript
+export interface RefundInfoShort {
+ transactionId: string;
+ timestamp: TalerProtocolTimestamp;
+ amountEffective: AmountString;
+ amountRaw: AmountString;
+}
+```
+```typescript
+export interface TransactionRefund extends TransactionCommon {
+ type: TransactionType.Refund;
+ refundedTransactionId: string;
+ info: OrderShortInfo;
+ /**
+ * Amount pending to be picked up
+ */
+ refundPending: AmountString | undefined;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
+```
+```typescript
+export interface TransactionTip extends TransactionCommon {
+ type: TransactionType.Tip;
+ amountRaw: AmountString;
+ /**
+ * More information about the merchant
+ */
+ amountEffective: AmountString;
+ merchantBaseUrl: string;
+}
+```
+```typescript
+/**
+ * A transaction shown for refreshes.
+ * Only shown for (1) refreshes not associated with other transactions
+ * and (2) refreshes in an error state.
+ */
+export interface TransactionRefresh extends TransactionCommon {
+ type: TransactionType.Refresh;
+ /**
+ * Exchange that the coins are refreshed with
+ */
+ exchangeBaseUrl: string;
+ refreshReason: RefreshReason;
+ /**
+ * Transaction ID that caused this refresh.
+ */
+ originatingTransactionId?: string;
+ /**
+ * Always zero for refreshes
+ */
+ amountRaw: AmountString;
+ /**
+ * Fees, i.e. the effective, negative effect of the refresh
+ * on the balance.
+ */
+ amountEffective: AmountString;
+}
+```
+```typescript
+/**
+ * Reasons for why a coin is being refreshed.
+ */
+export declare enum RefreshReason {
+ Manual = "manual",
+ PayMerchant = "pay-merchant",
+ PayDeposit = "pay-deposit",
+ PayPeerPush = "pay-peer-push",
+ PayPeerPull = "pay-peer-pull",
+ Refund = "refund",
+ AbortPay = "abort-pay",
+ Recoup = "recoup",
+ BackupRestored = "backup-restored",
+ Scheduled = "scheduled",
+}
+```
+```typescript
+/**
+ * Deposit transaction, which effectively sends
+ * money from this wallet somewhere else.
+ */
+export interface TransactionDeposit extends TransactionCommon {
+ type: TransactionType.Deposit;
+ depositGroupId: string;
+ /**
+ * Target for the deposit.
+ */
+ targetPaytoUri: string;
+ /**
+ * Raw amount that is being deposited
+ */
+ amountRaw: AmountString;
+ /**
+ * Effective amount that is being deposited
+ */
+ amountEffective: AmountString;
+}
+```
+```typescript
+/**
+ * Credit because we were paid for a P2P invoice we created.
+ */
+export interface TransactionPeerPullCredit extends TransactionCommon {
+ type: TransactionType.PeerPullCredit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+ /**
+ * URI to send to the other party.
+ */
+ talerUri: string;
+}
+```
+```typescript
+export interface PeerInfoShort {
+ expiration: TalerProtocolTimestamp | undefined;
+ summary: string | undefined;
+}
+```
+```typescript
+/**
+ * Debit because we paid someone's invoice.
+ */
+export interface TransactionPeerPullDebit extends TransactionCommon {
+ type: TransactionType.PeerPullDebit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
+```
+```typescript
+/**
+ * We received money via a P2P payment.
+ */
+export interface TransactionPeerPushCredit extends TransactionCommon {
+ type: TransactionType.PeerPushCredit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+}
+```
+```typescript
+/**
+ * We sent money via a P2P payment.
+ */
+export interface TransactionPeerPushDebit extends TransactionCommon {
+ type: TransactionType.PeerPushDebit;
+ info: PeerInfoShort;
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+ /**
+ * URI to accept the payment.
+ */
+ talerUri: string;
+}
+```
+```typescript
+export interface ExchangeListItem {
+ exchangeBaseUrl: string;
+ currency: string | undefined;
+ paytoUris: string[];
+ tosStatus: ExchangeTosStatus;
+ exchangeStatus: ExchangeEntryStatus;
+ ageRestrictionOptions: number[];
+ /**
+ * Permanently added to the wallet, as opposed to just
+ * temporarily queried.
+ */
+ permanent: boolean;
+ /**
+ * Information about the last error that occured when trying
+ * to update the exchange info.
+ */
+ lastUpdateErrorInfo?: OperationErrorInfo;
+}
+```
+```typescript
+export declare enum ExchangeTosStatus {
+ New = "new",
+ Accepted = "accepted",
+ Changed = "changed",
+ NotFound = "not-found",
+ Unknown = "unknown",
+}
+```
+```typescript
+export declare enum ExchangeEntryStatus {
+ Unknown = "unknown",
+ Outdated = "outdated",
+ Ok = "ok",
+}
+```
+```typescript
+export interface OperationErrorInfo {
+ error: TalerErrorDetail;
+}
+```
+```typescript
+export interface ForcedDenomSel {
+ denoms: {
+ value: AmountString;
+ count: number;
+ }[];
+}
+```
+```typescript
+/**
+ * Contract terms from a merchant.
+ * FIXME: Add type field!
+ */
+export interface MerchantContractTerms {
+ /**
+ * Hash of the merchant's wire details.
+ */
+ h_wire: string;
+ /**
+ * Hash of the merchant's wire details.
+ */
+ auto_refund?: TalerProtocolDuration;
+ /**
+ * Wire method the merchant wants to use.
+ */
+ wire_method: string;
+ /**
+ * Human-readable short summary of the contract.
+ */
+ summary: string;
+ summary_i18n?: InternationalizedString;
+ /**
+ * Nonce used to ensure freshness.
+ */
+ nonce: string;
+ /**
+ * Total amount payable.
+ */
+ amount: string;
+ /**
+ * Auditors accepted by the merchant.
+ */
+ auditors: AuditorHandle[];
+ /**
+ * Deadline to pay for the contract.
+ */
+ pay_deadline: TalerProtocolTimestamp;
+ /**
+ * Maximum deposit fee covered by the merchant.
+ */
+ max_fee: string;
+ /**
+ * Information about the merchant.
+ */
+ merchant: MerchantInfo;
+ /**
+ * Public key of the merchant.
+ */
+ merchant_pub: string;
+ /**
+ * Time indicating when the order should be delivered.
+ * May be overwritten by individual products.
+ */
+ delivery_date?: TalerProtocolTimestamp;
+ /**
+ * Delivery location for (all!) products.
+ */
+ delivery_location?: Location;
+ /**
+ * List of accepted exchanges.
+ */
+ exchanges: ExchangeHandle[];
+ /**
+ * Products that are sold in this contract.
+ */
+ products?: Product[];
+ /**
+ * Deadline for refunds.
+ */
+ refund_deadline: TalerProtocolTimestamp;
+ /**
+ * Deadline for the wire transfer.
+ */
+ wire_transfer_deadline: TalerProtocolTimestamp;
+ /**
+ * Time when the contract was generated by the merchant.
+ */
+ timestamp: TalerProtocolTimestamp;
+ /**
+ * Order id to uniquely identify the purchase within
+ * one merchant instance.
+ */
+ order_id: string;
+ /**
+ * Base URL of the merchant's backend.
+ */
+ merchant_base_url: string;
+ /**
+ * Fulfillment URL to view the product or
+ * delivery status.
+ */
+ fulfillment_url?: string;
+ /**
+ * URL meant to share the shopping cart.
+ */
+ public_reorder_url?: string;
+ /**
+ * Plain text fulfillment message in the merchant's default language.
+ */
+ fulfillment_message?: string;
+ /**
+ * Internationalized fulfillment messages.
+ */
+ fulfillment_message_i18n?: InternationalizedString;
+ /**
+ * Share of the wire fee that must be settled with one payment.
+ */
+ wire_fee_amortization?: number;
+ /**
+ * Maximum wire fee that the merchant agrees to pay for.
+ */
+ max_wire_fee?: string;
+ minimum_age?: number;
+ /**
+ * Extra data, interpreted by the mechant only.
+ */
+ extra?: any;
+}
+```
+```typescript
+export interface TalerProtocolDuration {
+ readonly d_us: number | "forever";
+}
+```
+```typescript
+export interface AuditorHandle {
+ /**
+ * Official name of the auditor.
+ */
+ name: string;
+ /**
+ * Master public signing key of the auditor.
+ */
+ auditor_pub: string;
+ /**
+ * Base URL of the auditor.
+ */
+ url: string;
+}
+```
+```typescript
+/**
+ * Information about an exchange as stored inside a
+ * merchant's contract terms.
+ */
+export interface ExchangeHandle {
+ /**
+ * Master public signing key of the exchange.
+ */
+ master_pub: string;
+ /**
+ * Base URL of the exchange.
+ */
+ url: string;
+}
+```
+```typescript
+/**
+ * Forced coin selection for deposits/payments.
+ */
+export interface ForcedCoinSel {
+ coins: {
+ value: AmountString;
+ contribution: AmountString;
+ }[];
+}
+```
+```typescript
+export interface ApplyRefundResponse {
+ contractTermsHash: string;
+ transactionId: string;
+ proposalId: string;
+ amountEffectivePaid: AmountString;
+ amountRefundGranted: AmountString;
+ amountRefundGone: AmountString;
+ pendingAtExchange: boolean;
+ info: OrderShortInfo;
+}
+```
+```typescript
+export interface AddExchangeRequest {
+ exchangeBaseUrl: string;
+ forceUpdate?: boolean;
+}
+```
+```typescript
+export interface AbsoluteTime {
+ /**
+ * Timestamp in milliseconds.
+ */
+ readonly t_ms: number | "never";
+}
+```
+```typescript
+export interface Duration {
+ /**
+ * Duration in milliseconds.
+ */
+ readonly d_ms: number | "forever";
+}
+```
+```typescript
+export interface BackupRecovery {
+ walletRootPriv: string;
+ providers: {
+ name: string;
+ url: string;
+ }[];
+}
+```