1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177 | ;; Dev-only self-test: the artifacts, the ledger fold, the pay link, escrow.
;;
;; Same shape as social-kit's: one exported promise, an in-memory localStorage
;; shim so it runs under node, and a `check` that throws on the first
;; disagreement. Deliberately assertion-light — the real contract lives in
;; test/vectors/, which runs against dist/. This is the smoke test a consumer
;; can call from a console to find out whether the kit is wired up at all, and
;; the later package that adds transport, stakes and trust extends it.
(ns ardegazu.wallet.selftest
(:require [ardegazu.id.identity :as id-identity]
[ardegazu.wallet.consts :as consts]
[ardegazu.wallet.ledger :as ledger]
[ardegazu.wallet.paylink :as paylink]
[ardegazu.wallet.shapes :as shapes])
(:require-macros [ardegazu.wallet.macros :refer [awaits obj oget]]))
(defn- check
"`ok?`, not `cond` — social-kit's copy of this helper takes `cond`, which
shadows the core macro for the whole body. Nothing there needed it; the source
lint here noticed anyway (test/source-hygiene.test.mjs)."
[ok? what]
(when-not ok?
(throw (js/Error. (str "wallet-kit self-test FAILED: " what)))))
(defn- ensure-local-storage []
;; node has no localStorage — dev-only in-memory shim for LedgerStore
(when (identical? (js* "typeof globalThis.localStorage") "undefined")
(let [m (js/Map.)]
(unchecked-set js/globalThis "localStorage"
(js-obj "getItem" (fn [k] (let [v (.get m k)]
(if (identical? v js/undefined) nil v)))
"setItem" (fn [k v] (.set m k v) js/undefined)
"removeItem" (fn [k] (.delete m k) js/undefined))))))
(defn- mk-identity [] (.fromSeed ^js id-identity/Identity (.newSeed ^js id-identity/Identity)))
(defn- pub-of [identity] (oget identity "publicKeyB64"))
(defn- bal [store cur] (unchecked-get (.balances ^js store) cur))
(defn- escrow-self-check
"One atomic-trade leg, end to end: lock, receipt, release.
Kept out of the main chain deliberately — it needs no ledger and no pay link,
and threading it through would deepen an already fourteen-level `awaits`
ladder for no coupling. Returns a promise; the caller awaits it like any
other step."
[banker payer payee cur]
(awaits [hl (shapes/new-hashlock)]
(check (.test consts/DIGEST-RE (oget hl "pre")) "hashlock preimage is well shaped")
(awaits [opens (shapes/hashlock-matches (oget hl "pre") (oget hl "hash"))
wrong (shapes/hashlock-matches (oget hl "hash") (oget hl "hash"))
junk (shapes/hashlock-matches nil (oget hl "hash"))]
(check opens "the preimage opens its own digest")
(check (not wrong) "a wrong preimage does not")
(check (not junk) "and a malformed one is false, not a throw")
(awaits [lk (shapes/build-lock
payer (obj "cur" cur "amt" 100 "to" (pub-of payee)
"seq" 1 "hash" (oget hl "hash") "ctx" "selftest:match"))
lv (shapes/verify-lock lk nil)]
(check (some? lv) "the lock verifies")
;; `order-shape` is SYNCHRONOUS and answers null — it cannot go in an
;; `awaits` binding vector, which chains .then over every value.
(check (nil? (shapes/order-shape lk nil))
"and a lock is never readable as a payment order")
(awaits [wlr (shapes/build-lock-receipt banker lk (obj "seq" "selftest-claim-1"))
rv (shapes/verify-lock-receipt wlr (pub-of banker) nil)
rcross (shapes/verify-lock-receipt wlr (pub-of payee) nil)
rel (shapes/build-release payee (obj "cur" cur "lh" "selftest-lock-entry"))
relv (shapes/verify-release rel (pub-of payee) nil)
relcross (shapes/verify-release rel (pub-of payer) nil)]
(check (some? rv) "the lock receipt verifies")
(check (nil? rcross) "a receipt expected from another bank is rejected")
(check (some? relv) "the beneficiary's release verifies")
(check (nil? relcross) "a release expected from another party is rejected")
js/undefined)))))
(defn run-wallet-kit-self-test []
(awaits [_ (js/Promise.resolve nil)]
(ensure-local-storage)
;; currency ids: the neutral unit parses as a denomination and settles never
(check (shapes/is-neutral-unit "~.GAZ") "neutral unit recognised")
(check (not (shapes/is-payable-currency "~.GAZ")) "neutral unit is not payable")
(check (identical? (oget (shapes/parse-currency "~.GAZ") "banker") "~")
"neutral unit still parses")
(awaits [banker (mk-identity)
payer (mk-identity)
payee (mk-identity)]
(let [cur (str (pub-of banker) ".LEI")
store (ledger/LedgerStore. (pub-of payer))
pstore (ledger/LedgerStore. (pub-of payee))
bstore (ledger/LedgerStore. (pub-of banker))]
(check (identical? (shapes/banker-of cur) (pub-of banker)) "banker parsed")
(check (identical? (.nextSeq ^js store cur) 1) "the first sequence is 1")
(awaits [req (shapes/build-pay-request
payee (obj "cur" cur "amt" 250 "tox" (pub-of payee) "memo" "cafea"))
back (paylink/parse-pay-fragment (paylink/pay-request-url req))]
(check (and (some? back)
(identical? (oget back "amt") 250)
(identical? (oget back "memo") "cafea")
(identical? (oget back "to") (pub-of payee)))
"pay link round trip")
(let [forged (js/Object.assign (js-obj) req)]
(unchecked-set forged "amt" 999)
;; js/undefined for nowMs everywhere here: this is a smoke test run
;; by a person on one machine, so the reader's own wall clock is the
;; right admission clock. `null` is for a replicated fold.
(awaits [bad (shapes/verify-pay-request forged js/undefined)]
(check (nil? bad) "tampered request rejected")
(awaits [po (shapes/build-order
payer (obj "cur" cur "amt" 250 "to" (pub-of payee)
"seq" (.nextSeq ^js store cur)))
added (.addOrder ^js store po)]
;; 1 is "recorded AND on disk"; 2 would mean the shim storage
;; refused the write, which is itself worth failing on here
(check (identical? added 1) "order recorded and persisted")
(check (identical? (oget (bal store cur) "settled") 0)
"a pending order settles nothing")
(check (and (identical? (oget (bal store cur) "held") 250)
(identical? (oget (bal store cur) "available") -250))
"a pending order holds")
(check (identical? (.nextSeq ^js store cur) 2) "the sequence advances")
(awaits [rc (shapes/build-settlement banker po (obj "seq" "log-1"))
v (shapes/verify-settlement rc (pub-of banker) js/undefined)
wrong (shapes/verify-settlement rc (pub-of payee) js/undefined)]
(check (some? v) "receipt verifies")
(check (nil? wrong) "a receipt replayed under another bank is rejected")
(awaits [applied (.applyReceipt ^js store rc)
again (.applyReceipt ^js store rc)]
(check (identical? applied 1) "receipt folds and persists")
;; 0, not false — and `(not 0)` is false in ClojureScript,
;; so this comparison has to be explicit
(check (identical? again 0) "receipt fold is idempotent")
(check (and (identical? (oget (bal store cur) "settled") -250)
(identical? (oget (bal store cur) "held") 0))
"settlement moves held into settled")
(check (zero? (.-length (.pendingOrders ^js store)))
"a settled order leaves the pending list")
(awaits [wri (shapes/build-issuance-receipt
banker (obj "cur" cur "seq" 1 "to" (pub-of payer)
"amt" 40 "h" "selftest-mint-1"))
iv (shapes/verify-issuance-receipt wri (pub-of banker) js/undefined)
icross (shapes/verify-issuance-receipt wri (pub-of payee) js/undefined)
iap (.applyIssuance ^js store wri)
iagain (.applyIssuance ^js store wri)
_ (.applyReceipt ^js pstore rc)
_esc (escrow-self-check banker payer payee cur)]
(check (some? iv) "issuance receipt verifies")
(check (nil? icross) "an issuance expected from another bank is rejected")
(check (identical? iap 1) "issuance folds and persists")
(check (identical? iagain 0) "issuance fold is idempotent")
(check (identical? (oget (bal store cur) "settled") -210)
"a mint credits the recipient: -250 + 40")
(check (identical? (oget (bal pstore cur) "settled") 250)
"the payee is credited by the same receipt")
(check (identical? (oget (bal pstore cur) "settledExact")
(js/BigInt 250))
"and the exact fold agrees with the Number")
(awaits [stranger (.importJson ^js bstore (.exportJson ^js pstore))]
(check (identical? stranger 0)
"another identity's exported ledger is refused")
(let [blob (.exportJson ^js pstore)]
(.prune ^js pstore (fn [_ _] false))
(check (zero? (.-length (.receipts ^js pstore)))
"prune is the only deletion, and it deleted")
(awaits [n (.importJson ^js pstore blob)]
(check (identical? n 1) "export/import round trip")
(js/console.info "✅ wallet-kit self-test passed")
js/undefined))))))))))))))
|