ledgdex 1.0.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ledgdex-1.0.20/LICENSE +121 -0
- ledgdex-1.0.20/MANIFEST.in +8 -0
- ledgdex-1.0.20/PKG-INFO +254 -0
- ledgdex-1.0.20/README.md +233 -0
- ledgdex-1.0.20/guide/dex/ledgdex.jsonl +5 -0
- ledgdex-1.0.20/guide/examples/01-first-trade.sh +37 -0
- ledgdex-1.0.20/guide/examples/02-marketplace.sh +51 -0
- ledgdex-1.0.20/guide/examples/03-auction.sh +39 -0
- ledgdex-1.0.20/guide/examples/04-disputes.sh +39 -0
- ledgdex-1.0.20/guide/examples/05-keys.sh +35 -0
- ledgdex-1.0.20/guide/examples/06-publish.sh +34 -0
- ledgdex-1.0.20/guide/examples/07-check.sh +37 -0
- ledgdex-1.0.20/guide/examples/08-python-api.py +40 -0
- ledgdex-1.0.20/guide/examples/09-bots.py +80 -0
- ledgdex-1.0.20/guide/examples/10-robot-cleaning.py +78 -0
- ledgdex-1.0.20/guide/examples/11-extending.py +55 -0
- ledgdex-1.0.20/guide/examples/12-javascript.mjs +28 -0
- ledgdex-1.0.20/guide/examples/13-js-verify.mjs +42 -0
- ledgdex-1.0.20/guide/examples/14-js-web-page.html +43 -0
- ledgdex-1.0.20/guide/examples/15-js-sign.mjs +49 -0
- ledgdex-1.0.20/guide/tutorial.md +977 -0
- ledgdex-1.0.20/js/canon.js +77 -0
- ledgdex-1.0.20/js/core.js +364 -0
- ledgdex-1.0.20/js/dexweb-template.json +14 -0
- ledgdex-1.0.20/js/dexweb.js +91 -0
- ledgdex-1.0.20/js/ed25519.js +117 -0
- ledgdex-1.0.20/js/fuzz.mjs +27 -0
- ledgdex-1.0.20/js/keystore.js +64 -0
- ledgdex-1.0.20/js/render.js +200 -0
- ledgdex-1.0.20/js/sha.js +113 -0
- ledgdex-1.0.20/js/sig.js +51 -0
- ledgdex-1.0.20/js/state.js +206 -0
- ledgdex-1.0.20/js/test.mjs +178 -0
- ledgdex-1.0.20/js/viewer.js +346 -0
- ledgdex-1.0.20/js/zip.js +36 -0
- ledgdex-1.0.20/py/ledgdex/__init__.py +2 -0
- ledgdex-1.0.20/py/ledgdex/__main__.py +3 -0
- ledgdex-1.0.20/py/ledgdex/canon.py +99 -0
- ledgdex-1.0.20/py/ledgdex/check.py +352 -0
- ledgdex-1.0.20/py/ledgdex/cli.py +559 -0
- ledgdex-1.0.20/py/ledgdex/core.py +535 -0
- ledgdex-1.0.20/py/ledgdex/dex.py +292 -0
- ledgdex-1.0.20/py/ledgdex/ed25519.py +135 -0
- ledgdex-1.0.20/py/ledgdex/publish.py +79 -0
- ledgdex-1.0.20/py/ledgdex/render.py +352 -0
- ledgdex-1.0.20/py/ledgdex/shop.py +217 -0
- ledgdex-1.0.20/py/ledgdex/sig.py +88 -0
- ledgdex-1.0.20/py/ledgdex/state.py +278 -0
- ledgdex-1.0.20/py/ledgdex/styles.css +123 -0
- ledgdex-1.0.20/py/ledgdex.egg-info/PKG-INFO +254 -0
- ledgdex-1.0.20/py/ledgdex.egg-info/SOURCES.txt +229 -0
- ledgdex-1.0.20/py/ledgdex.egg-info/dependency_links.txt +1 -0
- ledgdex-1.0.20/py/ledgdex.egg-info/entry_points.txt +2 -0
- ledgdex-1.0.20/py/ledgdex.egg-info/requires.txt +4 -0
- ledgdex-1.0.20/py/ledgdex.egg-info/top_level.txt +1 -0
- ledgdex-1.0.20/py/tests/fuzz.py +424 -0
- ledgdex-1.0.20/py/tests/helpers.py +52 -0
- ledgdex-1.0.20/py/tests/make_vectors.py +502 -0
- ledgdex-1.0.20/py/tests/offline.mjs +19 -0
- ledgdex-1.0.20/py/tests/page.mjs +22 -0
- ledgdex-1.0.20/py/tests/test_canon.py +45 -0
- ledgdex-1.0.20/py/tests/test_check.py +313 -0
- ledgdex-1.0.20/py/tests/test_cli_v2.py +216 -0
- ledgdex-1.0.20/py/tests/test_dex.py +283 -0
- ledgdex-1.0.20/py/tests/test_ed25519.py +31 -0
- ledgdex-1.0.20/py/tests/test_fast.py +158 -0
- ledgdex-1.0.20/py/tests/test_guide.py +95 -0
- ledgdex-1.0.20/py/tests/test_ledger.py +159 -0
- ledgdex-1.0.20/py/tests/test_safety.py +81 -0
- ledgdex-1.0.20/py/tests/test_shop.py +170 -0
- ledgdex-1.0.20/py/tests/test_state.py +117 -0
- ledgdex-1.0.20/py/tests/test_v2.py +274 -0
- ledgdex-1.0.20/py/tests/test_vectors.py +15 -0
- ledgdex-1.0.20/py/tests/tutorial.py +119 -0
- ledgdex-1.0.20/pyproject.toml +39 -0
- ledgdex-1.0.20/run_tests.py +79 -0
- ledgdex-1.0.20/setup.cfg +4 -0
- ledgdex-1.0.20/vectors/canon.json +138 -0
- ledgdex-1.0.20/vectors/dex/auctions/backup/data.json +92 -0
- ledgdex-1.0.20/vectors/dex/auctions/config.json +15 -0
- ledgdex-1.0.20/vectors/dex/auctions/data.json +92 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/assets/favicon.ico +0 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/farmledger.html +17 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/index.html +15 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/ledgdex.jsonl +29 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/oldmap.html +17 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/oldmapd3772d43.html +17 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/oldmapee9215e9.html +17 -0
- ledgdex-1.0.20/vectors/dex/auctions/gen/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/auctions/ledgdex.jsonl +29 -0
- ledgdex-1.0.20/vectors/dex/auctions/run.py +2 -0
- ledgdex-1.0.20/vectors/dex/auctions/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/buyer/backup/data.json +27 -0
- ledgdex-1.0.20/vectors/dex/buyer/config.json +15 -0
- ledgdex-1.0.20/vectors/dex/buyer/data.json +27 -0
- ledgdex-1.0.20/vectors/dex/buyer/gen/ashaledger.html +17 -0
- ledgdex-1.0.20/vectors/dex/buyer/gen/ashapurchases.html +17 -0
- ledgdex-1.0.20/vectors/dex/buyer/gen/assets/favicon.ico +0 -0
- ledgdex-1.0.20/vectors/dex/buyer/gen/index.html +15 -0
- ledgdex-1.0.20/vectors/dex/buyer/gen/ledgdex.jsonl +4 -0
- ledgdex-1.0.20/vectors/dex/buyer/gen/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/buyer/ledgdex.jsonl +4 -0
- ledgdex-1.0.20/vectors/dex/buyer/run.py +2 -0
- ledgdex-1.0.20/vectors/dex/buyer/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/backup/data.json +57 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/config.json +15 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/data.json +57 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/assets/favicon.ico +0 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/index.html +15 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/ledgdex.jsonl +12 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/ltscriptgtxltscriptgtltimgsrcxonerrorygtx27quotampampltahrefquotjavascriptzquotgt.html +17 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/ltscriptgtxltscriptgtltimgsrcxonerrorygtx27quotampampltahrefquotjavascriptzquotgtadmissions.html +17 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/ltscriptgtxltscriptgtltimgsrcxonerrorygtx27quotampampltahrefquotjavascriptzquotgtledger.html +17 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/gen/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/ledgdex.jsonl +12 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/run.py +2 -0
- ledgdex-1.0.20/vectors/dex/hostile-text/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/index/backup/data.json +21 -0
- ledgdex-1.0.20/vectors/dex/index/config.json +15 -0
- ledgdex-1.0.20/vectors/dex/index/data.json +21 -0
- ledgdex-1.0.20/vectors/dex/index/gen/assets/favicon.ico +0 -0
- ledgdex-1.0.20/vectors/dex/index/gen/index.html +15 -0
- ledgdex-1.0.20/vectors/dex/index/gen/ledgdex.jsonl +6 -0
- ledgdex-1.0.20/vectors/dex/index/gen/marketledger.html +17 -0
- ledgdex-1.0.20/vectors/dex/index/gen/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/index/ledgdex.jsonl +6 -0
- ledgdex-1.0.20/vectors/dex/index/run.py +2 -0
- ledgdex-1.0.20/vectors/dex/index/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/market/backup/data.json +96 -0
- ledgdex-1.0.20/vectors/dex/market/config.json +15 -0
- ledgdex-1.0.20/vectors/dex/market/data.json +96 -0
- ledgdex-1.0.20/vectors/dex/market/gen/assets/favicon.ico +0 -0
- ledgdex-1.0.20/vectors/dex/market/gen/farmadmissions.html +17 -0
- ledgdex-1.0.20/vectors/dex/market/gen/farmledger.html +17 -0
- ledgdex-1.0.20/vectors/dex/market/gen/index.html +15 -0
- ledgdex-1.0.20/vectors/dex/market/gen/ledgdex.jsonl +25 -0
- ledgdex-1.0.20/vectors/dex/market/gen/mangoes.html +17 -0
- ledgdex-1.0.20/vectors/dex/market/gen/mangoes/340/244/206/340/244/256.html +17 -0
- ledgdex-1.0.20/vectors/dex/market/gen/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/market/ledgdex.jsonl +25 -0
- ledgdex-1.0.20/vectors/dex/market/run.py +2 -0
- ledgdex-1.0.20/vectors/dex/market/styles.css +123 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/assets/favicon.ico +0 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/farmadmissions.html +17 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/farmledger.html +17 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/index.html +15 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/ledgdex.jsonl +25 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/mangoes.html +17 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/mangoes/340/244/206/340/244/256.html +17 -0
- ledgdex-1.0.20/vectors/dex/market-variant/gen/styles.css +123 -0
- ledgdex-1.0.20/vectors/dexnames.json +42 -0
- ledgdex-1.0.20/vectors/ed25519.json +119 -0
- ledgdex-1.0.20/vectors/ledgers/admitted-backdated.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/admitted-backdated.jsonl +5 -0
- ledgdex-1.0.20/vectors/ledgers/admitted-future-stamp.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/admitted-future-stamp.jsonl +5 -0
- ledgdex-1.0.20/vectors/ledgers/admitted-then.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/admitted-then.jsonl +7 -0
- ledgdex-1.0.20/vectors/ledgers/auctions-later.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/auctions-revealing.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/auctions-revealing.jsonl +23 -0
- ledgdex-1.0.20/vectors/ledgers/auctions.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/auctions.jsonl +29 -0
- ledgdex-1.0.20/vectors/ledgers/broken-byte.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-byte.jsonl +25 -0
- ledgdex-1.0.20/vectors/ledgers/broken-device-copies-owner-message.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-device-copies-owner-message.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/broken-forged-receipt.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-forged-receipt.jsonl +3 -0
- ledgdex-1.0.20/vectors/ledgers/broken-fullwidth-time.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-fullwidth-time.jsonl +2 -0
- ledgdex-1.0.20/vectors/ledgers/broken-header-true.json +8 -0
- ledgdex-1.0.20/vectors/ledgers/broken-header-true.jsonl +1 -0
- ledgdex-1.0.20/vectors/ledgers/broken-header.json +8 -0
- ledgdex-1.0.20/vectors/ledgers/broken-header.jsonl +1 -0
- ledgdex-1.0.20/vectors/ledgers/broken-newline.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-newline.jsonl +25 -0
- ledgdex-1.0.20/vectors/ledgers/broken-noroot.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-noroot.jsonl +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-order.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-order.jsonl +25 -0
- ledgdex-1.0.20/vectors/ledgers/broken-receipt-replayed-device.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-receipt-replayed-device.jsonl +3 -0
- ledgdex-1.0.20/vectors/ledgers/broken-recovery-replay.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-recovery-replay.jsonl +10 -0
- ledgdex-1.0.20/vectors/ledgers/broken-seq-true.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-seq-true.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/broken-signer.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-signer.jsonl +25 -0
- ledgdex-1.0.20/vectors/ledgers/broken-small-order-key.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-small-order-key.jsonl +2 -0
- ledgdex-1.0.20/vectors/ledgers/broken-type-object.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-type-object.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/broken-version-true.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/broken-version-true.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/buyer.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/buyer.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/disputes.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/disputes.jsonl +14 -0
- ledgdex-1.0.20/vectors/ledgers/duplicate-offer.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/duplicate-offer.jsonl +6 -0
- ledgdex-1.0.20/vectors/ledgers/hostile-buyer.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/hostile-buyer.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/hostile-text.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/hostile-text.jsonl +12 -0
- ledgdex-1.0.20/vectors/ledgers/index.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/index.jsonl +6 -0
- ledgdex-1.0.20/vectors/ledgers/keys.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/keys.jsonl +9 -0
- ledgdex-1.0.20/vectors/ledgers/links.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/links.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/market.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/market.jsonl +25 -0
- ledgdex-1.0.20/vectors/ledgers/receipt-recovered.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/receipt-recovered.jsonl +3 -0
- ledgdex-1.0.20/vectors/ledgers/recovered-from-thief.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/recovered-from-thief.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/recovered.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/recovered.jsonl +9 -0
- ledgdex-1.0.20/vectors/ledgers/replay.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/replay.jsonl +8 -0
- ledgdex-1.0.20/vectors/ledgers/root-later.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/root-later.jsonl +5 -0
- ledgdex-1.0.20/vectors/ledgers/root-thief.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/root-thief.jsonl +3 -0
- ledgdex-1.0.20/vectors/ledgers/root.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/root.jsonl +4 -0
- ledgdex-1.0.20/vectors/ledgers/year-99.json +9 -0
- ledgdex-1.0.20/vectors/ledgers/year-99.jsonl +3 -0
- ledgdex-1.0.20/vectors/pages.json +1065 -0
- ledgdex-1.0.20/vectors/signatures.json +101 -0
ledgdex-1.0.20/LICENSE
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Creative Commons Legal Code
|
|
2
|
+
|
|
3
|
+
CC0 1.0 Universal
|
|
4
|
+
|
|
5
|
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
|
6
|
+
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
|
7
|
+
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
|
8
|
+
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
|
9
|
+
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
|
10
|
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
|
11
|
+
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
|
12
|
+
HEREUNDER.
|
|
13
|
+
|
|
14
|
+
Statement of Purpose
|
|
15
|
+
|
|
16
|
+
The laws of most jurisdictions throughout the world automatically confer
|
|
17
|
+
exclusive Copyright and Related Rights (defined below) upon the creator
|
|
18
|
+
and subsequent owner(s) (each and all, an "owner") of an original work of
|
|
19
|
+
authorship and/or a database (each, a "Work").
|
|
20
|
+
|
|
21
|
+
Certain owners wish to permanently relinquish those rights to a Work for
|
|
22
|
+
the purpose of contributing to a commons of creative, cultural and
|
|
23
|
+
scientific works ("Commons") that the public can reliably and without fear
|
|
24
|
+
of later claims of infringement build upon, modify, incorporate in other
|
|
25
|
+
works, reuse and redistribute as freely as possible in any form whatsoever
|
|
26
|
+
and for any purposes, including without limitation commercial purposes.
|
|
27
|
+
These owners may contribute to the Commons to promote the ideal of a free
|
|
28
|
+
culture and the further production of creative, cultural and scientific
|
|
29
|
+
works, or to gain reputation or greater distribution for their Work in
|
|
30
|
+
part through the use and efforts of others.
|
|
31
|
+
|
|
32
|
+
For these and/or other purposes and motivations, and without any
|
|
33
|
+
expectation of additional consideration or compensation, the person
|
|
34
|
+
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
|
35
|
+
is an owner of Copyright and Related Rights in the Work, voluntarily
|
|
36
|
+
elects to apply CC0 to the Work and publicly distribute the Work under its
|
|
37
|
+
terms, with knowledge of his or her Copyright and Related Rights in the
|
|
38
|
+
Work and the meaning and intended legal effect of CC0 on those rights.
|
|
39
|
+
|
|
40
|
+
1. Copyright and Related Rights. A Work made available under CC0 may be
|
|
41
|
+
protected by copyright and related or neighboring rights ("Copyright and
|
|
42
|
+
Related Rights"). Copyright and Related Rights include, but are not
|
|
43
|
+
limited to, the following:
|
|
44
|
+
|
|
45
|
+
i. the right to reproduce, adapt, distribute, perform, display,
|
|
46
|
+
communicate, and translate a Work;
|
|
47
|
+
ii. moral rights retained by the original author(s) and/or performer(s);
|
|
48
|
+
iii. publicity and privacy rights pertaining to a person's image or
|
|
49
|
+
likeness depicted in a Work;
|
|
50
|
+
iv. rights protecting against unfair competition in regards to a Work,
|
|
51
|
+
subject to the limitations in paragraph 4(a), below;
|
|
52
|
+
v. rights protecting the extraction, dissemination, use and reuse of data
|
|
53
|
+
in a Work;
|
|
54
|
+
vi. database rights (such as those arising under Directive 96/9/EC of the
|
|
55
|
+
European Parliament and of the Council of 11 March 1996 on the legal
|
|
56
|
+
protection of databases, and under any national implementation
|
|
57
|
+
thereof, including any amended or successor version of such
|
|
58
|
+
directive); and
|
|
59
|
+
vii. other similar, equivalent or corresponding rights throughout the
|
|
60
|
+
world based on applicable law or treaty, and any national
|
|
61
|
+
implementations thereof.
|
|
62
|
+
|
|
63
|
+
2. Waiver. To the greatest extent permitted by, but not in contravention
|
|
64
|
+
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
|
65
|
+
irrevocably and unconditionally waives, abandons, and surrenders all of
|
|
66
|
+
Affirmer's Copyright and Related Rights and associated claims and causes
|
|
67
|
+
of action, whether now known or unknown (including existing as well as
|
|
68
|
+
future claims and causes of action), in the Work (i) in all territories
|
|
69
|
+
worldwide, (ii) for the maximum duration provided by applicable law or
|
|
70
|
+
treaty (including future time extensions), (iii) in any current or future
|
|
71
|
+
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
|
72
|
+
including without limitation commercial, advertising or promotional
|
|
73
|
+
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
|
74
|
+
member of the public at large and to the detriment of Affirmer's heirs and
|
|
75
|
+
successors, fully intending that such Waiver shall not be subject to
|
|
76
|
+
revocation, rescission, cancellation, termination, or any other legal or
|
|
77
|
+
equitable action to disrupt the quiet enjoyment of the Work by the public
|
|
78
|
+
as contemplated by Affirmer's express Statement of Purpose.
|
|
79
|
+
|
|
80
|
+
3. Public License Fallback. Should any part of the Waiver for any reason
|
|
81
|
+
be judged legally invalid or ineffective under applicable law, then the
|
|
82
|
+
Waiver shall be preserved to the maximum extent permitted taking into
|
|
83
|
+
account Affirmer's express Statement of Purpose. In addition, to the
|
|
84
|
+
extent the Waiver is so judged Affirmer hereby grants to each affected
|
|
85
|
+
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
|
86
|
+
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
|
87
|
+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
|
88
|
+
maximum duration provided by applicable law or treaty (including future
|
|
89
|
+
time extensions), (iii) in any current or future medium and for any number
|
|
90
|
+
of copies, and (iv) for any purpose whatsoever, including without
|
|
91
|
+
limitation commercial, advertising or promotional purposes (the
|
|
92
|
+
"License"). The License shall be deemed effective as of the date CC0 was
|
|
93
|
+
applied by Affirmer to the Work. Should any part of the License for any
|
|
94
|
+
reason be judged legally invalid or ineffective under applicable law, such
|
|
95
|
+
partial invalidity or ineffectiveness shall not invalidate the remainder
|
|
96
|
+
of the License, and in such case Affirmer hereby affirms that he or she
|
|
97
|
+
will not (i) exercise any of his or her remaining Copyright and Related
|
|
98
|
+
Rights in the Work or (ii) assert any associated claims and causes of
|
|
99
|
+
action with respect to the Work, in either case contrary to Affirmer's
|
|
100
|
+
express Statement of Purpose.
|
|
101
|
+
|
|
102
|
+
4. Limitations and Disclaimers.
|
|
103
|
+
|
|
104
|
+
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
|
105
|
+
surrendered, licensed or otherwise affected by this document.
|
|
106
|
+
b. Affirmer offers the Work as-is and makes no representations or
|
|
107
|
+
warranties of any kind concerning the Work, express, implied,
|
|
108
|
+
statutory or otherwise, including without limitation warranties of
|
|
109
|
+
title, merchantability, fitness for a particular purpose, non
|
|
110
|
+
infringement, or the absence of latent or other defects, accuracy, or
|
|
111
|
+
the present or absence of errors, whether or not discoverable, all to
|
|
112
|
+
the greatest extent permissible under applicable law.
|
|
113
|
+
c. Affirmer disclaims responsibility for clearing rights of other persons
|
|
114
|
+
that may apply to the Work or any use thereof, including without
|
|
115
|
+
limitation any person's Copyright and Related Rights in the Work.
|
|
116
|
+
Further, Affirmer disclaims responsibility for obtaining any necessary
|
|
117
|
+
consents, permissions or other rights required for any use of the
|
|
118
|
+
Work.
|
|
119
|
+
d. Affirmer understands and acknowledges that Creative Commons is not a
|
|
120
|
+
party to this document and has no duty or obligation with respect to
|
|
121
|
+
this CC0 or use of the Work.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# The source package (sdist) holds only what "python run_tests.py" needs. The wheel holds only py/ledgdex.
|
|
2
|
+
include README.md LICENSE run_tests.py
|
|
3
|
+
recursive-include py/tests *.py *.mjs
|
|
4
|
+
recursive-include vectors *
|
|
5
|
+
recursive-include js *.js *.mjs *.json
|
|
6
|
+
recursive-include guide/examples *
|
|
7
|
+
include guide/tutorial.md guide/dex/ledgdex.jsonl
|
|
8
|
+
global-exclude __pycache__ *.pyc
|
ledgdex-1.0.20/PKG-INFO
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ledgdex
|
|
3
|
+
Version: 1.0.20
|
|
4
|
+
Summary: Simple market on a dex: signed, append-only ledgers with triple-entry accounting
|
|
5
|
+
Author: Noorul Ali
|
|
6
|
+
License: CC0-1.0
|
|
7
|
+
Project-URL: Homepage, https://ledgdex.github.io
|
|
8
|
+
Project-URL: Documentation, https://ledgdex.github.io/docs/
|
|
9
|
+
Project-URL: Source, https://github.com/ledgdex/ledgdex
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: dexweb<5,>=4.2.5
|
|
18
|
+
Provides-Extra: fast
|
|
19
|
+
Requires-Dist: cryptography>=42; extra == "fast"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Ledgdex
|
|
23
|
+
|
|
24
|
+
A simple market on a dex. Every seller and buyer keeps a signed, append-only ledger (`ledgdex.jsonl`) in their own dex, a freely shared website built with [dexweb](https://github.com/matrixdex/dexweb). A trade exists three times: the buyer's signed claim, the seller's signed record of it, and the buyer's copy of that record. No server, no blockchain: SHA-256, Ed25519 and plain files.
|
|
25
|
+
|
|
26
|
+
**Site: https://ledgdex.github.io** (viewer and live markets). **Docs: https://ledgdex.github.io/docs/** (getting started, setting up a marketplace, extending ledgdex, bots, the command line and API references, and tested examples). The docs are a dex built with dexweb, and a ledgdex too: dex is the core; dex + ledger is a ledgdex. Their source is [guide/](guide/).
|
|
27
|
+
|
|
28
|
+
The specification is [architecture/SPEC.md](architecture/SPEC.md). Everything in it is built: the market (offers, claims, payment, delivery, receipts), disputes, sealed-bid auctions, device keys, key rotation and recovery, indexes, the root, dex pages, publishing and tamper checks, in Python, plus a JavaScript version and a browser viewer.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
pip install "ledgdex[fast]"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
From the source on GitHub: `pip install "ledgdex[fast] @ git+https://github.com/ledgdex/ledgdex@v1.0.20"`. From a clone,
|
|
37
|
+
to work on ledgdex: `pip install -e ".[fast]"`. Releases before 1.0.20 are on GitHub only, as tags; add
|
|
38
|
+
`#subdirectory=py` to their address.
|
|
39
|
+
|
|
40
|
+
Python 3.8 or later. The only dependency is dexweb (4.2.5 or later, before 5). Private keys live in `~/.ledgdex` (or `$LEDGDEX_HOME`), never in a dex, with mode 600: ledgdex refuses a key file others can read.
|
|
41
|
+
|
|
42
|
+
The `fast` extra adds the `cryptography` package: signing and verifying get about 20 to 25 times faster. Without it ledgdex uses its own pure-Python Ed25519, with the same results. Verified ledgers are also cached (in `~/.cache/ledgdex`, or `$LEDGDEX_CACHE`) by the exact bytes already verified, so a ledger that grew is only verified from where it was verified before; any changed byte is verified in full. `ledgdex verify --full` and `LEDGDEX_NO_CACHE=1` skip the cache, and `LEDGDEX_PURE=1` forces the pure-Python Ed25519.
|
|
43
|
+
|
|
44
|
+
## Sell
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
ledgdex init shop --name "Mango Farm" --about "Alphonso mangoes from Ratnagiri" --key farm \
|
|
48
|
+
--url https://farm.github.io --dest git@github.com:farm/farm.github.io.git --branch main
|
|
49
|
+
ledgdex offer shop offer.json
|
|
50
|
+
ledgdex publish shop
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`offer.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{"item": {"title": "Alphonso mangoes", "text": "One dozen, ripe."},
|
|
57
|
+
"quantity": 10, "unit": "dozen", "currency": "INR", "price": 120000,
|
|
58
|
+
"pay": [{"method": "upi", "to": "farm@bank"}]}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Prices are integers in the currency's smallest unit (120000 is INR 1,200.00). Left out: `allow` is `"any"`, `terms` is empty, and the arbiter is the seller.
|
|
62
|
+
|
|
63
|
+
## A whole shop from a catalog
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
ledgdex shop catalog.json --repo OWNER/NAME
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
One command makes the shop from a JSON catalog: the ledgdex (if it is missing), one offer for each product, and the
|
|
70
|
+
publish with dexweb to GitHub. Run it again after the catalog changes: unchanged products keep their offers, new and
|
|
71
|
+
changed products get new offers, and the offers of changed and removed products are withdrawn. The same catalog again
|
|
72
|
+
signs nothing. In Python: `from ledgdex.shop import shop; shop("catalog.json", repo="OWNER/NAME")`.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{"shop": {"name": "Penguin Shop", "currency": "USD", "pay": [{"method": "card", "to": "https://pay.example/penguin"}]},
|
|
76
|
+
"products": [{"title": "Ice axe", "price": 4999, "quantity": 20, "text": "Steel, 70 cm"}]}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`shop.name`, `shop.currency` and `shop.pay` (one or more methods) are necessary; each product needs `title`, `price`
|
|
80
|
+
and `quantity`. Unknown fields are refused, and a wrong product stops the run before anything is signed. The full
|
|
81
|
+
format is in spec 7.5. GitHub Pages must be on for the repository (`OWNER.github.io` always has it).
|
|
82
|
+
|
|
83
|
+
## Buy
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
ledgdex init me --name "Asha" --key asha --url https://asha.github.io --dest git@github.com:asha/asha.github.io.git
|
|
87
|
+
ledgdex claim me https://farm.github.io OFFER_ID --quantity 2
|
|
88
|
+
ledgdex publish me
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The claim is kept in your ledger as `sent`. Publishing your dex is sending it: the seller collects it with `ledgdex record shop --from https://asha.github.io`, or you send the claim file any other way and the seller runs `ledgdex record shop claim-....json`.
|
|
92
|
+
|
|
93
|
+
## The rest of a trade
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
ledgdex receipt me https://farm.github.io buyer keeps the seller's record: the receipt and the claim id
|
|
97
|
+
ledgdex pay me https://farm.github.io CLAIM_ID --method upi --ref UTR123
|
|
98
|
+
ledgdex record shop --from https://asha.github.io seller records the payment notice
|
|
99
|
+
ledgdex received shop CLAIM_ID 240000
|
|
100
|
+
ledgdex delivered shop CLAIM_ID --note "by courier"
|
|
101
|
+
ledgdex confirm me https://farm.github.io CLAIM_ID
|
|
102
|
+
ledgdex record shop --from https://asha.github.io the claim is now closed
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Publish after each step that others need to see. Anyone can check any ledger:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
ledgdex verify https://farm.github.io
|
|
109
|
+
ledgdex state https://farm.github.io
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Disputes
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
ledgdex dispute me CLAIM_ID --seller https://farm.github.io --text "never arrived" buyer
|
|
116
|
+
ledgdex record shop --from https://asha.github.io
|
|
117
|
+
ledgdex ruling judge https://farm.github.io DISPUTE_ID --outcome refund the offer's arbiter
|
|
118
|
+
ledgdex record shop --from https://judge.github.io
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Sealed-bid auctions
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
ledgdex auction shop auction.json {"item": {"title": "Old map"}, "currency": "INR", "close": "...", "reveal_until": "...", "reserve": 10000}
|
|
125
|
+
ledgdex bid me https://farm.github.io AUCTION_ID --amount 25000 only a commitment is sent; the amount stays in ~/.ledgdex/bids
|
|
126
|
+
ledgdex reveal me https://farm.github.io AUCTION_ID after the close, before reveal_until
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The seller records bids and reveals with `ledgdex record`. The best revealed bid that meets the reserve wins; payment and delivery then use the auction id in place of a claim id.
|
|
130
|
+
|
|
131
|
+
## Keys
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
ledgdex keygen phone on the phone: prints its public key
|
|
135
|
+
ledgdex device add shop ed25519:... --name phone with the owner key: the phone may now sign
|
|
136
|
+
ledgdex device revoke shop ed25519:... --reason lost
|
|
137
|
+
ledgdex rotate shop --key farm2 replace the owner key
|
|
138
|
+
ledgdex recover root shop ed25519:NEW the root names a new key for a lost one
|
|
139
|
+
ledgdex recovered shop --root ROOT --key farm-new the owner takes the ledger back
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
On a machine with a device key, everyday commands sign with it. Messages to other ledgers (claims, payments, bids) are signed with the owner key, which is a self's identity in other ledgers.
|
|
143
|
+
|
|
144
|
+
## Indexes and the root
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
ledgdex list market https://farm.github.io --note "mangoes" an index lists ledgers
|
|
148
|
+
ledgdex discover https://market.github.io --offers what the listed ledgers sell
|
|
149
|
+
ledgdex admit root ed25519:... the root admits selves to The Matrix
|
|
150
|
+
ledgdex state shop --root https://root.github.io "allow": "admitted" then needs the root too
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Running the root
|
|
154
|
+
|
|
155
|
+
The root is the ledger whose admissions define who is in The Matrix, and its owner is the default arbiter. Today
|
|
156
|
+
Noorul Ali, founder of The Matrix, runs it. It is an ordinary ledgdex:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
ledgdex init root --name "The Matrix root" --about "Admission to The Matrix" --key matrix-root \
|
|
160
|
+
--url https://ROOT_ADDRESS --dest git@github.com:YOU/root.git --branch main
|
|
161
|
+
ledgdex publish root
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Keep the owner key (`~/.ledgdex/matrix-root.key`) backed up offline. For daily work, give the laptop a device key,
|
|
165
|
+
which may admit and revoke but not change keys. Recovery hands a ledger to a new key, so `ledgdex recover` needs the
|
|
166
|
+
owner key itself:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
ledgdex keygen root-laptop prints ed25519:...
|
|
170
|
+
ledgdex device add root ed25519:... --name laptop with the owner key, once
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then, with `ledgdex publish root` after each change:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
ledgdex admit root ed25519:THEIR_KEY --name "Shop 12" --note "ground floor" admit a self
|
|
177
|
+
ledgdex revoke root ed25519:THEIR_KEY --reason "left the mall" take it back
|
|
178
|
+
ledgdex recover root https://THEIR_DEX ed25519:THEIR_NEW_KEY with the owner key, after checking who they are
|
|
179
|
+
ledgdex list root https://THEIR_DEX --note "Shop 12" optional: the root as an index too
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Every dex that trades in The Matrix names the root in its `config.json`, as `"ledgdex": {"root": "https://ROOT_ADDRESS"}`.
|
|
183
|
+
The first time ledgdex reads it, it adds `"root_id"`, the root's ledger id, and from then on refuses any other ledger
|
|
184
|
+
at that address (or a broken one): whoever controls the address cannot swap in a root of their own. Set `root_id`
|
|
185
|
+
yourself, from a source you trust, to pin it before the first read. The dex also keeps the last root it read, in `.ledgdex-root.jsonl`: when
|
|
186
|
+
the root's address is down, commands work on with that copy, and an address serving an older root is ignored.
|
|
187
|
+
Its "admitted" offers then need admission by the root too, and its offers and auctions default to the root's owner as
|
|
188
|
+
arbiter. Rulings are signed with the owner key, since that is the arbiter key offers name:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
ledgdex ruling root https://SELLER_DEX DISPUTE_ID --outcome refund --text "..."
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Run `ledgdex check` on the root (the GitHub workflow, or cron) so any change to its history is caught.
|
|
195
|
+
|
|
196
|
+
## JavaScript and the viewer
|
|
197
|
+
|
|
198
|
+
`js/` holds the same core in plain browser JavaScript (no npm): canonical JSON, SHA-256/512, Ed25519, verification, the state function, the dex pages (`render.js`) and the dexweb build (`dexweb.js`). `node js/test.mjs` checks it reproduces the shared vectors in `vectors/` byte for byte: states, pages, and whole dexs that dexweb built. `python py/tests/make_vectors.py` regenerates them.
|
|
199
|
+
|
|
200
|
+
The site https://ledgdex.github.io comes from three dexes. Dexweb publishes them to [ledgdex/ledgdex.github.io](https://github.com/ledgdex/ledgdex.github.io), and GitHub Pages serves that repository.
|
|
201
|
+
|
|
202
|
+
| Source | Published to | Address | Publish with |
|
|
203
|
+
|---|---|---|---|
|
|
204
|
+
| `home/` (home page, Live Markets, About) | the root | https://ledgdex.github.io | `cd home && python run.py` |
|
|
205
|
+
| `viewer/` (the viewer and the JavaScript modules) | `viewer/` | https://ledgdex.github.io/viewer/index.html | `cd viewer && python run.py` |
|
|
206
|
+
| `guide/dex/` (the docs) | `docs/` | https://ledgdex.github.io/docs/ | `cd guide/dex && ledgdex publish .` |
|
|
207
|
+
|
|
208
|
+
After you change `viewer/` or `js/`, publish the viewer again. `python run.py --build-only` builds `gen/` and does not publish. The home pages run no scripts. Only the viewer runs scripts.
|
|
209
|
+
|
|
210
|
+
The viewer page loads any ledger by address or file, verifies it in the browser, and shows it as the same pages its dex has. It signs claims, payments, confirmations, disputes, bids and reveals with a key kept in the browser, using the browser's own constant-time Web Crypto Ed25519 when it has one, else ledgdex's built-in code. The key is stored only sealed with your passphrase ("Keep, sealed"), and "Download key backup" gives you a copy to keep offline. The viewer also keeps your own ledger: every message you sign is kept as `sent`, and "Collect receipts" keeps the seller's records of them. "Download your dex" gives you your ledger as a complete dex (a zip), file for file what `ledgdex init` and dexweb make; publish it like any dex. So a buyer with only a browser has a ledgdex too. Open a ledger directly with `https://ledgdex.github.io/viewer/index.html?ledger=https://farm.github.io`.
|
|
211
|
+
|
|
212
|
+
## Checking for tampering
|
|
213
|
+
|
|
214
|
+
`ledgdex verify` checks one ledger once. `ledgdex check` checks ledgers over time:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
ledgdex check shop https://farm.github.io once; exit code 1 on tampering
|
|
218
|
+
ledgdex check shop --every 3600 as a worker, every hour
|
|
219
|
+
17 6 * * * cd /srv/ledgdex && ledgdex check shop --json report.json from cron
|
|
220
|
+
ledgdex workflow seller/ledgdex.jsonl > .github/workflows/ledgdex-check.yml as a GitHub workflow
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
It keeps the last copy of every ledger it has seen (in `.ledgdex-check/`) and reports an error, with signed proof in `.ledgdex-check/proofs/`, when a ledger stops extending that copy (two entries at one seq), when a receipt no longer matches the ledger it came from, or when an address starts serving a different ledger. Receipts in the ledgers you check are followed to the sellers' ledgers automatically. `--media` also checks offer pictures against their hashes; `--published` compares a dex folder with its published ledger. A copy that is only behind (a cache or an old snapshot) is a warning, not an error. The GitHub workflow runs on every push, daily and by hand, keeps its last-seen copies in the Actions cache, and attaches the report and proofs to each run.
|
|
224
|
+
|
|
225
|
+
## Every ledgdex is a dex
|
|
226
|
+
|
|
227
|
+
Each command rewrites the ledgdex pages in `data.json` (pages whose first paragraph starts with `<!-- ledgdex -->`) and rebuilds the site with dexweb. Your own pages are never touched. `ledgdex publish` publishes with dexweb; `config.json` has `"append_only": ["ledgdex.jsonl"]`, so dexweb refuses to publish a ledger that does not extend the published one. Publishing from two devices is safe: the second one catches up and re-sequences its unpublished entries.
|
|
228
|
+
|
|
229
|
+
## Production (v1.0)
|
|
230
|
+
|
|
231
|
+
Before you trade for real, read [SECURITY.md](SECURITY.md). In short:
|
|
232
|
+
|
|
233
|
+
- Install the `fast` extra (`pip install "ledgdex[fast]"`) on every machine that signs: it signs in constant time. ledgdex warns when it signs without it.
|
|
234
|
+
- Keep owner keys offline (backed up), and use device keys day to day. Revoke a lost device at once.
|
|
235
|
+
- Run `ledgdex check` on your ledger and the ledgers you trade with, daily (the workflow, cron or a worker). An error there is tampering, with proof.
|
|
236
|
+
- In the browser, keep your key sealed with a long passphrase and download a backup. Use the viewer at https://ledgdex.github.io/viewer/index.html, or host it on an address of its own: every page on one origin can read what pages there store.
|
|
237
|
+
- Generated workflows install ledgdex from the release tag and pin each action to a commit.
|
|
238
|
+
|
|
239
|
+
## Tests
|
|
240
|
+
|
|
241
|
+
Two suites. `python run_tests.py` is the essential one (about 30 s): every Python test except the docs examples and
|
|
242
|
+
the tutorial, with the fast backend, and the JavaScript core against the shared vectors; run it after small changes.
|
|
243
|
+
`python run_tests.py --full` is the full one (about 10 min): every Python test on both Ed25519 backends, the
|
|
244
|
+
JavaScript vectors, and a round of differential fuzzing; run it after larger changes and before a release. CI runs
|
|
245
|
+
the essential suite on every push (on Linux), and the full suite (on Linux, macOS and Windows) for release tags, by
|
|
246
|
+
hand, weekly, and for a push whose commit message contains `[full tests]`. Both work in a clone and in
|
|
247
|
+
the source package on PyPI, which holds the tests and their data; the wheel holds only the code. Steps:
|
|
248
|
+
https://ledgdex.github.io/docs/testlocalledgdexinstallation.html. It needs git and node 20 or later. The parts one by one:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
cd py && python -m unittest discover -s tests
|
|
252
|
+
node js/test.mjs
|
|
253
|
+
cd py && python tests/fuzz.py --seed 7 --ledgers 1000 Python and JavaScript must agree on random and corrupted ledgers
|
|
254
|
+
```
|
ledgdex-1.0.20/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Ledgdex
|
|
2
|
+
|
|
3
|
+
A simple market on a dex. Every seller and buyer keeps a signed, append-only ledger (`ledgdex.jsonl`) in their own dex, a freely shared website built with [dexweb](https://github.com/matrixdex/dexweb). A trade exists three times: the buyer's signed claim, the seller's signed record of it, and the buyer's copy of that record. No server, no blockchain: SHA-256, Ed25519 and plain files.
|
|
4
|
+
|
|
5
|
+
**Site: https://ledgdex.github.io** (viewer and live markets). **Docs: https://ledgdex.github.io/docs/** (getting started, setting up a marketplace, extending ledgdex, bots, the command line and API references, and tested examples). The docs are a dex built with dexweb, and a ledgdex too: dex is the core; dex + ledger is a ledgdex. Their source is [guide/](guide/).
|
|
6
|
+
|
|
7
|
+
The specification is [architecture/SPEC.md](architecture/SPEC.md). Everything in it is built: the market (offers, claims, payment, delivery, receipts), disputes, sealed-bid auctions, device keys, key rotation and recovery, indexes, the root, dex pages, publishing and tamper checks, in Python, plus a JavaScript version and a browser viewer.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install "ledgdex[fast]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
From the source on GitHub: `pip install "ledgdex[fast] @ git+https://github.com/ledgdex/ledgdex@v1.0.20"`. From a clone,
|
|
16
|
+
to work on ledgdex: `pip install -e ".[fast]"`. Releases before 1.0.20 are on GitHub only, as tags; add
|
|
17
|
+
`#subdirectory=py` to their address.
|
|
18
|
+
|
|
19
|
+
Python 3.8 or later. The only dependency is dexweb (4.2.5 or later, before 5). Private keys live in `~/.ledgdex` (or `$LEDGDEX_HOME`), never in a dex, with mode 600: ledgdex refuses a key file others can read.
|
|
20
|
+
|
|
21
|
+
The `fast` extra adds the `cryptography` package: signing and verifying get about 20 to 25 times faster. Without it ledgdex uses its own pure-Python Ed25519, with the same results. Verified ledgers are also cached (in `~/.cache/ledgdex`, or `$LEDGDEX_CACHE`) by the exact bytes already verified, so a ledger that grew is only verified from where it was verified before; any changed byte is verified in full. `ledgdex verify --full` and `LEDGDEX_NO_CACHE=1` skip the cache, and `LEDGDEX_PURE=1` forces the pure-Python Ed25519.
|
|
22
|
+
|
|
23
|
+
## Sell
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
ledgdex init shop --name "Mango Farm" --about "Alphonso mangoes from Ratnagiri" --key farm \
|
|
27
|
+
--url https://farm.github.io --dest git@github.com:farm/farm.github.io.git --branch main
|
|
28
|
+
ledgdex offer shop offer.json
|
|
29
|
+
ledgdex publish shop
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`offer.json`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{"item": {"title": "Alphonso mangoes", "text": "One dozen, ripe."},
|
|
36
|
+
"quantity": 10, "unit": "dozen", "currency": "INR", "price": 120000,
|
|
37
|
+
"pay": [{"method": "upi", "to": "farm@bank"}]}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Prices are integers in the currency's smallest unit (120000 is INR 1,200.00). Left out: `allow` is `"any"`, `terms` is empty, and the arbiter is the seller.
|
|
41
|
+
|
|
42
|
+
## A whole shop from a catalog
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
ledgdex shop catalog.json --repo OWNER/NAME
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
One command makes the shop from a JSON catalog: the ledgdex (if it is missing), one offer for each product, and the
|
|
49
|
+
publish with dexweb to GitHub. Run it again after the catalog changes: unchanged products keep their offers, new and
|
|
50
|
+
changed products get new offers, and the offers of changed and removed products are withdrawn. The same catalog again
|
|
51
|
+
signs nothing. In Python: `from ledgdex.shop import shop; shop("catalog.json", repo="OWNER/NAME")`.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{"shop": {"name": "Penguin Shop", "currency": "USD", "pay": [{"method": "card", "to": "https://pay.example/penguin"}]},
|
|
55
|
+
"products": [{"title": "Ice axe", "price": 4999, "quantity": 20, "text": "Steel, 70 cm"}]}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`shop.name`, `shop.currency` and `shop.pay` (one or more methods) are necessary; each product needs `title`, `price`
|
|
59
|
+
and `quantity`. Unknown fields are refused, and a wrong product stops the run before anything is signed. The full
|
|
60
|
+
format is in spec 7.5. GitHub Pages must be on for the repository (`OWNER.github.io` always has it).
|
|
61
|
+
|
|
62
|
+
## Buy
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
ledgdex init me --name "Asha" --key asha --url https://asha.github.io --dest git@github.com:asha/asha.github.io.git
|
|
66
|
+
ledgdex claim me https://farm.github.io OFFER_ID --quantity 2
|
|
67
|
+
ledgdex publish me
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The claim is kept in your ledger as `sent`. Publishing your dex is sending it: the seller collects it with `ledgdex record shop --from https://asha.github.io`, or you send the claim file any other way and the seller runs `ledgdex record shop claim-....json`.
|
|
71
|
+
|
|
72
|
+
## The rest of a trade
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
ledgdex receipt me https://farm.github.io buyer keeps the seller's record: the receipt and the claim id
|
|
76
|
+
ledgdex pay me https://farm.github.io CLAIM_ID --method upi --ref UTR123
|
|
77
|
+
ledgdex record shop --from https://asha.github.io seller records the payment notice
|
|
78
|
+
ledgdex received shop CLAIM_ID 240000
|
|
79
|
+
ledgdex delivered shop CLAIM_ID --note "by courier"
|
|
80
|
+
ledgdex confirm me https://farm.github.io CLAIM_ID
|
|
81
|
+
ledgdex record shop --from https://asha.github.io the claim is now closed
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Publish after each step that others need to see. Anyone can check any ledger:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
ledgdex verify https://farm.github.io
|
|
88
|
+
ledgdex state https://farm.github.io
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Disputes
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
ledgdex dispute me CLAIM_ID --seller https://farm.github.io --text "never arrived" buyer
|
|
95
|
+
ledgdex record shop --from https://asha.github.io
|
|
96
|
+
ledgdex ruling judge https://farm.github.io DISPUTE_ID --outcome refund the offer's arbiter
|
|
97
|
+
ledgdex record shop --from https://judge.github.io
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Sealed-bid auctions
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
ledgdex auction shop auction.json {"item": {"title": "Old map"}, "currency": "INR", "close": "...", "reveal_until": "...", "reserve": 10000}
|
|
104
|
+
ledgdex bid me https://farm.github.io AUCTION_ID --amount 25000 only a commitment is sent; the amount stays in ~/.ledgdex/bids
|
|
105
|
+
ledgdex reveal me https://farm.github.io AUCTION_ID after the close, before reveal_until
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The seller records bids and reveals with `ledgdex record`. The best revealed bid that meets the reserve wins; payment and delivery then use the auction id in place of a claim id.
|
|
109
|
+
|
|
110
|
+
## Keys
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
ledgdex keygen phone on the phone: prints its public key
|
|
114
|
+
ledgdex device add shop ed25519:... --name phone with the owner key: the phone may now sign
|
|
115
|
+
ledgdex device revoke shop ed25519:... --reason lost
|
|
116
|
+
ledgdex rotate shop --key farm2 replace the owner key
|
|
117
|
+
ledgdex recover root shop ed25519:NEW the root names a new key for a lost one
|
|
118
|
+
ledgdex recovered shop --root ROOT --key farm-new the owner takes the ledger back
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
On a machine with a device key, everyday commands sign with it. Messages to other ledgers (claims, payments, bids) are signed with the owner key, which is a self's identity in other ledgers.
|
|
122
|
+
|
|
123
|
+
## Indexes and the root
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
ledgdex list market https://farm.github.io --note "mangoes" an index lists ledgers
|
|
127
|
+
ledgdex discover https://market.github.io --offers what the listed ledgers sell
|
|
128
|
+
ledgdex admit root ed25519:... the root admits selves to The Matrix
|
|
129
|
+
ledgdex state shop --root https://root.github.io "allow": "admitted" then needs the root too
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Running the root
|
|
133
|
+
|
|
134
|
+
The root is the ledger whose admissions define who is in The Matrix, and its owner is the default arbiter. Today
|
|
135
|
+
Noorul Ali, founder of The Matrix, runs it. It is an ordinary ledgdex:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
ledgdex init root --name "The Matrix root" --about "Admission to The Matrix" --key matrix-root \
|
|
139
|
+
--url https://ROOT_ADDRESS --dest git@github.com:YOU/root.git --branch main
|
|
140
|
+
ledgdex publish root
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Keep the owner key (`~/.ledgdex/matrix-root.key`) backed up offline. For daily work, give the laptop a device key,
|
|
144
|
+
which may admit and revoke but not change keys. Recovery hands a ledger to a new key, so `ledgdex recover` needs the
|
|
145
|
+
owner key itself:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
ledgdex keygen root-laptop prints ed25519:...
|
|
149
|
+
ledgdex device add root ed25519:... --name laptop with the owner key, once
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then, with `ledgdex publish root` after each change:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
ledgdex admit root ed25519:THEIR_KEY --name "Shop 12" --note "ground floor" admit a self
|
|
156
|
+
ledgdex revoke root ed25519:THEIR_KEY --reason "left the mall" take it back
|
|
157
|
+
ledgdex recover root https://THEIR_DEX ed25519:THEIR_NEW_KEY with the owner key, after checking who they are
|
|
158
|
+
ledgdex list root https://THEIR_DEX --note "Shop 12" optional: the root as an index too
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Every dex that trades in The Matrix names the root in its `config.json`, as `"ledgdex": {"root": "https://ROOT_ADDRESS"}`.
|
|
162
|
+
The first time ledgdex reads it, it adds `"root_id"`, the root's ledger id, and from then on refuses any other ledger
|
|
163
|
+
at that address (or a broken one): whoever controls the address cannot swap in a root of their own. Set `root_id`
|
|
164
|
+
yourself, from a source you trust, to pin it before the first read. The dex also keeps the last root it read, in `.ledgdex-root.jsonl`: when
|
|
165
|
+
the root's address is down, commands work on with that copy, and an address serving an older root is ignored.
|
|
166
|
+
Its "admitted" offers then need admission by the root too, and its offers and auctions default to the root's owner as
|
|
167
|
+
arbiter. Rulings are signed with the owner key, since that is the arbiter key offers name:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
ledgdex ruling root https://SELLER_DEX DISPUTE_ID --outcome refund --text "..."
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Run `ledgdex check` on the root (the GitHub workflow, or cron) so any change to its history is caught.
|
|
174
|
+
|
|
175
|
+
## JavaScript and the viewer
|
|
176
|
+
|
|
177
|
+
`js/` holds the same core in plain browser JavaScript (no npm): canonical JSON, SHA-256/512, Ed25519, verification, the state function, the dex pages (`render.js`) and the dexweb build (`dexweb.js`). `node js/test.mjs` checks it reproduces the shared vectors in `vectors/` byte for byte: states, pages, and whole dexs that dexweb built. `python py/tests/make_vectors.py` regenerates them.
|
|
178
|
+
|
|
179
|
+
The site https://ledgdex.github.io comes from three dexes. Dexweb publishes them to [ledgdex/ledgdex.github.io](https://github.com/ledgdex/ledgdex.github.io), and GitHub Pages serves that repository.
|
|
180
|
+
|
|
181
|
+
| Source | Published to | Address | Publish with |
|
|
182
|
+
|---|---|---|---|
|
|
183
|
+
| `home/` (home page, Live Markets, About) | the root | https://ledgdex.github.io | `cd home && python run.py` |
|
|
184
|
+
| `viewer/` (the viewer and the JavaScript modules) | `viewer/` | https://ledgdex.github.io/viewer/index.html | `cd viewer && python run.py` |
|
|
185
|
+
| `guide/dex/` (the docs) | `docs/` | https://ledgdex.github.io/docs/ | `cd guide/dex && ledgdex publish .` |
|
|
186
|
+
|
|
187
|
+
After you change `viewer/` or `js/`, publish the viewer again. `python run.py --build-only` builds `gen/` and does not publish. The home pages run no scripts. Only the viewer runs scripts.
|
|
188
|
+
|
|
189
|
+
The viewer page loads any ledger by address or file, verifies it in the browser, and shows it as the same pages its dex has. It signs claims, payments, confirmations, disputes, bids and reveals with a key kept in the browser, using the browser's own constant-time Web Crypto Ed25519 when it has one, else ledgdex's built-in code. The key is stored only sealed with your passphrase ("Keep, sealed"), and "Download key backup" gives you a copy to keep offline. The viewer also keeps your own ledger: every message you sign is kept as `sent`, and "Collect receipts" keeps the seller's records of them. "Download your dex" gives you your ledger as a complete dex (a zip), file for file what `ledgdex init` and dexweb make; publish it like any dex. So a buyer with only a browser has a ledgdex too. Open a ledger directly with `https://ledgdex.github.io/viewer/index.html?ledger=https://farm.github.io`.
|
|
190
|
+
|
|
191
|
+
## Checking for tampering
|
|
192
|
+
|
|
193
|
+
`ledgdex verify` checks one ledger once. `ledgdex check` checks ledgers over time:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
ledgdex check shop https://farm.github.io once; exit code 1 on tampering
|
|
197
|
+
ledgdex check shop --every 3600 as a worker, every hour
|
|
198
|
+
17 6 * * * cd /srv/ledgdex && ledgdex check shop --json report.json from cron
|
|
199
|
+
ledgdex workflow seller/ledgdex.jsonl > .github/workflows/ledgdex-check.yml as a GitHub workflow
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
It keeps the last copy of every ledger it has seen (in `.ledgdex-check/`) and reports an error, with signed proof in `.ledgdex-check/proofs/`, when a ledger stops extending that copy (two entries at one seq), when a receipt no longer matches the ledger it came from, or when an address starts serving a different ledger. Receipts in the ledgers you check are followed to the sellers' ledgers automatically. `--media` also checks offer pictures against their hashes; `--published` compares a dex folder with its published ledger. A copy that is only behind (a cache or an old snapshot) is a warning, not an error. The GitHub workflow runs on every push, daily and by hand, keeps its last-seen copies in the Actions cache, and attaches the report and proofs to each run.
|
|
203
|
+
|
|
204
|
+
## Every ledgdex is a dex
|
|
205
|
+
|
|
206
|
+
Each command rewrites the ledgdex pages in `data.json` (pages whose first paragraph starts with `<!-- ledgdex -->`) and rebuilds the site with dexweb. Your own pages are never touched. `ledgdex publish` publishes with dexweb; `config.json` has `"append_only": ["ledgdex.jsonl"]`, so dexweb refuses to publish a ledger that does not extend the published one. Publishing from two devices is safe: the second one catches up and re-sequences its unpublished entries.
|
|
207
|
+
|
|
208
|
+
## Production (v1.0)
|
|
209
|
+
|
|
210
|
+
Before you trade for real, read [SECURITY.md](SECURITY.md). In short:
|
|
211
|
+
|
|
212
|
+
- Install the `fast` extra (`pip install "ledgdex[fast]"`) on every machine that signs: it signs in constant time. ledgdex warns when it signs without it.
|
|
213
|
+
- Keep owner keys offline (backed up), and use device keys day to day. Revoke a lost device at once.
|
|
214
|
+
- Run `ledgdex check` on your ledger and the ledgers you trade with, daily (the workflow, cron or a worker). An error there is tampering, with proof.
|
|
215
|
+
- In the browser, keep your key sealed with a long passphrase and download a backup. Use the viewer at https://ledgdex.github.io/viewer/index.html, or host it on an address of its own: every page on one origin can read what pages there store.
|
|
216
|
+
- Generated workflows install ledgdex from the release tag and pin each action to a commit.
|
|
217
|
+
|
|
218
|
+
## Tests
|
|
219
|
+
|
|
220
|
+
Two suites. `python run_tests.py` is the essential one (about 30 s): every Python test except the docs examples and
|
|
221
|
+
the tutorial, with the fast backend, and the JavaScript core against the shared vectors; run it after small changes.
|
|
222
|
+
`python run_tests.py --full` is the full one (about 10 min): every Python test on both Ed25519 backends, the
|
|
223
|
+
JavaScript vectors, and a round of differential fuzzing; run it after larger changes and before a release. CI runs
|
|
224
|
+
the essential suite on every push (on Linux), and the full suite (on Linux, macOS and Windows) for release tags, by
|
|
225
|
+
hand, weekly, and for a push whose commit message contains `[full tests]`. Both work in a clone and in
|
|
226
|
+
the source package on PyPI, which holds the tests and their data; the wheel holds only the code. Steps:
|
|
227
|
+
https://ledgdex.github.io/docs/testlocalledgdexinstallation.html. It needs git and node 20 or later. The parts one by one:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
cd py && python -m unittest discover -s tests
|
|
231
|
+
node js/test.mjs
|
|
232
|
+
cd py && python tests/fuzz.py --seed 7 --ledgers 1000 Python and JavaScript must agree on random and corrupted ledgers
|
|
233
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{"ledger":1,"name":"Ledgdex Docs","owner":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65"}
|
|
2
|
+
{"msg":{"at":"2026-10-20T09:00:00Z","body":{"about":"The ledger of the ledgdex docs: a demonstration. Its key was destroyed after signing, so nothing can be added to it, and nobody may buy from it.","dex":"https://ledgdex.github.io/docs"},"by":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65","sig":"48971bc58b4e5b55ff61660d5348fd0c7084443ebbaef1bb055ed36220930c21124dbd8304b828f8bd9518a6745deedc7fb14fed00b21c31f2e6551adc756c01","type":"open","v":1},"prev":"sha256:7b3d502db18a78b0c53a2c23d0ae2cf6281d27aa98708f9ae3edf0802d8f345c","seq":0,"sig":"608a79e5be4420a51b851629457143d7edeba04913687bdd06a0760fbd82bbefd57555fabe4e183a77c9eef07e6e3a1522bde05013a3c3657135e183b741840d","time":"2026-10-20T09:00:00Z"}
|
|
3
|
+
{"msg":{"at":"2026-10-20T09:00:01Z","body":{"allow":[],"arbiter":{"key":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65","url":""},"currency":"USD","item":{"media":[],"text":"A demonstration of the page ledgdex makes for an offer: the weights of Penjointu, a neural network that analyzes the bone density of knee joints. Nobody may buy it.","title":"Example offer: Penjointu v1 weights"},"pay":[{"method":"card","to":"https://pay.example/biobot"}],"price":250000,"quantity":10,"terms":"An example in the ledgdex docs: \"allow\" is an empty list, so every claim is refused.","unit":"license"},"by":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65","sig":"f15781fbad22189ca3d83b0f1e820e54033a5bd430443acc497c2434c8fe656475eb878ed97b60eceb592839ad671fe9ae65bda85f19b8c76ce1749ea8681c0c","type":"offer","v":1},"prev":"sha256:c934353ae5d46814eeb94844b46dbc31ae9af77b6fe4c70785d37e1a831c8e6b","seq":1,"sig":"fe42164cfea5a20fb3747c301029c4d4196d467f2f44f60a7963ad61d88f4713178eba4bb67b28685a3e1bbf8345050dad99ae2a194fab742bcf8cd2db216801","time":"2026-10-20T09:00:01Z"}
|
|
4
|
+
{"msg":{"at":"2026-10-20T09:00:02Z","body":{"allow":[],"arbiter":{"key":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65","url":""},"best":"highest","close":"2026-10-28T09:00:00Z","currency":"USD","item":{"media":[],"text":"A demonstration of the page ledgdex makes for a sealed-bid auction: the v2 weights, 90 days before all other clinics. Nobody may bid.","title":"Example auction: Penjointu v2 early access"},"reserve":1000000,"reveal_until":"2026-10-29T09:00:00Z","terms":"An example in the ledgdex docs."},"by":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65","sig":"cb188870d7b4189fd5591c7ae7e0b5776d89085594edde2b18475ba2eff2f7cc5daa5c7d393f7d30d5648a5a75af0b5191698669fefecaef910eb6240d11db07","type":"auction","v":1},"prev":"sha256:039bb4080d22c05d5f464e137c3f8ec720e64734bcd52fdf7677c3a72ca7aadc","seq":2,"sig":"598a418c8a10c57adc628ffc623f074857033e0e11a4d6ef561c941e6066f0fa876dbbe9957c4e84c103e2b5271823d2896c61c724a624a472d888b53041db08","time":"2026-10-20T09:00:02Z"}
|
|
5
|
+
{"msg":{"at":"2026-10-20T09:00:03Z","body":{"ref":"sha256:039bb4080d22c05d5f464e137c3f8ec720e64734bcd52fdf7677c3a72ca7aadc","text":"This ledger was signed once, for the docs."},"by":"ed25519:109fe19cccb5efede333f889bfedf3bb30ab39b0eeec7d6898dbb40de25c1a65","sig":"6593e48a66c33bb60f69854707f35cfaa7679bb3e10cc078f03c4132e2db7811798ee9383f38aa13dfc88461ed9ece4b37f0d8c7bea8f3c7193ed99beb3b2000","type":"note","v":1},"prev":"sha256:e78a0fa08f3f9c1fb85d69fc66e43e9ac5a04248f0921cc303f2fe302b2cc664","seq":3,"sig":"2ad71ad9ef800cade5e92a381fe7dd2f3cea86eea2a489ae16500429a8e3ee252afaa6a725d1aa3a205aa855b6c6a3797a1000aba126df04ad006c725095b50f","time":"2026-10-20T09:00:03Z"}
|